lib_serial.c

/**
 * # Serial Port Access
 *
 * The `serial` module provides raw access to the termios terminal API and
 * the serial port modem control lines.
 *
 * Functions can be individually imported and directly accessed using the
 * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import}
 * syntax:
 *
 *   ```
 *   import { attr, B115200 } from 'serial';
 *
 *   let fd = io.open('/dev/ttyS0', io.O_RDWR);
 *   print(attr(fd));
 *   ```
 *
 * Alternatively, the module namespace can be imported
 * using a wildcard import statement:
 *
 *   ```
 *   import * as serial from 'serial';
 *
 *   let fd = io.open('/dev/ttyS0', io.O_RDWR);
 *   print(serial.attr(fd));
 *   ```
 *
 * Additionally, the serial module namespace may also be imported by invoking
 * the `ucode` interpreter with the `-lserial` switch.
 *
 * @module serial
 */

#define _DEFAULT_SOURCE

#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>

#ifdef __linux__
#include <linux/serial.h>
#endif

#include "ucode/module.h"

#ifndef TIOCINQ
#define TIOCINQ FIONREAD
#endif

#define err_return(err) do { \
	uc_vm_registry_set(vm, "serial.last_error", ucv_int64_new(err)); \
	return NULL; \
} while(0)

static int
get_fd(uc_vm_t *vm, uc_value_t *val)
{
	uc_value_t *fn = ucv_property_get(val, "fileno");
	int64_t n;

	errno = 0;

	if (ucv_is_callable(fn)) {
		uc_vm_stack_push(vm, ucv_get(val));
		uc_vm_stack_push(vm, ucv_get(fn));

		if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE)
			return -1;

		val = uc_vm_stack_pop(vm);
		n = ucv_int64_get(val);
		ucv_put(val);
	}
	else {
		n = ucv_int64_get(val);
	}

	if (errno || n < 0 || n > (int64_t)INT_MAX)
		return -1;

	return (int)n;
}

/**
 * Query error information.
 *
 * Returns a string containing a description of the last occurred error or
 * `null` if there is no error information.
 *
 * @function module:serial#error
 *
 * @returns {?string}
 */
static uc_value_t *
uc_serial_error(uc_vm_t *vm, size_t nargs)
{
	int last_error = ucv_int64_get(uc_vm_registry_get(vm, "serial.last_error"));

	if (last_error == 0)
		return NULL;

	uc_vm_registry_set(vm, "serial.last_error", ucv_int64_new(0));

	return ucv_string_new(strerror(last_error));
}

/**
 * Check whether the file descriptor refers to a terminal.
 *
 * @function module:serial#isatty
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @returns {?boolean}
 * Returns `true` if the file descriptor refers to a terminal device,
 * `false` otherwise, or `null` if an error occurred.
 */
static uc_value_t *
uc_serial_isatty(uc_vm_t *vm, size_t nargs)
{
	int fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	return ucv_boolean_new(isatty(fd) == 1);
}

/**
 * Get terminal attributes.
 *
 * Retrieves the current termios attributes for the file descriptor.
 *
 * Returns an object containing the `iflag`, `oflag`, `cflag` and `lflag`
 * flag values, the `ispeed` and `ospeed` baud rates, and a `cc` array of
 * control character settings.
 *
 * Returns `null` if an error occurred or if the descriptor is not a terminal.
 *
 * @function module:serial#attr
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @returns {?object}
 */
static uc_value_t *
uc_serial_attr(uc_vm_t *vm, size_t nargs)
{
	struct termios t;
	uc_value_t *rv, *cc;
	int fd, i;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (tcgetattr(fd, &t) != 0)
		err_return(errno);

	rv = ucv_object_new(vm);

	ucv_object_add(rv, "iflag", ucv_uint64_new(t.c_iflag));
	ucv_object_add(rv, "oflag", ucv_uint64_new(t.c_oflag));
	ucv_object_add(rv, "cflag", ucv_uint64_new(t.c_cflag));
	ucv_object_add(rv, "lflag", ucv_uint64_new(t.c_lflag));
	ucv_object_add(rv, "ispeed", ucv_uint64_new(cfgetispeed(&t)));
	ucv_object_add(rv, "ospeed", ucv_uint64_new(cfgetospeed(&t)));

	cc = ucv_array_new(vm);

	for (i = 0; i < NCCS; i++)
		ucv_array_push(cc, ucv_uint64_new(t.c_cc[i]));

	ucv_object_add(rv, "cc", cc);

	return rv;
}

