Initial MCTP core code

Just a skeleton of the MCTP library at present.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ee1eee8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+
+CC = gcc
+AR = ar
+CFLAGS = -Wall -Wextra -Werror -ggdb
+CPPFLAGS = -DMCTP_LOG_STDERR -DMCTP_FILEIO -I$(LIBMCTP_DIR)
+
+LIBMCTP_DIR=./
+
+include Makefile.inc
+
+all: $(LIBMCTP)
+
+libmctp.a:
+	$(AR) rcsTPD $@ $^
+
+tests/%: tests/%.o libmctp.a
+	$(LINK.o) -o $@ $^
+
+clean:
+	rm -f $(LIBMCTP)
+	rm -f $(LIBMCTP_OBJS)
+	rm -f tests/*.o
diff --git a/Makefile.inc b/Makefile.inc
new file mode 100644
index 0000000..99edaac
--- /dev/null
+++ b/Makefile.inc
@@ -0,0 +1,6 @@
+LIBMCTP_DIR ?= libmctp/
+LIBMCTP_OBJS = core.o alloc.o serial.o
+
+LIBMCTP = $(LIBMCTP_DIR)libmctp.a
+
+$(LIBMCTP): $(LIBMCTP_OBJS:%=$(LIBMCTP_DIR)%)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d58f51b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,68 @@
+libmctp: Implementation of MCTP (DTMF DSP0236)
+==============================================
+
+This library is intended to be a portable implementation of the Management
+Component Transport Protocol (MCTP), as defined by DMTF standard "DSP0236",
+plus transport binding specifications.
+
+Currently, the library is is only at prototyping stage. Interfaces will likely
+change, and are missing lots of components of the standard.
+
+Core API
+--------
+
+To initialise the MCTP stack with a single hardware bus:
+
+ * `mctp = mctp_init()`: Initialise the MCTP core
+ * `binding = mctp_<binding>_init()`: Initialise a hardware binding
+ * `mctp_register_bus(mctp, binding, eid)`: Register the hardware binding with
+   the core, using a predefined EID
+
+Then, register a function call to be invoked when a message is received:
+
+ * `mctp_set_rx_all(mctp, function)`: Provide a callback to be invoked when a
+   MCTP message is received
+
+Or transmit a message:
+
+ * `mctp_message_tx(mctp, message, len)`: Transmit a MCTP message
+
+The binding may require you to notify it to receive packets. For example,
+for the serial binding, the `mctp_serial_read()` function should be invoked
+when the file-descriptor for the serial device has data available.
+
+Environment configuration
+-------------------------
+
+This library is intended to be portable to be used in a range of environments,
+but the main targets are:
+
+  - Linux userspace, typically for BMC use-cases
+  - Low-level firmware environments
+
+For the latter, we need to support customisation of the functions that libmctp
+uses (for example, POSIX file IO is not available).
+
+In order to support these, we have a couple of compile-time definitions:
+
+ - `MCTP_FILEIO`: define if POSIX file io is available, allowing the
+   serial hardware binding to access char devices for IO.
+
+ - `MCTP_LOG_`: allows selection of a logging backend. Currently available
+   are:
+
+    - `MCTP_LOG_STDERR`: use `fprintf(stderr, ...)` for log output
+
+    - `MCTP_LOG_SYSLOG`: use `syslog()` for log output
+
+    - `MCTP_LOG_CUSTOM`: provide your own macro for logging, of
+      the format: ```#define mctp_prlog(level, fmt, ...) (....)```
+
+TODO
+----
+
+ - Message packetisation and reassembly
+ - Control messages
+ - Message- and packet-buffer pools and preallocation
+ - C++ API
+ - Non-file-based serial binding
diff --git a/alloc.c b/alloc.c
new file mode 100644
index 0000000..2389dd3
--- /dev/null
+++ b/alloc.c
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include <assert.h>
+
+#include "libmctp-alloc.h"
+
+struct {
+	void	*(*alloc)(size_t);
+	void	(*free)(void *);
+	void	*(*realloc)(void *, size_t);
+} alloc_ops = {
+	malloc,
+	free,
+	realloc,
+};
+
+/* internal-only allocation functions */
+void *__mctp_alloc(size_t size)
+{
+	if (alloc_ops.alloc)
+		return alloc_ops.alloc(size);
+	if (alloc_ops.realloc)
+		return alloc_ops.realloc(NULL, size);
+	assert(0);
+}
+
+void __mctp_free(void *ptr)
+{
+	if (alloc_ops.free)
+		alloc_ops.free(ptr);
+	else if (alloc_ops.realloc)
+		alloc_ops.realloc(ptr, 0);
+	else
+		assert(0);
+}
+
+void *__mctp_realloc(void *ptr, size_t size)
+{
+	if (alloc_ops.realloc)
+		return alloc_ops.realloc(ptr, size);
+	assert(0);
+}
+
+void mctp_set_alloc_ops(void *(*alloc)(size_t),
+		void (*free)(void *),
+		void *(realloc)(void *, size_t))
+{
+	alloc_ops.alloc = alloc;
+	alloc_ops.free = free;
+	alloc_ops.realloc = realloc;
+}
diff --git a/core.c b/core.c
new file mode 100644
index 0000000..575d14f
--- /dev/null
+++ b/core.c
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "core: " fmt
+
+#include "libmctp.h"
+#include "libmctp-alloc.h"
+#include "libmctp-log.h"
+
+/* Internal data structures */
+
+struct mctp_bus {
+	mctp_eid_t		eid;
+	struct mctp_binding	*binding;
+
+	/* todo: routing */
+};
+
+struct mctp {
+	/* todo: multiple busses */
+	struct mctp_bus	busses[1];
+
+	struct mctp_pktbuf	txbuf;
+
+	/* Message RX callback */
+	mctp_rx_fn		message_rx;
+	void			*message_rx_data;
+};
+
+#ifndef BUILD_ASSERT
+#define BUILD_ASSERT(x) \
+	do { (void)sizeof(char[0-(!(x))]); } while (0)
+#endif
+
+struct mctp_pktbuf *mctp_pktbuf_alloc(uint8_t len)
+{
+	struct mctp_pktbuf *buf;
+
+	BUILD_ASSERT(MCTP_PKTBUF_SIZE <= 0xff);
+
+	/* todo: pools */
+	buf = __mctp_alloc(sizeof(*buf));
+
+	buf->start = MCTP_PKTBUF_BINDING_PAD;
+	buf->end = buf->start + len;
+	buf->mctp_hdr_off = buf->start;
+
+	return buf;
+}
+
+void mctp_pktbuf_free(struct mctp_pktbuf *pkt)
+{
+	__mctp_free(pkt);
+}
+
+struct mctp_hdr *mctp_pktbuf_hdr(struct mctp_pktbuf *pkt)
+{
+	return (void *)pkt->data + pkt->mctp_hdr_off;
+}
+
+void *mctp_pktbuf_data(struct mctp_pktbuf *pkt)
+{
+	return (void *)pkt->data + pkt->mctp_hdr_off + sizeof(struct mctp_hdr);
+}
+
+uint8_t mctp_pktbuf_size(struct mctp_pktbuf *pkt)
+{
+	return pkt->end - pkt->start;
+}
+
+void *mctp_pktbuf_alloc_start(struct mctp_pktbuf *pkt, uint8_t size)
+{
+	assert(size <= pkt->start);
+	pkt->start -= size;
+	return pkt->data + pkt->start;
+}
+
+void *mctp_pktbuf_alloc_end(struct mctp_pktbuf *pkt, uint8_t size)
+{
+	void *buf;
+
+	assert(size < (MCTP_PKTBUF_SIZE - pkt->end));
+	buf = pkt->data + pkt->end;
+	pkt->end += size;
+	return buf;
+}
+
+int mctp_pktbuf_push(struct mctp_pktbuf *pkt, void *data, uint8_t len)
+{
+	void *p;
+
+	assert(pkt->end + len <= MCTP_PKTBUF_SIZE);
+
+	if (pkt->end + len > MCTP_PKTBUF_SIZE)
+		return -1;
+
+	p = pkt->data + pkt->end;
+
+	pkt->end += len;
+	memcpy(p, data, len);
+
+	return 0;
+}
+
+struct mctp *mctp_init(void)
+{
+	struct mctp *mctp;
+
+	mctp = __mctp_alloc(sizeof(*mctp));
+	memset(mctp, 0, sizeof(*mctp));
+
+	return mctp;
+}
+
+int mctp_set_rx_all(struct mctp *mctp, mctp_rx_fn fn, void *data)
+{
+	mctp->message_rx = fn;
+	mctp->message_rx_data = data;
+	return 0;
+}
+
+static struct mctp_bus *find_bus_for_eid(struct mctp *mctp,
+		mctp_eid_t dest __attribute__((unused)))
+{
+	return &mctp->busses[0];
+}
+
+unsigned long mctp_register_bus(struct mctp *mctp,
+		struct mctp_binding *binding,
+		mctp_eid_t eid)
+{
+	assert(!mctp->busses[0].binding);
+	mctp->busses[0].binding = binding;
+	mctp->busses[0].eid = eid;
+	return 0;
+}
+
+void mctp_bus_rx(struct mctp *mctp, unsigned long bus_id,
+		struct mctp_pktbuf *pkt)
+{
+	struct mctp_bus *bus = &mctp->busses[bus_id];
+	size_t len;
+	void *p;
+
+	len = pkt->end - pkt->mctp_hdr_off - sizeof(struct mctp_hdr);
+	p = pkt->data + pkt->mctp_hdr_off + sizeof(struct mctp_hdr),
+	mctp->message_rx(bus->eid, mctp->message_rx_data, p, len);
+}
+
+static int mctp_packet_tx(struct mctp *mctp __attribute__((unused)),
+		struct mctp_bus *bus,
+		struct mctp_pktbuf *pkt)
+{
+	return bus->binding->tx(bus->binding, pkt);
+}
+
+int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid,
+		void *msg, size_t msg_len)
+{
+	struct mctp_pktbuf *pkt;
+	struct mctp_hdr *hdr;
+	struct mctp_bus *bus;
+	int rc;
+
+	/* todo: multiple-packet messages, sequence numbers */
+	assert(msg_len <= MCTP_MTU);
+
+	bus = find_bus_for_eid(mctp, eid);
+
+	pkt = mctp_pktbuf_alloc(msg_len + sizeof(*hdr));
+	hdr = mctp_pktbuf_hdr(pkt);
+
+	/* todo: tags */
+	hdr->ver = bus->binding->version & 0xf;
+	hdr->dest = eid;
+	hdr->src = bus->eid;
+	hdr->flags_seq_tag = MCTP_HDR_FLAG_SOM |
+		MCTP_HDR_FLAG_EOM |
+		(0 << MCTP_HDR_SEQ_SHIFT) |
+		MCTP_HDR_FLAG_TO |
+		(0 << MCTP_HDR_TAG_SHIFT);
+
+	/* todo: zero copy? */
+	memcpy(mctp_pktbuf_data(pkt), msg, msg_len);
+
+	rc = mctp_packet_tx(mctp, bus, pkt);
+
+	return rc;
+}
diff --git a/libmctp-alloc.h b/libmctp-alloc.h
new file mode 100644
index 0000000..e7b00f4
--- /dev/null
+++ b/libmctp-alloc.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#ifndef _LIBMCTP_ALLOC_H
+#define _LIBMCTP_ALLOC_H
+
+#include <stdlib.h>
+
+void *__mctp_alloc(size_t size);
+void __mctp_free(void *ptr);
+void *__mctp_realloc(void *ptr, size_t size);
+
+#endif /* _LIBMCTP_ALLOC_H */
diff --git a/libmctp-log.h b/libmctp-log.h
new file mode 100644
index 0000000..e24738a
--- /dev/null
+++ b/libmctp-log.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#ifndef _LIBMCTP_LOG_H
+#define _LIBMCTP_LOG_H
+
+/* libmctp-internal logging */
+#ifndef pr_fmt
+#define pr_fmt
+#endif
+
+#if defined(MCTP_LOG_STDERR)
+
+#include <stdio.h>
+
+#define MCTP_LOG_ERR		0
+#define MCTP_LOG_WARNING	0
+#define MCTP_LOG_NOTICE		0
+#define MCTP_LOG_INFO		0
+#define MCTP_LOG_DEBUG		0
+
+#define mctp_prlog(x, fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
+
+#elif defined(MCTP_LOG_SYSLOG)
+
+#include <syslog.h>
+
+#define MCTP_LOG_ERR		LOG_ERR
+#define MCTP_LOG_WARNING	LOG_WARNING
+#define MCTP_LOG_NOTICE		LOG_NOTICE
+#define MCTP_LOG_INFO		LOG_INFO
+#define MCTP_LOG_DEBUG		LOG_DEBUG
+
+#define mctp_prlog(x, fmt, ...) syslog(x, fmt, ##__VA_ARGS__)
+
+#elif defined(MCTP_LOG_CUSTOM)
+
+#include <config.h>
+
+#if !defined(mctp_prlog)
+#error Custom logging implementation enabled, but no definition for mctp_prlog
+#endif
+
+
+#else
+#error No log implementation found
+#endif
+
+#define mctp_prerr(fmt, ...)  mctp_prlog(MCTP_LOG_ERR, fmt, ##__VA_ARGS__)
+#define mctp_prwarn(fmt, ...) mctp_prlog(MCTP_LOG_WARNING, fmt, ##__VA_ARGS__)
+#define mctp_prinfo(fmt, ...) mctp_prlog(MCTP_LOG_INFO, fmt, ##__VA_ARGS__)
+#define mctp_prdebug(fmt, ...)  mctp_prlog(MCTP_LOG_DEBUG, fmt, ##__VA_ARGS__)
+
+
+#endif /* _LIBMCTP_LOG_H */
diff --git a/libmctp-serial.h b/libmctp-serial.h
new file mode 100644
index 0000000..0d3b972
--- /dev/null
+++ b/libmctp-serial.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#ifndef _LIBMCTP_SERIAL_H
+#define _LIBMCTP_SERIAL_H
+
+#include "libmctp.h"
+
+struct mctp_binding_serial;
+
+struct mctp_binding_serial *mctp_serial_init(void);
+int mctp_serial_get_fd(struct mctp_binding_serial *serial);
+void mctp_serial_register_bus(struct mctp_binding_serial *serial,
+		struct mctp *mctp, mctp_eid_t eid);
+int mctp_serial_read(struct mctp_binding_serial *serial);
+int mctp_serial_open_path(struct mctp_binding_serial *serial,
+		const char *path);
+void mctp_serial_open_fd(struct mctp_binding_serial *serial, int fd);
+
+#endif /* _LIBMCTP_SERIAL_H */
diff --git a/libmctp.h b/libmctp.h
new file mode 100644
index 0000000..5400788
--- /dev/null
+++ b/libmctp.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#ifndef _LIBMCTP_H
+#define _LIBMCTP_H
+
+#include <stdint.h>
+
+typedef uint8_t mctp_eid_t;
+
+/* MCTP packet definitions */
+struct mctp_hdr {
+	uint8_t	ver;
+	uint8_t	dest;
+	uint8_t	src;
+	uint8_t	flags_seq_tag;
+};
+
+/* Definitions for flags_seq_tag field */
+#define MCTP_HDR_FLAG_SOM	(1<<7)
+#define MCTP_HDR_FLAG_EOM	(1<<6)
+#define MCTP_HDR_FLAG_TO	(1<<3)
+#define MCTP_HDR_SEQ_SHIFT	(5)
+#define MCTP_HDR_SEQ_MASK	(0x3)
+#define MCTP_HDR_TAG_SHIFT	(0)
+#define MCTP_HDR_TAG_MASK	(0x7)
+
+/* Maximum size of *payload* data in a MCTP packet
+ * @todo: dynamic sixing based on channel implementation.
+ */
+#define MCTP_MTU	64
+
+/* packet buffers */
+
+/* Allow a little space before the MCTP header in the packet, for bindings that
+ * may add their own header
+ */
+#define MCTP_PKTBUF_BINDING_PAD	2
+
+#define MCTP_PKTBUF_SIZE	(MCTP_PKTBUF_BINDING_PAD + \
+		(sizeof(struct mctp_hdr) + MCTP_MTU)
+
+struct mctp_pktbuf {
+	unsigned char	data[MCTP_PKTBUF_SIZE];
+	uint8_t		start, end;
+	uint8_t		mctp_hdr_off;
+};
+
+struct mctp_pktbuf *mctp_pktbuf_alloc(uint8_t len);
+void mctp_pktbuf_free(struct mctp_pktbuf *pkt);
+struct mctp_hdr *mctp_pktbuf_hdr(struct mctp_pktbuf *pkt);
+void *mctp_pktbuf_data(struct mctp_pktbuf *pkt);
+uint8_t mctp_pktbuf_size(struct mctp_pktbuf *pkt);
+void *mctp_pktbuf_alloc_start(struct mctp_pktbuf *pkt, uint8_t size);
+void *mctp_pktbuf_alloc_end(struct mctp_pktbuf *pkt, uint8_t size);
+int mctp_pktbuf_push(struct mctp_pktbuf *pkt, void *data, uint8_t len);
+
+/* MCTP core */
+struct mctp;
+struct mctp_binding;
+
+struct mctp *mctp_init(void);
+
+unsigned long mctp_register_bus(struct mctp *mctp,
+		struct mctp_binding *binding,
+		mctp_eid_t eid);
+
+typedef void (*mctp_rx_fn)(uint8_t src_eid, void *data,
+		void *msg, size_t len);
+
+int mctp_set_rx_all(struct mctp *mctp, mctp_rx_fn fn, void *data);
+
+int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid,
+		void *msg, size_t msg_len);
+
+/* hardware bindings */
+struct mctp_binding {
+	const char	*name;
+	uint8_t		version;
+	int		(*tx)(struct mctp_binding *binding,
+				struct mctp_pktbuf *pkt);
+};
+
+void mctp_bus_rx(struct mctp *mctp, unsigned long bus_id,
+		struct mctp_pktbuf *pkt);
+
+/* environment-specific allocation */
+void mctp_set_alloc_ops(void *(*alloc)(size_t),
+		void (*free)(void *),
+		void *(realloc)(void *, size_t));
+
+
+#endif /* _LIBMCTP_H */
diff --git a/serial.c b/serial.c
new file mode 100644
index 0000000..7c4e9e3
--- /dev/null
+++ b/serial.c
@@ -0,0 +1,300 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef MCTP_FILEIO
+#include <fcntl.h>
+#endif
+
+#define pr_fmt(x) "serial: " x
+
+#include "libmctp.h"
+#include "libmctp-alloc.h"
+#include "libmctp-log.h"
+#include "libmctp-serial.h"
+
+struct mctp_binding_serial {
+	struct mctp_binding	binding;
+	struct mctp		*mctp;
+	int			fd;
+	unsigned long		bus_id;
+
+	/* receive buffer and state */
+	uint8_t			rxbuf[1024];
+	struct mctp_pktbuf	*rx_pkt;
+	uint8_t			rx_exp_len;
+	uint16_t		rx_fcs;
+	enum {
+		STATE_WAIT_SYNC_START,
+		STATE_WAIT_REVISION,
+		STATE_WAIT_LEN,
+		STATE_DATA,
+		STATE_DATA_ESCAPED,
+		STATE_WAIT_FCS1,
+		STATE_WAIT_FCS2,
+		STATE_WAIT_SYNC_END,
+	} rx_state;
+};
+
+#ifndef container_of
+#define container_of(ptr, type, member) \
+    (type *)((char *)(ptr) - (char *)&((type *)0)->member)
+#endif
+
+#define binding_to_serial(b) \
+	container_of(b, struct mctp_binding_serial, binding)
+
+#define MCTP_SERIAL_REVISION		0x01
+#define MCTP_SERIAL_FRAMING_FLAG	0x7e
+#define MCTP_SERIAL_ESCAPE		0x7d
+
+struct mctp_serial_header {
+	uint8_t	flag;
+	uint8_t revision;
+	uint8_t	len;
+};
+
+struct mctp_serial_trailer {
+	uint8_t	fcs_msb;
+	uint8_t fcs_lsb;
+	uint8_t	flag;
+};
+
+static void mctp_serial_pkt_escape(struct mctp_pktbuf *pkt)
+{
+	uint8_t buf[MCTP_MTU + sizeof(struct mctp_hdr)], *p;
+	uint8_t total_len;
+	int i, j;
+
+	total_len = pkt->end - pkt->mctp_hdr_off;
+
+	p = (void *)mctp_pktbuf_hdr(pkt);
+
+	memcpy(buf, p, total_len);
+
+	for (i = 0, j = 0; i < total_len; i++, j++) {
+		uint8_t c = buf[i];
+		if (c == 0x7e || c == 0x7d) {
+			p[j] = 0x7d;
+			mctp_pktbuf_alloc_end(pkt, 1);
+			j++;
+			c ^= 0x20;
+		}
+		p[j] = c;
+	}
+}
+
+static int mctp_binding_serial_tx(struct mctp_binding *b,
+		struct mctp_pktbuf *pkt)
+{
+	struct mctp_binding_serial *serial = binding_to_serial(b);
+	struct mctp_serial_header *hdr;
+	struct mctp_serial_trailer *tlr;
+	uint8_t len;
+
+	/* the length field in the header excludes serial framing
+	 * and escape sequences */
+	len = mctp_pktbuf_size(pkt);
+
+	hdr = mctp_pktbuf_alloc_start(pkt, 3);
+	hdr->flag = MCTP_SERIAL_FRAMING_FLAG;
+	hdr->revision = MCTP_SERIAL_REVISION;
+	hdr->len = len;
+
+	mctp_serial_pkt_escape(pkt);
+
+	tlr = mctp_pktbuf_alloc_end(pkt, 3);
+	/* todo: trailer FCS */
+	tlr->flag = MCTP_SERIAL_FRAMING_FLAG;
+
+	write(serial->fd, pkt->data + pkt->start, pkt->end - pkt->start);
+
+	return 0;
+}
+
+static void mctp_serial_finish_packet(struct mctp_binding_serial *serial,
+		bool valid)
+{
+	struct mctp_pktbuf *pkt = serial->rx_pkt;
+	assert(pkt);
+
+	if (valid)
+		mctp_bus_rx(serial->mctp, serial->bus_id, pkt);
+
+	mctp_pktbuf_free(pkt);
+	serial->rx_pkt = NULL;
+}
+
+static void mctp_serial_start_packet(struct mctp_binding_serial *serial,
+		uint8_t len)
+{
+	serial->rx_pkt = mctp_pktbuf_alloc(len);
+}
+
+static void mctp_rx_consume_one(struct mctp_binding_serial *serial,
+		uint8_t c)
+{
+	struct mctp_pktbuf *pkt = serial->rx_pkt;
+
+	mctp_prdebug("state: %d, char 0x%02x", serial->rx_state, c);
+
+	assert(!pkt == (serial->rx_state == STATE_WAIT_SYNC_START ||
+			serial->rx_state == STATE_WAIT_REVISION ||
+			serial->rx_state == STATE_WAIT_LEN));
+
+	switch (serial->rx_state) {
+	case STATE_WAIT_SYNC_START:
+		if (c != MCTP_SERIAL_FRAMING_FLAG) {
+			mctp_prdebug("lost sync, dropping packet");
+			if (pkt)
+				mctp_serial_finish_packet(serial, false);
+		} else {
+			serial->rx_state = STATE_WAIT_REVISION;
+		}
+		break;
+
+	case STATE_WAIT_REVISION:
+		if (c == MCTP_SERIAL_REVISION) {
+			serial->rx_state = STATE_WAIT_LEN;
+		} else {
+			mctp_prdebug("invalid revision 0x%02x", c);
+			serial->rx_state = STATE_WAIT_SYNC_START;
+		}
+		break;
+	case STATE_WAIT_LEN:
+		if (c > MCTP_MTU || c < sizeof(struct mctp_hdr)) {
+			mctp_prdebug("invalid size %d", c);
+			serial->rx_state = STATE_WAIT_SYNC_START;
+		} else {
+			uint8_t *p;
+
+			mctp_serial_start_packet(serial, 0);
+			pkt = serial->rx_pkt;
+			p = mctp_pktbuf_alloc_start(pkt, 3);
+			p[0] = MCTP_SERIAL_FRAMING_FLAG;
+			p[1] = MCTP_SERIAL_REVISION;
+			p[2] = c;
+			serial->rx_exp_len = c;
+			serial->rx_state = STATE_DATA;
+		}
+		break;
+
+	case STATE_DATA:
+		if (c == MCTP_SERIAL_ESCAPE) {
+			serial->rx_state = STATE_DATA_ESCAPED;
+		} else {
+			mctp_pktbuf_push(pkt, &c, 1);
+			if (pkt->end - pkt->mctp_hdr_off == serial->rx_exp_len)
+				serial->rx_state = STATE_WAIT_FCS1;
+		}
+		break;
+
+	case STATE_DATA_ESCAPED:
+		c ^= 0x20;
+		mctp_pktbuf_push(pkt, &c, 1);
+		if (pkt->end - pkt->mctp_hdr_off == serial->rx_exp_len)
+			serial->rx_state = STATE_WAIT_FCS1;
+		else
+			serial->rx_state = STATE_DATA;
+		break;
+
+	case STATE_WAIT_FCS1:
+		serial->rx_fcs = c << 8;
+		serial->rx_state = STATE_WAIT_FCS2;
+		break;
+	case STATE_WAIT_FCS2:
+		serial->rx_fcs |= c;
+		/* todo: check fcs */
+		serial->rx_state = STATE_WAIT_SYNC_END;
+		break;
+
+	case STATE_WAIT_SYNC_END:
+		if (c == MCTP_SERIAL_FRAMING_FLAG) {
+			mctp_serial_finish_packet(serial, true);
+		} else {
+			mctp_prdebug("missing end frame marker");
+			mctp_serial_finish_packet(serial, false);
+		}
+		serial->rx_state = STATE_WAIT_SYNC_START;
+		break;
+	}
+
+	mctp_prdebug(" -> state: %d", serial->rx_state);
+}
+static void __attribute__((used)) mctp_rx_consume(struct mctp_binding_serial *serial,
+		void *buf, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		mctp_rx_consume_one(serial, *(uint8_t *)(buf + i));
+}
+
+#ifdef MCTP_FILEIO
+int mctp_serial_read(struct mctp_binding_serial *serial)
+{
+	ssize_t len;
+
+	len = read(serial->fd, serial->rxbuf, sizeof(serial->rxbuf));
+	if (len == 0)
+		return -1;
+
+	if (len < 0) {
+		mctp_prerr("can't read from serial device: %m");
+		return -1;
+	}
+
+	mctp_rx_consume(serial, serial->rxbuf, len);
+
+	return 0;
+}
+
+int mctp_serial_get_fd(struct mctp_binding_serial *serial)
+{
+	return serial->fd;
+}
+
+int mctp_serial_open_path(struct mctp_binding_serial *serial,
+		const char *device)
+{
+	serial->fd = open(device, O_RDWR);
+	if (serial->fd < 0)
+		mctp_prerr("can't open device %s: %m", device);
+
+	return 0;
+}
+
+void mctp_serial_open_fd(struct mctp_binding_serial *serial, int fd)
+{
+	serial->fd = fd;
+}
+#endif
+
+void mctp_serial_register_bus(struct mctp_binding_serial *serial,
+		struct mctp *mctp, mctp_eid_t eid)
+{
+	assert(serial->fd >= 0);
+	serial->mctp = mctp;
+	serial->bus_id = mctp_register_bus(mctp, &serial->binding, eid);
+}
+
+struct mctp_binding_serial *mctp_serial_init(void)
+{
+	struct mctp_binding_serial *serial;
+
+	serial = __mctp_alloc(sizeof(*serial));
+	serial->fd = -1;
+	serial->rx_state = STATE_WAIT_SYNC_START;
+	serial->rx_pkt = NULL;
+	serial->binding.name = "serial";
+	serial->binding.version = 1;
+
+	serial->binding.tx = mctp_binding_serial_tx;
+
+	return serial;
+}
+
diff --git a/tests/mctp-in.c b/tests/mctp-in.c
new file mode 100644
index 0000000..8444d59
--- /dev/null
+++ b/tests/mctp-in.c
@@ -0,0 +1,46 @@
+
+#include <assert.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+
+#include "libmctp.h"
+#include "libmctp-serial.h"
+
+static void rx_message(uint8_t eid, void *data, void *msg, size_t len)
+{
+	(void)eid;
+	(void)data;
+	write(STDOUT_FILENO, msg, len);
+}
+
+int main(void)
+{
+	struct mctp_binding_serial *serial;
+	struct mctp *mctp;
+	int rc;
+
+	mctp = mctp_init();
+	assert(mctp);
+
+	serial = mctp_serial_init();
+	assert(serial);
+
+	mctp_serial_open_fd(serial, STDIN_FILENO);
+
+	mctp_serial_register_bus(serial, mctp, 8);
+
+	mctp_set_rx_all(mctp, rx_message, NULL);
+
+	for (;;) {
+		rc = mctp_serial_read(serial);
+		if (rc)
+			break;
+	}
+
+	return EXIT_SUCCESS;
+
+}
diff --git a/tests/mctp-pipe.c b/tests/mctp-pipe.c
new file mode 100644
index 0000000..9c37578
--- /dev/null
+++ b/tests/mctp-pipe.c
@@ -0,0 +1,80 @@
+
+#include <assert.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+
+#include "libmctp.h"
+#include "libmctp-serial.h"
+
+static void rx_message(uint8_t eid, void *data, void *msg, size_t len)
+{
+	(void)eid;
+	(void)data;
+	write(STDOUT_FILENO, msg, len);
+}
+
+int main(void)
+{
+	struct mctp_binding_serial *serial[2];
+	mctp_eid_t eids[] = {8, 9};
+	struct pollfd pollfds[3];
+	struct mctp *mctp[2];
+	int rc, mctp_fds[2];
+
+	mctp[0] = mctp_init();
+	mctp[1] = mctp_init();
+
+	assert(mctp[0] && mctp[1]);
+
+	serial[0] = mctp_serial_init();
+	serial[1] = mctp_serial_init();
+
+	assert(serial[0] && serial[1]);
+
+	rc = socketpair(AF_UNIX, SOCK_DGRAM, 0, mctp_fds);
+	if (rc)
+		err(EXIT_FAILURE, "Can't create sockets");
+
+	mctp_serial_open_fd(serial[0], mctp_fds[0]);
+	mctp_serial_open_fd(serial[1], mctp_fds[1]);
+
+	mctp_serial_register_bus(serial[0], mctp[0], eids[0]);
+	mctp_serial_register_bus(serial[1], mctp[1], eids[1]);
+
+	mctp_set_rx_all(mctp[1], rx_message, NULL);
+
+	pollfds[0].fd = mctp_fds[0];
+	pollfds[0].events = POLLIN;
+	pollfds[1].fd = mctp_fds[1];
+	pollfds[1].events = POLLIN;
+	pollfds[2].fd = STDIN_FILENO;
+	pollfds[2].events = POLLIN;
+
+	for (;;) {
+		uint8_t buf[1024];
+
+		rc = poll(pollfds, 3, 0);
+		if (rc < 0)
+			return EXIT_FAILURE;
+
+		if (pollfds[0].revents)
+			mctp_serial_read(serial[0]);
+		if (pollfds[1].revents)
+			mctp_serial_read(serial[1]);
+		if (pollfds[2].revents) {
+			rc = read(STDIN_FILENO, buf, sizeof(buf));
+			if (rc == 0)
+				break;
+			else if (rc < 0)
+				err(EXIT_FAILURE, "read");
+			mctp_message_tx(mctp[0], eids[1], buf, rc);
+		}
+	}
+
+	return EXIT_SUCCESS;
+
+}