/**
 * Set terminal attributes.
 *
 * Updates the termios attributes for the file descriptor. The given object
 * may contain any of the following properties:
 *
 *  - `iflag`: input flags
 *  - `oflag`: output flags
 *  - `cflag`: control flags
 *  - `lflag`: local flags
 *  - `ispeed`: input baud rate
 *  - `ospeed`: output baud rate
 *  - `cc`: array of control character settings
 *
 * Only the provided properties are modified, all other attributes are left
 * unchanged.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#setattr
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {object} attrs
 * The terminal attributes to set.
 *
 * @param {number} [when=0]
 * When to apply the changes (`TCSANOW`, `TCSADRAIN` or `TCSAFLUSH`).
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_setattr(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *attrs = uc_fn_arg(1);
	uc_value_t *when = uc_fn_arg(2);
	uc_value_t *v, *cc;
	struct termios t;
	int fd, act;
	size_t i, n;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ucv_type(attrs) != UC_OBJECT)
		err_return(EINVAL);

	if (tcgetattr(fd, &t) != 0)
		err_return(errno);

	v = ucv_object_get(attrs, "iflag", NULL);
	if (v) t.c_iflag = (tcflag_t)ucv_to_unsigned(v);

	v = ucv_object_get(attrs, "oflag", NULL);
	if (v) t.c_oflag = (tcflag_t)ucv_to_unsigned(v);

	v = ucv_object_get(attrs, "cflag", NULL);
	if (v) t.c_cflag = (tcflag_t)ucv_to_unsigned(v);

	v = ucv_object_get(attrs, "lflag", NULL);
	if (v) t.c_lflag = (tcflag_t)ucv_to_unsigned(v);

	v = ucv_object_get(attrs, "ispeed", NULL);
	if (v) cfsetispeed(&t, (speed_t)ucv_to_unsigned(v));

	v = ucv_object_get(attrs, "ospeed", NULL);
	if (v) cfsetospeed(&t, (speed_t)ucv_to_unsigned(v));

	cc = ucv_object_get(attrs, "cc", NULL);

	if (ucv_type(cc) == UC_ARRAY) {
		n = ucv_array_length(cc);

		for (i = 0; i < n && i < NCCS; i++) {
			v = ucv_array_get(cc, i);
			if (v) t.c_cc[i] = (cc_t)ucv_to_unsigned(v);
		}
	}

	act = (ucv_type(when) == UC_INTEGER) ? (int)ucv_int64_get(when) : TCSANOW;

	if (tcsetattr(fd, act, &t) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Set the terminal baud rate.
 *
 * Sets both the input and output baud rate of the file descriptor to the
 * given speed.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#setspeed
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {number} speed
 * The baud rate to set, e.g. `B115200`.
 *
 * @param {number} [when=0]
 * When to apply the changes (`TCSANOW`, `TCSADRAIN` or `TCSAFLUSH`).
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_setspeed(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *speed = uc_fn_arg(1);
	uc_value_t *when = uc_fn_arg(2);
	struct termios t;
	speed_t spd;
	int fd, act;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ucv_type(speed) != UC_INTEGER)
		err_return(EINVAL);

	spd = (speed_t)ucv_to_unsigned(speed);

	if (tcgetattr(fd, &t) != 0)
		err_return(errno);

	if (cfsetispeed(&t, spd) != 0 || cfsetospeed(&t, spd) != 0)
		err_return(errno);

	act = (ucv_type(when) == UC_INTEGER) ? (int)ucv_int64_get(when) : TCSANOW;

	if (tcsetattr(fd, act, &t) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Put the terminal into raw mode.
 *
 * Disables input and output processing, canonical mode, signal generation
 * and echo, following the behaviour of `cfmakeraw()`.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#setraw
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {number} [when=0]
 * When to apply the changes (`TCSANOW`, `TCSADRAIN` or `TCSAFLUSH`).
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_setraw(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *when = uc_fn_arg(1);
	struct termios t;
	int fd, act;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (tcgetattr(fd, &t) != 0)
		err_return(errno);

	cfmakeraw(&t);

	act = (ucv_type(when) == UC_INTEGER) ? (int)ucv_int64_get(when) : TCSANOW;

	if (tcsetattr(fd, act, &t) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Configure the terminal read blocking behaviour.
 *
 * Sets the `VMIN` and `VTIME` control character settings which determine
 * how reads from the terminal block:
 *
 *  - `vmin = 0`, `vtime = 0`: non-blocking reads
 *  - `vmin = 0`, `vtime > 0`: reads time out after `vtime` tenths of a second
 *  - `vmin > 0`, `vtime = 0`: reads block until at least `vmin` bytes arrive
 *  - `vmin > 0`, `vtime > 0`: reads block until `vmin` bytes arrive or the
 *    inter-byte `vtime` timeout expires
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#setblocking
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {number} vmin
 * Minimum number of bytes to read before a read operation returns.
 *
 * @param {number} vtime
 * Read timeout in tenths of a second.
 *
 * @param {number} [when=0]
 * When to apply the changes (`TCSANOW`, `TCSADRAIN` or `TCSAFLUSH`).
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_setblocking(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *vmin = uc_fn_arg(1);
	uc_value_t *vtime = uc_fn_arg(2);
	uc_value_t *when = uc_fn_arg(3);
	struct termios t;
	int fd, act;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ucv_type(vmin) != UC_INTEGER || ucv_type(vtime) != UC_INTEGER)
		err_return(EINVAL);

	if (tcgetattr(fd, &t) != 0)
		err_return(errno);

	t.c_cc[VMIN] = (cc_t)ucv_to_unsigned(vmin);
	t.c_cc[VTIME] = (cc_t)ucv_to_unsigned(vtime);

	act = (ucv_type(when) == UC_INTEGER) ? (int)ucv_int64_get(when) : TCSANOW;

	if (tcsetattr(fd, act, &t) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Get the serial port modem control line status.
 *
 * Returns a bitmask of the current modem control line states, using the
 * `TIOCM_*` constants to test individual lines.
 *
 * Returns `null` if an error occurred.
 *
 * @function module:serial#mget
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @returns {?number}
 */
static uc_value_t *
uc_serial_mget(uc_vm_t *vm, size_t nargs)
{
	int fd, bits;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ioctl(fd, TIOCMGET, &bits) != 0)
		err_return(errno);

	return ucv_int64_new(bits);
}

/**
 * Set the serial port modem control line states.
 *
 * Replaces the full set of modem control line states with the given bitmask.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#mset
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {number} bits
 * The modem control line states to set, as a bitmask of `TIOCM_*` constants.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_mset(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *b = uc_fn_arg(1);
	int fd, bits;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ucv_type(b) != UC_INTEGER)
		err_return(EINVAL);

	bits = (int)ucv_int64_get(b);

	if (ioctl(fd, TIOCMSET, &bits) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

static uc_value_t *
serial_modem_change(uc_vm_t *vm, size_t nargs, unsigned long req)
{
	uc_value_t *b = uc_fn_arg(1);
	int fd, bits;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ucv_type(b) != UC_INTEGER)
		err_return(EINVAL);

	bits = (int)ucv_int64_get(b);

	if (ioctl(fd, req, &bits) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Set individual serial port modem control lines.
 *
 * Sets the modem control lines selected by the given bitmask, leaving all
 * other lines unchanged.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#mbis
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {number} bits
 * The modem control lines to set, as a bitmask of `TIOCM_*` constants.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_mbis(uc_vm_t *vm, size_t nargs)
{
	return serial_modem_change(vm, nargs, TIOCMBIS);
}

/**
 * Clear individual serial port modem control lines.
 *
 * Clears the modem control lines selected by the given bitmask, leaving all
 * other lines unchanged.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#mbic
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {number} bits
 * The modem control lines to clear, as a bitmask of `TIOCM_*` constants.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_mbic(uc_vm_t *vm, size_t nargs)
{
	return serial_modem_change(vm, nargs, TIOCMBIC);
}

static uc_value_t *
serial_modem_line(uc_vm_t *vm, size_t nargs, int bit)
{
	uc_value_t *on = uc_fn_arg(1);
	int fd;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ioctl(fd, ucv_is_truish(on) ? TIOCMBIS : TIOCMBIC, &bit) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Assert or deassert the DTR (Data Terminal Ready) modem control line.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#dtr
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {boolean} on
 * Whether to assert (`true`) or deassert (`false`) the line.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_dtr(uc_vm_t *vm, size_t nargs)
{
	return serial_modem_line(vm, nargs, TIOCM_DTR);
}

/**
 * Assert or deassert the RTS (Request To Send) modem control line.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#rts
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {boolean} on
 * Whether to assert (`true`) or deassert (`false`) the line.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_rts(uc_vm_t *vm, size_t nargs)
{
	return serial_modem_line(vm, nargs, TIOCM_RTS);
}

/**
 * Send a break signal on the serial line.
 *
 * If the terminal is in canonical mode, the break is sent after the current
 * input line has been processed, otherwise it is sent immediately.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#sendbreak
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {number} [duration=0]
 * The break duration in seconds, or `0` to send a standard length break.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_sendbreak(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *dur = uc_fn_arg(1);
	int fd, d;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	d = (ucv_type(dur) == UC_INTEGER) ? (int)ucv_int64_get(dur) : 0;

	if (tcsendbreak(fd, d) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Wait for pending output to be written.
 *
 * Blocks until all output written to the file descriptor has been
 * transmitted.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#drain
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_drain(uc_vm_t *vm, size_t nargs)
{
	int fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (tcdrain(fd) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Flush the terminal input and/or output queues.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#flush
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {number} [queue=TCIOFLUSH]
 * The queue(s) to flush: `TCIFLUSH` for input, `TCOFLUSH` for output, or
 * `TCIOFLUSH` for both.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_flush(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *q = uc_fn_arg(1);
	int fd, queue;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	queue = (ucv_type(q) == UC_INTEGER) ? (int)ucv_int64_get(q) : TCIOFLUSH;

	if (tcflush(fd, queue) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

static uc_value_t *
serial_queue_count(uc_vm_t *vm, size_t nargs, unsigned long req)
{
	int fd, n = 0;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ioctl(fd, req, &n) != 0)
		err_return(errno);

	return ucv_int64_new(n);
}

/**
 * Get the number of bytes waiting in the input queue.
 *
 * Returns the number of bytes available for reading without blocking, or
 * `null` if an error occurred.
 *
 * @function module:serial#input_waiting
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @returns {?number}
 */
static uc_value_t *
uc_serial_input_waiting(uc_vm_t *vm, size_t nargs)
{
	return serial_queue_count(vm, nargs, TIOCINQ);
}

/**
 * Get the number of bytes waiting in the output queue.
 *
 * Returns the number of bytes pending transmission, or `null` if an error
 * occurred.
 *
 * @function module:serial#output_waiting
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @returns {?number}
 */
static uc_value_t *
uc_serial_output_waiting(uc_vm_t *vm, size_t nargs)
{
	return serial_queue_count(vm, nargs, TIOCOUTQ);
}

#ifdef __linux__
/**
 * Get the serial port configuration.
 *
 * Retrieves the serial port specific configuration (setserial style) for
 * the file descriptor.
 *
 * Returns an object containing the `type`, `line`, `port`, `irq`, `flags`,
 * `xmit_fifo_size`, `custom_divisor`, `baud_base`, `close_delay`,
 * `closing_wait`, `hub6`, `io_type`, `port_high` and `iomem_reg_shift`
 * properties, or `null` if an error occurred.
 *
 * @function module:serial#getinfo
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @returns {?object}
 */
static uc_value_t *
uc_serial_getinfo(uc_vm_t *vm, size_t nargs)
{
	struct serial_struct ss;
	uc_value_t *rv;
	int fd;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ioctl(fd, TIOCGSERIAL, &ss) != 0)
		err_return(errno);

	rv = ucv_object_new(vm);

	ucv_object_add(rv, "type", ucv_int64_new(ss.type));
	ucv_object_add(rv, "line", ucv_int64_new(ss.line));
	ucv_object_add(rv, "port", ucv_uint64_new(ss.port));
	ucv_object_add(rv, "irq", ucv_int64_new(ss.irq));
	ucv_object_add(rv, "flags", ucv_int64_new(ss.flags));
	ucv_object_add(rv, "xmit_fifo_size", ucv_int64_new(ss.xmit_fifo_size));
	ucv_object_add(rv, "custom_divisor", ucv_int64_new(ss.custom_divisor));
	ucv_object_add(rv, "baud_base", ucv_int64_new(ss.baud_base));
	ucv_object_add(rv, "close_delay", ucv_int64_new(ss.close_delay));
	ucv_object_add(rv, "closing_wait", ucv_int64_new(ss.closing_wait));
	ucv_object_add(rv, "hub6", ucv_int64_new(ss.hub6));
	ucv_object_add(rv, "io_type", ucv_int64_new(ss.io_type));
	ucv_object_add(rv, "port_high", ucv_uint64_new(ss.port_high));
	ucv_object_add(rv, "iomem_reg_shift", ucv_int64_new(ss.iomem_reg_shift));

	return rv;
}

/**
 * Set the serial port configuration.
 *
 * Updates the serial port specific configuration (setserial style) for the
 * file descriptor. The given object may contain any of the following
 * properties:
 *
 *  - `type`: port type
 *  - `port`: I/O port address
 *  - `irq`: interrupt line
 *  - `flags`: port flags
 *  - `xmit_fifo_size`: transmit FIFO size
 *  - `custom_divisor`: custom baud rate divisor
 *  - `baud_base`: base clock frequency
 *  - `close_delay`: delay before closing the port
 *  - `closing_wait`: wait time when closing the port
 *  - `hub6`: HUB6 port selection
 *  - `port_high`: high I/O port address
 *  - `iomem_reg_shift`: I/O memory register shift
 *
 * Only the provided properties are modified, all other settings are left
 * unchanged.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#setinfo
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {object} opts
 * The serial port configuration options to set.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_setinfo(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *opts = uc_fn_arg(1);
	uc_value_t *v;
	struct serial_struct ss;
	int fd;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ucv_type(opts) != UC_OBJECT)
		err_return(EINVAL);

	if (ioctl(fd, TIOCGSERIAL, &ss) != 0)
		err_return(errno);

	v = ucv_object_get(opts, "type", NULL);
	if (v) ss.type = (int)ucv_int64_get(v);

	v = ucv_object_get(opts, "port", NULL);
	if (v) ss.port = (unsigned int)ucv_to_unsigned(v);

	v = ucv_object_get(opts, "irq", NULL);
	if (v) ss.irq = (int)ucv_int64_get(v);

	v = ucv_object_get(opts, "flags", NULL);
	if (v) ss.flags = (int)ucv_int64_get(v);

	v = ucv_object_get(opts, "xmit_fifo_size", NULL);
	if (v) ss.xmit_fifo_size = (int)ucv_int64_get(v);

	v = ucv_object_get(opts, "custom_divisor", NULL);
	if (v) ss.custom_divisor = (int)ucv_int64_get(v);

	v = ucv_object_get(opts, "baud_base", NULL);
	if (v) ss.baud_base = (int)ucv_int64_get(v);

	v = ucv_object_get(opts, "close_delay", NULL);
	if (v) ss.close_delay = (unsigned short)ucv_to_unsigned(v);

	v = ucv_object_get(opts, "closing_wait", NULL);
	if (v) ss.closing_wait = (unsigned short)ucv_to_unsigned(v);

	v = ucv_object_get(opts, "hub6", NULL);
	if (v) ss.hub6 = (int)ucv_int64_get(v);

	v = ucv_object_get(opts, "port_high", NULL);
	if (v) ss.port_high = (unsigned int)ucv_to_unsigned(v);

	v = ucv_object_get(opts, "iomem_reg_shift", NULL);
	if (v) ss.iomem_reg_shift = (unsigned short)ucv_to_unsigned(v);

	if (ioctl(fd, TIOCSSERIAL, &ss) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}

/**
 * Enable or disable the low latency mode.
 *
 * Toggles the `ASYNC_LOW_LATENCY` flag in the serial port configuration,
 * which reduces the latency of the serial port at the cost of increased
 * CPU usage.
 *
 * Returns `true` on success, or `null` if an error occurred.
 *
 * @function module:serial#lowlatency
 *
 * @param {number|object} fd
 * The file descriptor, or an object with a `fileno()` method.
 *
 * @param {boolean} on
 * Whether to enable (`true`) or disable (`false`) the low latency mode.
 *
 * @returns {?boolean}
 */
static uc_value_t *
uc_serial_lowlatency(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *on = uc_fn_arg(1);
	struct serial_struct ss;
	int fd;

	fd = get_fd(vm, uc_fn_arg(0));

	if (fd < 0)
		err_return(EBADF);

	if (ioctl(fd, TIOCGSERIAL, &ss) != 0)
		err_return(errno);

	if (ucv_is_truish(on))
		ss.flags |= ASYNC_LOW_LATENCY;
	else
		ss.flags &= ~ASYNC_LOW_LATENCY;

	if (ioctl(fd, TIOCSSERIAL, &ss) != 0)
		err_return(errno);

	return ucv_boolean_new(true);
}
#endif

static const uc_function_list_t global_fns[] = {
	{ "error",       uc_serial_error },
	{ "isatty",      uc_serial_isatty },
	{ "attr",        uc_serial_attr },
	{ "setattr",     uc_serial_setattr },
	{ "setspeed",    uc_serial_setspeed },
	{ "setraw",      uc_serial_setraw },
	{ "setblocking", uc_serial_setblocking },
	{ "mget",        uc_serial_mget },
	{ "mset",        uc_serial_mset },
	{ "mbis",        uc_serial_mbis },
	{ "mbic",        uc_serial_mbic },
	{ "dtr",         uc_serial_dtr },
	{ "rts",         uc_serial_rts },
	{ "sendbreak",      uc_serial_sendbreak },
	{ "drain",          uc_serial_drain },
	{ "flush",          uc_serial_flush },
	{ "input_waiting",  uc_serial_input_waiting },
	{ "output_waiting", uc_serial_output_waiting },
#ifdef __linux__
	{ "getinfo",        uc_serial_getinfo },
	{ "setinfo",        uc_serial_setinfo },
	{ "lowlatency",     uc_serial_lowlatency },
#endif
};

void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
	uc_function_list_register(scope, global_fns);

	#define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))

	/**
	 * @typedef
	 * @name Apply Modes
	 * @description
	 * The `TCS*` constants are used as the optional *when* argument of
	 * {@link module:serial#setattr|setattr()},
	 * {@link module:serial#setspeed|setspeed()},
	 * {@link module:serial#setraw|setraw()} and
	 * {@link module:serial#setblocking|setblocking()} to control when the
	 * attribute changes take effect.
	 * @property {number} TCSANOW - Apply the changes immediately.
	 * @property {number} TCSADRAIN - Apply the changes after all pending output has been transmitted (default).
	 * @property {number} TCSAFLUSH - Apply the changes after all pending output has been transmitted, discarding any unread input.
	 */
#ifdef TCSANOW
	ADD_CONST(TCSANOW);
#endif
#ifdef TCSADRAIN
	ADD_CONST(TCSADRAIN);
#endif
#ifdef TCSAFLUSH
	ADD_CONST(TCSAFLUSH);
#endif

	/**
	 * @typedef
	 * @name Flush Queues
	 * @description
	 * The `TC*FLUSH` constants are used as the optional *queue* argument of
	 * {@link module:serial#flush|flush()} to select the queue(s) to flush.
	 * @property {number} TCIFLUSH - Flush data received but not read.
	 * @property {number} TCOFLUSH - Flush data written but not yet transmitted.
	 * @property {number} TCIOFLUSH - Flush both received and written data (default).
	 */
#ifdef TCIFLUSH
	ADD_CONST(TCIFLUSH);
#endif
#ifdef TCOFLUSH
	ADD_CONST(TCOFLUSH);
#endif
#ifdef TCIOFLUSH
	ADD_CONST(TCIOFLUSH);
#endif

	/**
	 * @typedef
	 * @name Control Flags
	 * @description
	 * The `cflag` constants select the control mode of the port and are used
	 * with the `cflag` property of {@link module:serial#setattr|setattr()}.
	 * @property {number} CSIZE - Mask for the character size bits.
	 * @property {number} CS5 - Use 5 data bits per character.
	 * @property {number} CS6 - Use 6 data bits per character.
	 * @property {number} CS7 - Use 7 data bits per character.
	 * @property {number} CS8 - Use 8 data bits per character.
	 * @property {number} CSTOPB - Use two stop bits (one if clear).
	 * @property {number} CREAD - Enable the receiver.
	 * @property {number} PARENB - Enable parity generation and detection.
	 * @property {number} PARODD - Use odd parity (even if clear).
	 * @property {number} HUPCL - Hang up (drop the carrier) when the last file descriptor is closed.
	 * @property {number} CLOCAL - Ignore the modem status lines.
	 * @property {number} CRTSCTS - Enable in-band (hardware) flow control.
	 * @property {number} CMSPAR - Use "stick" (space/mark) parity.
	 * @property {number} CBAUD - Mask for the baud rate bits.
	 * @property {number} CBAUDEX - Extended baud rate bits.
	 */
#ifdef CSIZE
	ADD_CONST(CSIZE);
#endif
#ifdef CS5
	ADD_CONST(CS5);
#endif
#ifdef CS6
	ADD_CONST(CS6);
#endif
#ifdef CS7
	ADD_CONST(CS7);
#endif
#ifdef CS8
	ADD_CONST(CS8);
#endif
#ifdef CSTOPB
	ADD_CONST(CSTOPB);
#endif
#ifdef CREAD
	ADD_CONST(CREAD);
#endif
#ifdef PARENB
	ADD_CONST(PARENB);
#endif
#ifdef PARODD
	ADD_CONST(PARODD);
#endif
#ifdef HUPCL
	ADD_CONST(HUPCL);
#endif
#ifdef CLOCAL
	ADD_CONST(CLOCAL);
#endif
#ifdef CRTSCTS
	ADD_CONST(CRTSCTS);
#endif
#ifdef CMSPAR
	ADD_CONST(CMSPAR);
#endif
#ifdef CBAUD
	ADD_CONST(CBAUD);
#endif
#ifdef CBAUDEX
	ADD_CONST(CBAUDEX);
#endif

	/**
	 * @typedef
	 * @name Input Flags
	 * @description
	 * The `iflag` constants control input processing and are used with the
	 * `iflag` property of {@link module:serial#setattr|setattr()}.
	 * @property {number} IGNBRK - Ignore the break condition.
	 * @property {number} BRKINT - If IGNBRK is not set, a break causes an interrupt signal.
	 * @property {number} IGNPAR - Ignore characters with parity errors.
	 * @property {number} PARMRK - Mark parity errors with a three-byte sequence.
	 * @property {number} INPCK - Enable input parity checking.
	 * @property {number} ISTRIP - Strip the eighth bit of input characters.
	 * @property {number} INLCR - Map NL to CR on input.
	 * @property {number} IGNCR - Ignore CR on input.
	 * @property {number} ICRNL - Map CR to NL on input.
	 * @property {number} IUCLC - Map uppercase to lowercase on input.
	 * @property {number} IXON - Enable XON/XOFF flow control output.
	 * @property {number} IXANY - Allow any character to restart output.
	 * @property {number} IXOFF - Enable XON/XOFF flow control input.
	 * @property {number} IMAXBEL - Ring the bell when the input queue is full.
	 * @property {number} IUTF8 - Input characters are UTF-8 encoded.
	 */
#ifdef IGNBRK
	ADD_CONST(IGNBRK);
#endif
#ifdef BRKINT
	ADD_CONST(BRKINT);
#endif
#ifdef IGNPAR
	ADD_CONST(IGNPAR);
#endif
#ifdef PARMRK
	ADD_CONST(PARMRK);
#endif
#ifdef INPCK
	ADD_CONST(INPCK);
#endif
#ifdef ISTRIP
	ADD_CONST(ISTRIP);
#endif
#ifdef INLCR
	ADD_CONST(INLCR);
#endif
#ifdef IGNCR
	ADD_CONST(IGNCR);
#endif
#ifdef ICRNL
	ADD_CONST(ICRNL);
#endif
#ifdef IUCLC
	ADD_CONST(IUCLC);
#endif
#ifdef IXON
	ADD_CONST(IXON);
#endif
#ifdef IXANY
	ADD_CONST(IXANY);
#endif
#ifdef IXOFF
	ADD_CONST(IXOFF);
#endif
#ifdef IMAXBEL
	ADD_CONST(IMAXBEL);
#endif
#ifdef IUTF8
	ADD_CONST(IUTF8);
#endif

	/**
	 * @typedef
	 * @name Output Flags
	 * @description
	 * The `oflag` constants control output processing and are used with the
	 * `oflag` property of {@link module:serial#setattr|setattr()}.
	 * @property {number} OPOST - Enable implementation-defined output processing.
	 * @property {number} OLCUC - Map lowercase to uppercase on output.
	 * @property {number} ONLCR - Map NL to CR-NL on output.
	 * @property {number} OCRNL - Map CR to NL on output.
	 * @property {number} ONOCR - Translate CR to NUL in the first column.
	 * @property {number} ONLRET - Do not transmit CR.
	 * @property {number} OFILL - Use fill characters for timing.
	 * @property {number} OFDEL - Use DEL characters for fill (NUL if clear).
	 */
#ifdef OPOST
	ADD_CONST(OPOST);
#endif
#ifdef OLCUC
	ADD_CONST(OLCUC);
#endif
#ifdef ONLCR
	ADD_CONST(ONLCR);
#endif
#ifdef OCRNL
	ADD_CONST(OCRNL);
#endif
#ifdef ONOCR
	ADD_CONST(ONOCR);
#endif
#ifdef ONLRET
	ADD_CONST(ONLRET);
#endif
#ifdef OFILL
	ADD_CONST(OFILL);
#endif
#ifdef OFDEL
	ADD_CONST(OFDEL);
#endif

	/**
	 * @typedef
	 * @name Local Flags
	 * @description
	 * The `lflag` constants control local (non-modem) behaviour and are used
	 * with the `lflag` property of {@link module:serial#setattr|setattr()}.
	 * @property {number} ISIG - Enable signal generation (INTR, QUIT, SUSP).
	 * @property {number} ICANON - Enable canonical mode (line-buffered input).
	 * @property {number} ECHO - Enable echoing of input characters.
	 * @property {number} ECHOE - Erase the last character on ERASE.
	 * @property {number} ECHOK - Ring the bell on the kill character.
	 * @property {number} ECHONL - Echo NL even if ECHO is not set.
	 * @property {number} ECHOCTL - Echo control characters in hat notation.
	 * @property {number} ECHOKE - Erase a killed line.
	 * @property {number} NOFLSH - Disable flushing on signal.
	 * @property {number} TOSTOP - Generate SIGTTOU for background writes.
	 * @property {number} IEXTEN - Enable implementation-defined input extensions.
	 */
#ifdef ISIG
	ADD_CONST(ISIG);
#endif
#ifdef ICANON
	ADD_CONST(ICANON);
#endif
#ifdef ECHO
	ADD_CONST(ECHO);
#endif
#ifdef ECHOE
	ADD_CONST(ECHOE);
#endif
#ifdef ECHOK
	ADD_CONST(ECHOK);
#endif
#ifdef ECHONL
	ADD_CONST(ECHONL);
#endif
#ifdef ECHOCTL
	ADD_CONST(ECHOCTL);
#endif
#ifdef ECHOKE
	ADD_CONST(ECHOKE);
#endif
#ifdef NOFLSH
	ADD_CONST(NOFLSH);
#endif
#ifdef TOSTOP
	ADD_CONST(TOSTOP);
#endif
#ifdef IEXTEN
	ADD_CONST(IEXTEN);
#endif

	/**
	 * @typedef
	 * @name Control Character Indices
	 * @description
	 * The `V*` constants are indices into the `cc` array returned by
	 * {@link module:serial#attr|attr()} and accepted by
	 * {@link module:serial#setattr|setattr()}. `NCCS` is the number of
	 * control characters in the array.
	 * @property {number} VINTR - Interrupt character (sends SIGINT).
	 * @property {number} VQUIT - Quit character (sends SIGQUIT).
	 * @property {number} VERASE - Erase character (erases the last character).
	 * @property {number} VKILL - Kill character (erases the current line).
	 * @property {number} VEOF - End-of-file character.
	 * @property {number} VTIME - Read timeout in tenths of a second.
	 * @property {number} VMIN - Minimum number of bytes for a read.
	 * @property {number} VSWTC - Switch character (XON/XOFF switching).
	 * @property {number} VSTART - Restart character (XON).
	 * @property {number} VSTOP - Stop character (XOFF).
	 * @property {number} VSUSP - Suspend character (sends SIGTSTP).
	 * @property {number} VEOL - End-of-line character (first).
	 * @property {number} VREPRINT - Reprint character (reprints the line).
	 * @property {number} VDISCARD - Discard mode toggle character.
	 * @property {number} VWERASE - Word-erase character.
	 * @property {number} VLNEXT - Literal next character (disables special characters).
	 * @property {number} VEOL2 - End-of-line character (second).
	 * @property {number} NCCS - Number of control characters.
	 */
#ifdef VINTR
	ADD_CONST(VINTR);
#endif
#ifdef VQUIT
	ADD_CONST(VQUIT);
#endif
#ifdef VERASE
	ADD_CONST(VERASE);
#endif
#ifdef VKILL
	ADD_CONST(VKILL);
#endif
#ifdef VEOF
	ADD_CONST(VEOF);
#endif
#ifdef VTIME
	ADD_CONST(VTIME);
#endif
#ifdef VMIN
	ADD_CONST(VMIN);
#endif
#ifdef VSWTC
	ADD_CONST(VSWTC);
#endif
#ifdef VSTART
	ADD_CONST(VSTART);
#endif
#ifdef VSTOP
	ADD_CONST(VSTOP);
#endif
#ifdef VSUSP
	ADD_CONST(VSUSP);
#endif
#ifdef VEOL
	ADD_CONST(VEOL);
#endif
#ifdef VREPRINT
	ADD_CONST(VREPRINT);
#endif
#ifdef VDISCARD
	ADD_CONST(VDISCARD);
#endif
#ifdef VWERASE
	ADD_CONST(VWERASE);
#endif
#ifdef VLNEXT
	ADD_CONST(VLNEXT);
#endif
#ifdef VEOL2
	ADD_CONST(VEOL2);
#endif
#ifdef NCCS
	ADD_CONST(NCCS);
#endif

	/**
	 * @typedef
	 * @name Baud Rates
	 * @description
	 * The `B*` constants select the port baud rate and are used with the
	 * `ispeed`/`ospeed` properties of {@link module:serial#setattr|setattr()}
	 * or as the *speed* argument of
	 * {@link module:serial#setspeed|setspeed()}.
	 * @property {number} B0 - Hang up (no carrier).
	 * @property {number} B50 - 50 baud.
	 * @property {number} B75 - 75 baud.
	 * @property {number} B110 - 110 baud.
	 * @property {number} B134 - 134.5 baud.
	 * @property {number} B150 - 150 baud.
	 * @property {number} B200 - 200 baud.
	 * @property {number} B300 - 300 baud.
	 * @property {number} B600 - 600 baud.
	 * @property {number} B1200 - 1200 baud.
	 * @property {number} B1800 - 1800 baud.
	 * @property {number} B2400 - 2400 baud.
	 * @property {number} B4800 - 4800 baud.
	 * @property {number} B9600 - 9600 baud.
	 * @property {number} B19200 - 19200 baud.
	 * @property {number} B38400 - 38400 baud.
	 * @property {number} B57600 - 57600 baud.
	 * @property {number} B115200 - 115200 baud.
	 * @property {number} B230400 - 230400 baud.
	 * @property {number} B460800 - 460800 baud.
	 * @property {number} B500000 - 500000 baud.
	 * @property {number} B576000 - 576000 baud.
	 * @property {number} B921600 - 921600 baud.
	 * @property {number} B1000000 - 1000000 baud.
	 * @property {number} B1152000 - 1152000 baud.
	 * @property {number} B1500000 - 1500000 baud.
	 * @property {number} B2000000 - 2000000 baud.
	 * @property {number} B2500000 - 2500000 baud.
	 * @property {number} B3000000 - 3000000 baud.
	 * @property {number} B3500000 - 3500000 baud.
	 * @property {number} B4000000 - 4000000 baud.
	 */
#ifdef B0
	ADD_CONST(B0);
#endif
#ifdef B50
	ADD_CONST(B50);
#endif
#ifdef B75
	ADD_CONST(B75);
#endif
#ifdef B110
	ADD_CONST(B110);
#endif
#ifdef B134
	ADD_CONST(B134);
#endif
#ifdef B150
	ADD_CONST(B150);
#endif
#ifdef B200
	ADD_CONST(B200);
#endif
#ifdef B300
	ADD_CONST(B300);
#endif
#ifdef B600
	ADD_CONST(B600);
#endif
#ifdef B1200
	ADD_CONST(B1200);
#endif
#ifdef B1800
	ADD_CONST(B1800);
#endif
#ifdef B2400
	ADD_CONST(B2400);
#endif
#ifdef B4800
	ADD_CONST(B4800);
#endif
#ifdef B9600
	ADD_CONST(B9600);
#endif
#ifdef B19200
	ADD_CONST(B19200);
#endif
#ifdef B38400
	ADD_CONST(B38400);
#endif
#ifdef B57600
	ADD_CONST(B57600);
#endif
#ifdef B115200
	ADD_CONST(B115200);
#endif
#ifdef B230400
	ADD_CONST(B230400);
#endif
#ifdef B460800
	ADD_CONST(B460800);
#endif
#ifdef B500000
	ADD_CONST(B500000);
#endif
#ifdef B576000
	ADD_CONST(B576000);
#endif
#ifdef B921600
	ADD_CONST(B921600);
#endif
#ifdef B1000000
	ADD_CONST(B1000000);
#endif
#ifdef B1152000
	ADD_CONST(B1152000);
#endif
#ifdef B1500000
	ADD_CONST(B1500000);
#endif
#ifdef B2000000
	ADD_CONST(B2000000);
#endif
#ifdef B2500000
	ADD_CONST(B2500000);
#endif
#ifdef B3000000
	ADD_CONST(B3000000);
#endif
#ifdef B3500000
	ADD_CONST(B3500000);
#endif
#ifdef B4000000
	ADD_CONST(B4000000);
#endif

	/**
	 * @typedef
	 * @name Modem Control Line Bits
	 * @description
	 * The `TIOCM_*` constants identify the serial port modem control lines
	 * and are used with {@link module:serial#mget|mget()},
	 * {@link module:serial#mset|mset()},
	 * {@link module:serial#mbis|mbis()} and
	 * {@link module:serial#mbic|mbic()}.
	 * @property {number} TIOCM_LE - Loopback output.
	 * @property {number} TIOCM_DTR - Data Terminal Ready.
	 * @property {number} TIOCM_RTS - Request To Send.
	 * @property {number} TIOCM_ST - Secondary transmit (TX2).
	 * @property {number} TIOCM_SR - Secondary receive (RX2).
	 * @property {number} TIOCM_CTS - Clear To Send.
	 * @property {number} TIOCM_CAR - Carrier Detect.
	 * @property {number} TIOCM_CD - Carrier Detect (alias).
	 * @property {number} TIOCM_RNG - Ring Indicator.
	 * @property {number} TIOCM_RI - Ring Indicator (alias).
	 * @property {number} TIOCM_DSR - Data Set Ready.
	 */
#ifdef TIOCM_LE
	ADD_CONST(TIOCM_LE);
#endif
#ifdef TIOCM_DTR
	ADD_CONST(TIOCM_DTR);
#endif
#ifdef TIOCM_RTS
	ADD_CONST(TIOCM_RTS);
#endif
#ifdef TIOCM_ST
	ADD_CONST(TIOCM_ST);
#endif
#ifdef TIOCM_SR
	ADD_CONST(TIOCM_SR);
#endif
#ifdef TIOCM_CTS
	ADD_CONST(TIOCM_CTS);
#endif
#ifdef TIOCM_CAR
	ADD_CONST(TIOCM_CAR);
#endif
#ifdef TIOCM_CD
	ADD_CONST(TIOCM_CD);
#endif
#ifdef TIOCM_RNG
	ADD_CONST(TIOCM_RNG);
#endif
#ifdef TIOCM_RI
	ADD_CONST(TIOCM_RI);
#endif
#ifdef TIOCM_DSR
	ADD_CONST(TIOCM_DSR);
#endif

#ifdef __linux__
	/**
	 * @typedef
	 * @name Serial Port Flags
	 * @description
	 * The `ASYNC_*` constants are flags of the `flags` property returned by
	 * {@link module:serial#getinfo|getinfo()} and accepted by
	 * {@link module:serial#setinfo|setinfo()}.
	 * @property {number} ASYNC_HUP_NOTIFY - Send SIGHUP when the port is closed.
	 * @property {number} ASYNC_FOURPORT - Enable four-port mode.
	 * @property {number} ASYNC_SAK - Enable special "SAK" character handling.
	 * @property {number} ASYNC_SPD_HI - High speed serial support (> 115200).
	 * @property {number} ASYNC_SPD_VHI - Very high speed serial support (> 230400).
	 * @property {number} ASYNC_SPD_SHI - Shigh speed serial support (1.5-3 Mbit).
	 * @property {number} ASYNC_SPD_CUST - Custom divisor baud rate.
	 * @property {number} ASYNC_SPD_WARP - Warp speed serial (bit-banged).
	 * @property {number} ASYNC_SPD_MASK - Mask for the speed selection bits.
	 * @property {number} ASYNC_SKIP_TEST - Skip the UART presence test.
	 * @property {number} ASYNC_AUTO_IRQ - Auto-detect the IRQ line.
	 * @property {number} ASYNC_CALLOUT_NOHUP - Do not send SIGHUP on close.
	 * @property {number} ASYNC_LOW_LATENCY - Enable low latency mode.
	 * @property {number} ASYNC_BUGGY_UART - Workaround for buggy UARTs.
	 * @property {number} ASYNC_CLOSING_WAIT_INF - Infinite closing wait.
	 * @property {number} ASYNC_CLOSING_WAIT_NONE - No closing wait.
	 */
#ifdef ASYNC_HUP_NOTIFY
	ADD_CONST(ASYNC_HUP_NOTIFY);
#endif
#ifdef ASYNC_FOURPORT
	ADD_CONST(ASYNC_FOURPORT);
#endif
#ifdef ASYNC_SAK
	ADD_CONST(ASYNC_SAK);
#endif
#ifdef ASYNC_SPD_HI
	ADD_CONST(ASYNC_SPD_HI);
#endif
#ifdef ASYNC_SPD_VHI
	ADD_CONST(ASYNC_SPD_VHI);
#endif
#ifdef ASYNC_SPD_SHI
	ADD_CONST(ASYNC_SPD_SHI);
#endif
#ifdef ASYNC_SPD_CUST
	ADD_CONST(ASYNC_SPD_CUST);
#endif
#ifdef ASYNC_SPD_WARP
	ADD_CONST(ASYNC_SPD_WARP);
#endif
#ifdef ASYNC_SPD_MASK
	ADD_CONST(ASYNC_SPD_MASK);
#endif
#ifdef ASYNC_SKIP_TEST
	ADD_CONST(ASYNC_SKIP_TEST);
#endif
#ifdef ASYNC_AUTO_IRQ
	ADD_CONST(ASYNC_AUTO_IRQ);
#endif
#ifdef ASYNC_CALLOUT_NOHUP
	ADD_CONST(ASYNC_CALLOUT_NOHUP);
#endif
#ifdef ASYNC_LOW_LATENCY
	ADD_CONST(ASYNC_LOW_LATENCY);
#endif
#ifdef ASYNC_BUGGY_UART
	ADD_CONST(ASYNC_BUGGY_UART);
#endif
#ifdef ASYNC_CLOSING_WAIT_INF
	ADD_CONST(ASYNC_CLOSING_WAIT_INF);
#endif
#ifdef ASYNC_CLOSING_WAIT_NONE
	ADD_CONST(ASYNC_CLOSING_WAIT_NONE);
#endif

	/**
	 * @typedef
	 * @name Serial Port Types
	 * @description
	 * The `PORT_*` constants identify the serial port type of the `type`
	 * property returned by {@link module:serial#getinfo|getinfo()} and
	 * accepted by {@link module:serial#setinfo|setinfo()}.
	 * @property {number} PORT_UNKNOWN - Unknown port type.
	 * @property {number} PORT_8250 - Generic 8250 UART.
	 * @property {number} PORT_16450 - 16450 UART.
	 * @property {number} PORT_16550 - 16550 UART.
	 * @property {number} PORT_16550A - 16550A UART.
	 * @property {number} PORT_16650 - 16650 UART.
	 * @property {number} PORT_16650V2 - 16650V2 UART.
	 * @property {number} PORT_16750 - 16750 UART.
	 */
#ifdef PORT_UNKNOWN
	ADD_CONST(PORT_UNKNOWN);
#endif
#ifdef PORT_8250
	ADD_CONST(PORT_8250);
#endif
#ifdef PORT_16450
	ADD_CONST(PORT_16450);
#endif
#ifdef PORT_16550
	ADD_CONST(PORT_16550);
#endif
#ifdef PORT_16550A
	ADD_CONST(PORT_16550A);
#endif
#ifdef PORT_16650
	ADD_CONST(PORT_16650);
#endif
#ifdef PORT_16650V2
	ADD_CONST(PORT_16650V2);
#endif
#ifdef PORT_16750
	ADD_CONST(PORT_16750);
#endif
#endif

	#undef ADD_CONST
}