├── utils ├── mctp-test-client.c ├── meson.build ├── mctp-in.c ├── mctp-capture.h ├── mctp-astlpc-daemon.c ├── mctp-pipe.c ├── mctp-capture.c └── mctp-demux-daemon.c ├── crc32.h ├── libmctp-sizes.h.in ├── systemd └── system │ ├── mctp-demux.socket │ └── mctp-demux.service ├── compiler.h ├── range.h ├── libmctp.pc.in ├── container_of.h ├── Makefile.inc ├── docs ├── bindings │ ├── Makefile │ └── vendor-ibm-astlpc.md └── fuzzing.md ├── control.h ├── tests ├── fuzz │ ├── meson.build │ ├── fuzz-coverage.py │ ├── fuzz-build.py │ └── i2c-fuzz.c ├── meson.build ├── test-utils.h ├── test_eid.c ├── test-utils.c ├── test_bridge.c ├── test_cmds.c ├── test_seq.c ├── test_serial.c ├── test_i2c.c └── test_core.c ├── crc-16-ccitt.h ├── libmctp-alloc.h ├── .gitignore ├── crc32.c ├── bootstrap.sh ├── libmctp-i2c.h ├── i2c-internal.h ├── libmctp-log.h ├── libmctp-serial.h ├── log.c ├── libmctp-astlpc.h ├── Makefile.am ├── meson.options ├── OWNERS ├── CMakeLists.txt ├── alloc.c ├── crc-16-ccitt.c ├── core-internal.h ├── .clang-format ├── libmctp-cmds.h ├── configure.ac ├── meson.build ├── README.md ├── i2c.c ├── control.c ├── libmctp.h └── serial.c /utils/mctp-test-client.c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /crc32.h: -------------------------------------------------------------------------------- 1 | #ifndef _CRC32_H 2 | #define _CRC32_H 3 | 4 | #include 5 | #include 6 | 7 | uint32_t crc32(const void *buf, size_t len); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /libmctp-sizes.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define MCTP_SIZEOF_STRUCT_MCTP @sizeof_struct_mctp@ 4 | /* sizeof(struct mctp_binding_i2c) */ 5 | #define MCTP_SIZEOF_BINDING_I2C @sizeof_binding_i2c@ 6 | -------------------------------------------------------------------------------- /systemd/system/mctp-demux.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MCTP demux Unix domain socket 3 | 4 | [Socket] 5 | ListenSequentialPacket=@mctp-mux 6 | Accept=no 7 | 8 | [Install] 9 | WantedBy=sockets.target 10 | -------------------------------------------------------------------------------- /compiler.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | #ifndef _COMPILER_H 3 | #define _COMPILER_H 4 | 5 | #ifndef __unused 6 | #define __unused __attribute__((unused)) 7 | #endif 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /range.h: -------------------------------------------------------------------------------- 1 | #ifndef _RANGE_H 2 | #define _RANGE_H 3 | 4 | #ifndef MIN 5 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 6 | #endif 7 | 8 | #ifndef MAX 9 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 10 | #endif 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /libmctp.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | includedir=@includedir@ 5 | 6 | Name: libmctp 7 | Description: MCTP protocol implementation 8 | Version: @VERSION@ 9 | Libs: -L${libdir} -lmctp 10 | Cflags: -I${includedir} 11 | -------------------------------------------------------------------------------- /container_of.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONTAINER_OF_H 2 | #define _CONTAINER_OF_H 3 | 4 | #ifndef container_of 5 | #define container_of(ptr, type, member) \ 6 | (type *)((char *)(ptr) - offsetof(type, member)) 7 | #endif 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /Makefile.inc: -------------------------------------------------------------------------------- 1 | LIBMCTP_DIR ?= libmctp/ 2 | LIBMCTP_OBJS = crc32.o core.o alloc.o log.o 3 | LIBMCTP_BINDINGS ?= serial astlpc 4 | 5 | LIBMCTP_OBJS += $(LIBMCTP_BINDINGS:%=%.o) 6 | 7 | LIBMCTP = $(LIBMCTP_DIR)libmctp.a 8 | 9 | $(LIBMCTP): $(LIBMCTP_OBJS:%=$(LIBMCTP_DIR)%) 10 | -------------------------------------------------------------------------------- /docs/bindings/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: html 2 | html: $(patsubst %.md, %.html, $(wildcard *.md)) 3 | 4 | %.html: %.md 5 | markdown2 \ 6 | --extras=code-friendly \ 7 | --extras=fenced-code-blocks \ 8 | --extras=tables \ 9 | $< > $@ 10 | 11 | .PHONY: clean 12 | clean: 13 | $(RM) *.html 14 | -------------------------------------------------------------------------------- /systemd/system/mctp-demux.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MCTP demultiplexer daemon 3 | 4 | [Service] 5 | Restart=always 6 | Environment=DEMUX_BINDING_OPTS=null 7 | EnvironmentFile=-/etc/default/mctp 8 | ExecStart=/usr/bin/mctp-demux-daemon $DEMUX_BINDING_OPTS 9 | SyslogIdentifier=mctp-demux 10 | -------------------------------------------------------------------------------- /control.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "libmctp.h" 7 | 8 | /* Handle a MCTP control message. Returns true for control requests, 9 | * false otherwise */ 10 | bool mctp_control_handler(struct mctp_bus *bus, uint8_t src_eid, bool tag_owner, 11 | uint8_t msg_tag, const void *data, size_t len); 12 | -------------------------------------------------------------------------------- /tests/fuzz/meson.build: -------------------------------------------------------------------------------- 1 | if get_option('bindings').contains('i2c') 2 | executable( 3 | 'i2c-fuzz', 4 | 'i2c-fuzz.c', 5 | # for __AFL_LOOP 6 | cpp_args: ['-Wno-gnu-statement-expression-from-macro-expansion'], 7 | include_directories: test_include_dirs, 8 | dependencies: [libmctp_dep], 9 | ) 10 | endif 11 | -------------------------------------------------------------------------------- /crc-16-ccitt.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifndef _CRC_16_CCITT_H 4 | #define _CRC_16_CCITT_H 5 | 6 | #include 7 | 8 | #define FCS_INIT_16 0xFFFF /* Initial FCS value */ 9 | 10 | uint16_t crc_16_ccitt(uint16_t fcs, const uint8_t *cp, uint32_t len); 11 | 12 | uint16_t crc_16_ccitt_byte(uint16_t fcs, const uint8_t c); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /libmctp-alloc.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifndef _LIBMCTP_ALLOC_H 4 | #define _LIBMCTP_ALLOC_H 5 | 6 | #include 7 | 8 | struct mctp; 9 | 10 | void *__mctp_alloc(size_t size); 11 | void __mctp_free(void *ptr); 12 | 13 | void *__mctp_msg_alloc(size_t size, struct mctp *mctp); 14 | void __mctp_msg_free(void *ptr, struct mctp *mctp); 15 | 16 | #endif /* _LIBMCTP_ALLOC_H */ 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | aclocal.m4 3 | aminclude_static.am 4 | ar-lib 5 | autom4te.cache 6 | compile 7 | config.guess 8 | config.h.in 9 | config.log 10 | config.status 11 | config.sub 12 | configure 13 | cscope.* 14 | depcomp 15 | .deps 16 | .dirstamp 17 | *.gcda 18 | *.gcno 19 | install-sh 20 | *.la 21 | libtool 22 | *.lo 23 | *.log 24 | ltmain.sh 25 | m4 26 | Makefile 27 | Makefile.in 28 | missing 29 | *.o 30 | stamp-h1 31 | *.swp 32 | test-driver 33 | *.trs 34 | .libs 35 | -------------------------------------------------------------------------------- /crc32.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 */ 2 | /* Copyright 2021 IBM Corp. */ 3 | 4 | #include "crc32.h" 5 | 6 | #include 7 | 8 | /* Very dumb CRC-32 implementation */ 9 | uint32_t crc32(const void *buf, size_t len) 10 | { 11 | const uint8_t *buf8 = buf; 12 | uint32_t rem = 0xffffffff; 13 | 14 | for (; len; len--) { 15 | int i; 16 | 17 | rem = rem ^ *buf8; 18 | for (i = 0; i < CHAR_BIT; i++) 19 | rem = (rem >> 1) ^ ((rem & 1) * 0xEDB88320); 20 | 21 | buf8++; 22 | } 23 | 24 | return rem ^ 0xffffffff; 25 | } 26 | -------------------------------------------------------------------------------- /tests/meson.build: -------------------------------------------------------------------------------- 1 | 2 | tests = ['test_eid', 'test_seq', 'test_bridge', 'test_cmds', 'test_core'] 3 | 4 | if get_option('bindings').contains('serial') 5 | tests += 'test_serial' 6 | endif 7 | if get_option('bindings').contains('astlpc') 8 | tests += 'test_astlpc' 9 | endif 10 | if get_option('bindings').contains('i2c') 11 | tests += 'test_i2c' 12 | endif 13 | 14 | test_include_dirs = [include_directories('.'), libmctp_include_dir] 15 | foreach t : tests 16 | test( 17 | t, 18 | executable( 19 | t, 20 | [t + '.c', 'test-utils.c'], 21 | include_directories: test_include_dirs, 22 | dependencies: [libmctp_dep], 23 | ), 24 | ) 25 | endforeach 26 | 27 | subdir('fuzz') 28 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | autoreconf -f -i 6 | 7 | BOOTSTRAP_MODE="" 8 | 9 | if [ $# -gt 0 ]; 10 | then 11 | BOOTSTRAP_MODE="${1}" 12 | shift 1 13 | fi 14 | 15 | case "${BOOTSTRAP_MODE}" in 16 | dev) 17 | FLAGS="-O2 -Wall -Wextra -Wformat-security" 18 | FLAGS="${FLAGS} -Wparentheses" 19 | FLAGS="${FLAGS} -Wno-type-limits" 20 | FLAGS="${FLAGS} -Werror" 21 | # FLAGS="${FLAGS} -Wpedantic" 22 | FLAGS="${FLAGS} -fsanitize=address,leak,undefined" 23 | FLAGS="${FLAGS} -ggdb" 24 | ./configure \ 25 | CFLAGS="${FLAGS}" \ 26 | --enable-code-coverage \ 27 | "$@" 28 | ;; 29 | *) 30 | # shellcheck disable=SC2016 31 | echo 'Run "./configure ${CONFIGURE_FLAGS} && make"' 32 | ;; 33 | esac 34 | -------------------------------------------------------------------------------- /tests/test-utils.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifndef _MCTP_TESTS_TEST_UTILS_H 4 | #define _MCTP_TESTS_TEST_UTILS_H 5 | 6 | #include 7 | #include 8 | 9 | /* test binding implementation */ 10 | 11 | /* standard binding interface */ 12 | struct mctp_binding_test *mctp_binding_test_init(void); 13 | void mctp_binding_test_destroy(struct mctp_binding_test *test); 14 | void mctp_binding_test_register_bus(struct mctp_binding_test *binding, 15 | struct mctp *mctp, mctp_eid_t eid); 16 | 17 | /* internal test binding interface */ 18 | void mctp_binding_test_rx_raw(struct mctp_binding_test *test, void *buf, 19 | size_t len); 20 | 21 | /* gerneral utility functions */ 22 | 23 | /* create a MCTP stack, and add a test binding, using the specified EID */ 24 | void mctp_test_stack_init(struct mctp **mctp, 25 | struct mctp_binding_test **binding, mctp_eid_t eid); 26 | 27 | #endif /* _MCTP_TESTS_TEST_UTILS_H */ 28 | -------------------------------------------------------------------------------- /utils/meson.build: -------------------------------------------------------------------------------- 1 | demux_sources = ['mctp-demux-daemon.c'] 2 | demux_args = [] 3 | demux_dep = [libmctp_dep, pcap_dep, libsystemd_dep] 4 | # While mctp-demux-daemon will build without pcap, it won't 5 | # be functional. 6 | # TODO only build mctp-demux-daemon when pcap is available. 7 | if pcap_dep.found() 8 | demux_args += '-DHAVE_PCAP' 9 | demux_sources += 'mctp-capture.c' 10 | endif 11 | 12 | demux = executable( 13 | 'mctp-demux-daemon', 14 | demux_sources, 15 | include_directories: libmctp_include_dir, 16 | dependencies: demux_dep, 17 | c_args: demux_args, 18 | install: true, 19 | ) 20 | 21 | pipe = executable( 22 | 'mctp-pipe', 23 | 'mctp-pipe.c', 24 | include_directories: libmctp_include_dir, 25 | dependencies: [libmctp_dep], 26 | install: false, 27 | ) 28 | 29 | mctp_in = executable( 30 | 'mctp-in', 31 | 'mctp-in.c', 32 | include_directories: libmctp_include_dir, 33 | dependencies: [libmctp_dep], 34 | install: false, 35 | ) 36 | -------------------------------------------------------------------------------- /libmctp-i2c.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "libmctp.h" 4 | 5 | struct mctp_binding_i2c; 6 | 7 | typedef int (*mctp_i2c_tx_fn)(const void *buf, size_t len, void *ctx); 8 | 9 | /* Configures the i2c binding. */ 10 | int mctp_i2c_setup(struct mctp_binding_i2c *i2c, uint8_t own_addr, 11 | mctp_i2c_tx_fn tx_fn, void *tx_ctx); 12 | void mctp_i2c_cleanup(struct mctp_binding_i2c *i2c); 13 | 14 | int mctp_i2c_set_address(struct mctp_binding_i2c *i2c, uint8_t own_addr); 15 | 16 | struct mctp_binding *mctp_binding_i2c_core(struct mctp_binding_i2c *i2c); 17 | 18 | int mctp_i2c_set_neighbour(struct mctp_binding_i2c *i2c, uint8_t eid, 19 | uint8_t addr); 20 | 21 | void mctp_i2c_rx(struct mctp_binding_i2c *i2c, const void *data, size_t len); 22 | int mctp_i2c_parse_hdr(const void *data, size_t len, uint8_t *src_addr, 23 | uint8_t *dest_addr, uint8_t *bytecount); 24 | void mctp_i2c_tx_poll(struct mctp_binding_i2c *i2c); 25 | 26 | /* Can be customised if needed */ 27 | #ifndef I2C_BTU 28 | #define I2C_BTU MCTP_BTU 29 | #endif 30 | 31 | #define MCTP_I2C_PACKET_SIZE (MCTP_PACKET_SIZE(I2C_BTU) + 4) 32 | -------------------------------------------------------------------------------- /i2c-internal.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | #pragma once 3 | 4 | #include 5 | #include "libmctp.h" 6 | #include "libmctp-i2c.h" 7 | 8 | /* Limited by bytecount field */ 9 | static_assert(I2C_BTU <= 254, "I2C BTU is limited to 254"); 10 | 11 | #ifndef MCTP_I2C_NEIGH_COUNT 12 | #define MCTP_I2C_NEIGH_COUNT 4 13 | #endif 14 | 15 | struct mctp_i2c_hdr { 16 | uint8_t dest; 17 | uint8_t cmd; 18 | uint8_t bytecount; 19 | uint8_t source; 20 | }; 21 | 22 | struct mctp_i2c_neigh { 23 | bool used; 24 | /* 7-bit address */ 25 | uint8_t addr; 26 | uint8_t eid; 27 | /* from platform_now(), for LRU eviction */ 28 | uint64_t last_seen_timestamp; 29 | }; 30 | 31 | struct mctp_binding_i2c { 32 | struct mctp_binding binding; 33 | 34 | struct mctp_i2c_neigh neigh[MCTP_I2C_NEIGH_COUNT]; 35 | 36 | uint8_t own_addr; 37 | 38 | uint8_t tx_storage[sizeof(struct mctp_i2c_hdr) + 39 | MCTP_PKTBUF_SIZE(I2C_BTU)] PKTBUF_STORAGE_ALIGN; 40 | uint8_t rx_storage[sizeof(struct mctp_i2c_hdr) + 41 | MCTP_PKTBUF_SIZE(I2C_BTU)] PKTBUF_STORAGE_ALIGN; 42 | 43 | mctp_i2c_tx_fn tx_fn; 44 | void *tx_ctx; 45 | }; 46 | -------------------------------------------------------------------------------- /libmctp-log.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifndef _LIBMCTP_LOG_H 4 | #define _LIBMCTP_LOG_H 5 | 6 | /* libmctp-internal logging */ 7 | 8 | #ifdef HAVE_CONFIG_H 9 | #include "config.h" 10 | #endif 11 | 12 | #ifdef MCTP_NOLOG 13 | 14 | __attribute__((format(printf, 2, 3))) static inline void 15 | mctp_prlog(int level __unused, const char *fmt __unused, ...) 16 | { 17 | } 18 | 19 | #else 20 | 21 | void mctp_prlog(int level, const char *fmt, ...) 22 | __attribute__((format(printf, 2, 3))); 23 | 24 | #endif 25 | 26 | #ifndef pr_fmt 27 | #define pr_fmt(x) x 28 | #endif 29 | 30 | #define mctp_prerr(fmt, ...) \ 31 | mctp_prlog(MCTP_LOG_ERR, pr_fmt(fmt), ##__VA_ARGS__) 32 | #define mctp_prwarn(fmt, ...) \ 33 | mctp_prlog(MCTP_LOG_WARNING, pr_fmt(fmt), ##__VA_ARGS__) 34 | #define mctp_prinfo(fmt, ...) \ 35 | mctp_prlog(MCTP_LOG_INFO, pr_fmt(fmt), ##__VA_ARGS__) 36 | #define mctp_prdebug(fmt, ...) \ 37 | mctp_prlog(MCTP_LOG_DEBUG, pr_fmt(fmt), ##__VA_ARGS__) 38 | 39 | #endif /* _LIBMCTP_LOG_H */ 40 | -------------------------------------------------------------------------------- /utils/mctp-in.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include "compiler.h" 4 | #include "libmctp.h" 5 | #include "libmctp-serial.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static void rx_message(uint8_t eid __unused, bool tag_owner __unused, 16 | uint8_t msg_tag __unused, void *data __unused, void *msg, 17 | size_t len) 18 | { 19 | ssize_t rc; 20 | 21 | rc = write(STDOUT_FILENO, msg, len); 22 | if (rc < 0) 23 | warn("Write failed"); 24 | else if ((size_t)rc < len) 25 | warnx("Short write of length %zd, requested %zd", rc, len); 26 | } 27 | 28 | int main(void) 29 | { 30 | struct mctp_binding_serial *serial; 31 | struct mctp *mctp; 32 | int rc; 33 | 34 | mctp = mctp_init(); 35 | assert(mctp); 36 | 37 | serial = mctp_serial_init(); 38 | assert(serial); 39 | 40 | mctp_serial_open_fd(serial, STDIN_FILENO); 41 | 42 | mctp_register_bus(mctp, mctp_binding_serial_core(serial), 8); 43 | 44 | mctp_set_rx_all(mctp, rx_message, NULL); 45 | 46 | for (;;) { 47 | rc = mctp_serial_read(serial); 48 | if (rc) 49 | break; 50 | } 51 | 52 | return EXIT_SUCCESS; 53 | } 54 | -------------------------------------------------------------------------------- /libmctp-serial.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifndef _LIBMCTP_SERIAL_H 4 | #define _LIBMCTP_SERIAL_H 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #include 11 | 12 | struct mctp_binding_serial; 13 | 14 | struct mctp_binding_serial *mctp_serial_init(void); 15 | void mctp_serial_destroy(struct mctp_binding_serial *serial); 16 | 17 | struct mctp_binding *mctp_binding_serial_core(struct mctp_binding_serial *b); 18 | 19 | /* file-based IO */ 20 | struct pollfd; 21 | int mctp_serial_init_pollfd(struct mctp_binding_serial *serial, 22 | struct pollfd *pollfd); 23 | 24 | int mctp_serial_read(struct mctp_binding_serial *serial); 25 | int mctp_serial_open_path(struct mctp_binding_serial *serial, const char *path); 26 | void mctp_serial_open_fd(struct mctp_binding_serial *serial, int fd); 27 | 28 | /* direct function call IO */ 29 | typedef int 30 | __attribute__((warn_unused_result)) (*mctp_serial_tx_fn)(void *data, 31 | void *buf, 32 | size_t len); 33 | void mctp_serial_set_tx_fn(struct mctp_binding_serial *serial, 34 | mctp_serial_tx_fn fn, void *data); 35 | int mctp_serial_rx(struct mctp_binding_serial *serial, const void *buf, 36 | size_t len); 37 | 38 | #ifdef __cplusplus 39 | } 40 | #endif 41 | 42 | #endif /* _LIBMCTP_SERIAL_H */ 43 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include 4 | 5 | #include "libmctp.h" 6 | #include "libmctp-log.h" 7 | 8 | #ifdef HAVE_CONFIG_H 9 | #include "config.h" 10 | #endif 11 | 12 | #ifdef MCTP_HAVE_STDIO 13 | #include 14 | #endif 15 | 16 | #ifdef MCTP_HAVE_SYSLOG 17 | #include 18 | #endif 19 | 20 | enum { 21 | MCTP_LOG_NONE, 22 | MCTP_LOG_STDIO, 23 | MCTP_LOG_SYSLOG, 24 | MCTP_LOG_CUSTOM, 25 | } log_type = MCTP_LOG_NONE; 26 | 27 | static int log_stdio_level; 28 | static void (*log_custom_fn)(int, const char *, va_list); 29 | 30 | void mctp_prlog(int level, const char *fmt, ...) 31 | { 32 | va_list ap; 33 | 34 | va_start(ap, fmt); 35 | 36 | switch (log_type) { 37 | case MCTP_LOG_NONE: 38 | break; 39 | case MCTP_LOG_STDIO: 40 | #ifdef MCTP_HAVE_STDIO 41 | if (level <= log_stdio_level) { 42 | vfprintf(stderr, fmt, ap); 43 | fputs("\n", stderr); 44 | } 45 | #endif 46 | break; 47 | case MCTP_LOG_SYSLOG: 48 | #ifdef MCTP_HAVE_SYSLOG 49 | vsyslog(level, fmt, ap); 50 | #endif 51 | break; 52 | case MCTP_LOG_CUSTOM: 53 | log_custom_fn(level, fmt, ap); 54 | break; 55 | } 56 | 57 | va_end(ap); 58 | } 59 | 60 | void mctp_set_log_stdio(int level) 61 | { 62 | log_type = MCTP_LOG_STDIO; 63 | log_stdio_level = level; 64 | } 65 | 66 | void mctp_set_log_syslog(void) 67 | { 68 | log_type = MCTP_LOG_SYSLOG; 69 | } 70 | 71 | void mctp_set_log_custom(void (*fn)(int, const char *, va_list)) 72 | { 73 | log_type = MCTP_LOG_CUSTOM; 74 | log_custom_fn = fn; 75 | } 76 | -------------------------------------------------------------------------------- /utils/mctp-capture.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifndef _UTILS_MCTP_CAPTURE_H 4 | #define _UTILS_MCTP_CAPTURE_H 5 | 6 | #ifdef HAVE_CONFIG_H 7 | #include "config.h" 8 | #endif 9 | 10 | #include "compiler.h" 11 | #include "libmctp.h" 12 | 13 | #include 14 | 15 | #if HAVE_PCAP 16 | #include 17 | 18 | #else 19 | typedef void pcap_t; 20 | typedef void pcap_dumper_t; 21 | #endif 22 | 23 | #define CAPTURE_LINKTYPE_LINUX_SLL2 276 24 | 25 | struct capture { 26 | const char *path; 27 | pcap_t *pcap; 28 | pcap_dumper_t *dumper; 29 | }; 30 | 31 | #if HAVE_PCAP 32 | int capture_init(void); 33 | int capture_prepare(struct capture *cap); 34 | void capture_close(struct capture *cap); 35 | void capture_binding(struct mctp_pktbuf *pkt, bool outgoing, void *user); 36 | void capture_socket(pcap_dumper_t *dumper, const void *buf, size_t len, 37 | bool outgoing, int eid); 38 | #else 39 | #include 40 | static inline int capture_init(void) 41 | { 42 | fprintf(stderr, 43 | "libpcap support is disabled, cannot initialise libpcap\n"); 44 | return 0; 45 | } 46 | 47 | static inline int capture_prepare(struct capture *cap) 48 | { 49 | fprintf(stderr, "libpcap support is disabled, cannot capture to %s\n", 50 | cap->path); 51 | return 0; 52 | } 53 | 54 | static inline void capture_close(struct capture *cap __unused) 55 | { 56 | } 57 | 58 | static inline void capture_binding(struct mctp_pktbuf *pkt __unused, 59 | bool outgoing __unused, void *user __unused) 60 | { 61 | } 62 | 63 | static inline void capture_socket(pcap_dumper_t *dumper __unused, 64 | const void *buf __unused, size_t len __unused, 65 | bool outgoing __unused, int eid __unused) 66 | { 67 | } 68 | #endif 69 | #endif 70 | -------------------------------------------------------------------------------- /libmctp-astlpc.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifndef _LIBMCTP_ASTLPCL_H 4 | #define _LIBMCTP_ASTLPCL_H 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #include 11 | 12 | #include 13 | 14 | struct mctp_binding_astlpc; 15 | 16 | /* todo: Remove enum from public interfaces */ 17 | enum mctp_binding_astlpc_kcs_reg { 18 | MCTP_ASTLPC_KCS_REG_DATA = 0, 19 | MCTP_ASTLPC_KCS_REG_STATUS = 1, 20 | }; 21 | 22 | struct mctp_binding_astlpc_ops { 23 | int (*kcs_read)(void *data, enum mctp_binding_astlpc_kcs_reg reg, 24 | uint8_t *val); 25 | int (*kcs_write)(void *data, enum mctp_binding_astlpc_kcs_reg reg, 26 | uint8_t val); 27 | int (*lpc_read)(void *data, void *buf, long offset, size_t len); 28 | int (*lpc_write)(void *data, const void *buf, long offset, size_t len); 29 | }; 30 | 31 | #define MCTP_BINDING_ASTLPC_MODE_BMC 0 32 | #define MCTP_BINDING_ASTLPC_MODE_HOST 1 33 | struct mctp_binding_astlpc * 34 | mctp_astlpc_init(uint8_t mode, uint32_t mtu, void *lpc_map, 35 | const struct mctp_binding_astlpc_ops *ops, void *ops_data); 36 | 37 | struct mctp_binding_astlpc * 38 | mctp_astlpc_init_ops(const struct mctp_binding_astlpc_ops *ops, void *ops_data, 39 | void *lpc_map); 40 | void mctp_astlpc_destroy(struct mctp_binding_astlpc *astlpc); 41 | 42 | struct mctp_binding *mctp_binding_astlpc_core(struct mctp_binding_astlpc *b); 43 | 44 | bool mctp_astlpc_tx_done(struct mctp_binding_astlpc *astlpc); 45 | int mctp_astlpc_poll(struct mctp_binding_astlpc *astlpc); 46 | 47 | /* fileio-based interface */ 48 | struct mctp_binding_astlpc *mctp_astlpc_init_fileio(const char *kcs_path); 49 | 50 | struct pollfd; 51 | int mctp_astlpc_init_pollfd(struct mctp_binding_astlpc *astlpc, 52 | struct pollfd *pollfd); 53 | 54 | #ifdef __cplusplus 55 | } 56 | #endif 57 | 58 | #endif /* _LIBMCTP_ASTLPC_H */ 59 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | 3 | lib_LTLIBRARIES = libmctp.la 4 | libmctp_la_SOURCES = core.c alloc.c log.c \ 5 | libmctp-alloc.h libmctp-log.h \ 6 | libmctp-cmds.h control.c 7 | include_HEADERS = libmctp.h 8 | 9 | if LIBMCTP_BINDING_serial 10 | libmctp_la_SOURCES += serial.c crc-16-ccitt.c 11 | include_HEADERS += libmctp-serial.h 12 | endif 13 | 14 | if LIBMCTP_BINDING_astlpc 15 | libmctp_la_SOURCES += astlpc.c crc32.c 16 | include_HEADERS += libmctp-astlpc.h 17 | endif 18 | 19 | if HAVE_SYSTEMD 20 | systemdsystemunit_DATA = \ 21 | systemd/system/mctp-demux.service \ 22 | systemd/system/mctp-demux.socket 23 | endif 24 | 25 | bin_PROGRAMS = utils/mctp-demux-daemon 26 | noinst_PROGRAMS = utils/mctp-in utils/mctp-pipe 27 | utils_mctp_demux_daemon_SOURCES = utils/mctp-demux-daemon.c 28 | if HAVE_PCAP 29 | utils_mctp_demux_daemon_SOURCES += utils/mctp-capture.c 30 | endif 31 | utils_mctp_demux_daemon_LDADD = libmctp.la $(pcap_LIBS) 32 | utils_mctp_demux_daemon_CFLAGS = $(pcap_CFLAGS) 33 | 34 | pkgconfig_DATA = libmctp.pc 35 | 36 | if AUTOCONF_CODE_COVERAGE_2019_01_06 37 | include $(top_srcdir)/aminclude_static.am 38 | clean-local: code-coverage-clean 39 | distclean-local: code-coverage-dist-clean 40 | else 41 | @CODE_COVERAGE_RULES@ 42 | endif 43 | 44 | AM_CPPFLAGS = $(CODE_COVERAGE_CPPFLAGS) 45 | AM_CFLAGS = $(CODE_COVERAGE_CFLAGS) 46 | AM_LDFLAGS = $(CODE_COVERAGE_LDFLAGS) 47 | 48 | TESTS = $(check_PROGRAMS) 49 | 50 | check_PROGRAMS = tests/test_eid tests/test_seq tests/test_bridge \ 51 | tests/test_astlpc tests/test_serial tests/test_cmds \ 52 | tests/test_core 53 | # We set a global LDADD here, as there's no way to specify it for all 54 | # tests. This means other targets' LDADDs need to be overridden. 55 | LDADD = tests/libtest-utils.a libmctp.la 56 | 57 | noinst_LIBRARIES = tests/libtest-utils.a 58 | 59 | tests_libtest_utils_a_SOURCES = tests/test-utils.c tests/test-utils.h 60 | 61 | @VALGRIND_CHECK_RULES@ 62 | -------------------------------------------------------------------------------- /meson.options: -------------------------------------------------------------------------------- 1 | option( 2 | 'bindings', 3 | type: 'array', 4 | description: 'Bindings to include', 5 | choices: ['serial', 'astlpc', 'i2c'], 6 | value: ['serial', 'astlpc', 'i2c'], 7 | ) 8 | option( 9 | 'control', 10 | type: 'boolean', 11 | value: true, 12 | description: 'Include MCTP control protocol handler', 13 | ) 14 | option( 15 | 'custom_alloc', 16 | type: 'boolean', 17 | value: false, 18 | description: 'Use fixed application-provided allocators', 19 | ) 20 | option( 21 | 'default_alloc', 22 | type: 'feature', 23 | description: 'Use libc malloc and free for heap memory', 24 | ) 25 | option( 26 | 'default_clock_gettime', 27 | type: 'boolean', 28 | value: true, 29 | description: 'Use clock_gettime() for time', 30 | ) 31 | option( 32 | 'fileio', 33 | type: 'feature', 34 | description: 'Support interfaces based on file-descriptors', 35 | ) 36 | option( 37 | 'nolog', 38 | type: 'boolean', 39 | value: false, 40 | description: 'Don\'t include any logging functionality', 41 | ) 42 | option('stdio', type: 'feature', description: 'Support logging to stdio') 43 | option('syslog', type: 'feature', description: 'Support logging to syslog') 44 | option('tests', type: 'feature', value: 'enabled', description: 'Build tests') 45 | 46 | 47 | option( 48 | 'max_message_size', 49 | type: 'integer', 50 | value: 65536, 51 | description: 'Maximum message size', 52 | ) 53 | option( 54 | 'reassembly_contexts', 55 | type: 'integer', 56 | value: 16, 57 | description: 'Number of concurrent reassembly contexts', 58 | ) 59 | option( 60 | 'request_tags', 61 | type: 'integer', 62 | value: 16, 63 | description: 'Number of outbound request tags', 64 | ) 65 | 66 | 67 | option('i2c_mtu', type: 'integer', value: 64, description: 'I2C packet MTU') 68 | option( 69 | 'i2c_neigh_count', 70 | type: 'integer', 71 | value: 4, 72 | description: 'I2C neighbour table size', 73 | ) 74 | -------------------------------------------------------------------------------- /tests/test_eid.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifdef NDEBUG 4 | #undef NDEBUG 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "compiler.h" 13 | #include "libmctp.h" 14 | #include "test-utils.h" 15 | 16 | struct test_ctx { 17 | struct mctp *mctp; 18 | struct mctp_binding_test *binding; 19 | int rx_count; 20 | mctp_eid_t src_eid; 21 | }; 22 | 23 | static void test_rx(uint8_t eid, bool tag_owner __unused, 24 | uint8_t msg_tag __unused, void *data, void *msg __unused, 25 | size_t len __unused) 26 | { 27 | struct test_ctx *ctx = data; 28 | 29 | (void)msg; 30 | (void)len; 31 | 32 | ctx->rx_count++; 33 | ctx->src_eid = eid; 34 | } 35 | 36 | static void create_packet(struct mctp_hdr *pkt, mctp_eid_t src, mctp_eid_t dest) 37 | { 38 | memset(pkt, 0, sizeof(*pkt)); 39 | pkt->src = src; 40 | pkt->dest = dest; 41 | pkt->flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM; 42 | } 43 | 44 | int main(void) 45 | { 46 | struct test_ctx _ctx, *ctx = &_ctx; 47 | const mctp_eid_t local_eid = 8; 48 | const mctp_eid_t remote_eid = 9; 49 | const mctp_eid_t other_eid = 10; 50 | struct { 51 | struct mctp_hdr hdr; 52 | uint8_t payload[1]; 53 | } pktbuf; 54 | 55 | mctp_test_stack_init(&ctx->mctp, &ctx->binding, local_eid); 56 | 57 | mctp_set_rx_all(ctx->mctp, test_rx, ctx); 58 | 59 | /* check a message addressed to us is received */ 60 | ctx->rx_count = 0; 61 | 62 | create_packet(&pktbuf.hdr, remote_eid, local_eid); 63 | 64 | mctp_binding_test_rx_raw(ctx->binding, &pktbuf, sizeof(pktbuf)); 65 | 66 | assert(ctx->rx_count == 1); 67 | assert(ctx->src_eid == remote_eid); 68 | 69 | /* check a message not addressed to us is not received */ 70 | ctx->rx_count = 0; 71 | 72 | create_packet(&pktbuf.hdr, remote_eid, other_eid); 73 | 74 | mctp_binding_test_rx_raw(ctx->binding, &pktbuf, sizeof(pktbuf)); 75 | 76 | assert(ctx->rx_count == 0); 77 | 78 | mctp_binding_test_destroy(ctx->binding); 79 | mctp_destroy(ctx->mctp); 80 | 81 | return EXIT_SUCCESS; 82 | } 83 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # OWNERS 2 | # ------ 3 | # 4 | # The OWNERS file maintains the list of individuals responsible for various 5 | # parts of this repository, including code review and approval. We use the 6 | # Gerrit 'owners' plugin, which consumes this file, along with some extra 7 | # keywords for our own purposes and tooling. 8 | # 9 | # For details on the configuration used by 'owners' see: 10 | # https://gerrit.googlesource.com/plugins/owners/+/refs/heads/master/owners/src/main/resources/Documentation/config.md 11 | # 12 | # An OWNERS file must be in the root of a repository but may also be present 13 | # in any subdirectory. The contents of the subdirectory OWNERS file are 14 | # combined with parent directories unless 'inherit: false' is set. 15 | # 16 | # The owners file is YAML and has [up to] 4 top-level keywords. 17 | # * owners: A list of individuals who have approval authority on the 18 | # repository. 19 | # 20 | # * reviewers: A list of individuals who have requested review notification 21 | # on the repository. 22 | # 23 | # * matchers: A list of specific file/path matchers for granular 'owners' and 24 | # 'reviewers'. See 'owners' plugin documentation. 25 | # 26 | # * openbmc: A list of openbmc-specific meta-data about owners and reviewers. 27 | # - name: preferred name of the individual. 28 | # - email: preferred email address of the individual. 29 | # - discord: Discord nickname of the individual. 30 | # 31 | # It is expected that these 4 sections will be listed in the order above and 32 | # data within them will be kept sorted. 33 | 34 | owners: 35 | - andrew@codeconstruct.com.au 36 | - jk@ozlabs.org 37 | 38 | reviewers: 39 | - iwona.winiarska@intel.com 40 | - sumanth.bhat@linux.intel.com 41 | 42 | matchers: 43 | 44 | openbmc: 45 | - name: Andrew Jeffery 46 | email: andrew@codeconstruct.com.au 47 | discord: arj 48 | - name: Iwona Winiarska 49 | email: iwona.winiarska@intel.com 50 | discord: iklm 51 | - name: Jeremy Kerr 52 | email: jk@ozlabs.org 53 | discord: jk- 54 | - name: Sumanth Bhat 55 | email: sumanth.bhat@linux.intel.com 56 | discord: sumanthb 57 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.5 FATAL_ERROR) 2 | 3 | option(DEV "Option for developer testing" OFF) 4 | 5 | if(DEV) 6 | set(CMAKE_C_FLAGS 7 | "${CMAKE_C_FLAGS} \ 8 | -Werror \ 9 | -Wall \ 10 | -Wextra \ 11 | -Wnull-dereference \ 12 | -Wformat-security \ 13 | -Wno-type-limits \ 14 | -fsanitize=address,leak,undefined \ 15 | -ggdb \ 16 | ") 17 | endif() 18 | 19 | 20 | add_definitions (-DMCTP_LOG_STDERR) 21 | add_definitions (-DMCTP_HAVE_FILEIO) 22 | add_definitions (-DMCTP_HAVE_STDIO) 23 | add_definitions (-DMCTP_DEFAULT_ALLOC) 24 | 25 | add_library (mctp STATIC alloc.c astlpc.c crc32.c core.c log.c libmctp.h serial.c crc-16-ccitt.c control.c) 26 | 27 | target_include_directories (mctp PUBLIC 28 | $ 29 | $ 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "test-utils.h" 14 | 15 | /* mctp_binding_test can be used for loopback in tests. Senders must use 16 | * the local EID as the destination */ 17 | struct mctp_binding_test { 18 | struct mctp_binding binding; 19 | uint8_t tx_storage[MCTP_PKTBUF_SIZE(MCTP_BTU)] PKTBUF_STORAGE_ALIGN; 20 | }; 21 | 22 | static int mctp_binding_test_tx(struct mctp_binding *b, struct mctp_pktbuf *pkt) 23 | { 24 | mctp_bus_rx(b, pkt); 25 | return 0; 26 | } 27 | 28 | struct mctp_binding_test *mctp_binding_test_init(void) 29 | { 30 | struct mctp_binding_test *test; 31 | test = __mctp_alloc(sizeof(*test)); 32 | memset(test, '\0', sizeof(*test)); 33 | test->binding.name = "test"; 34 | test->binding.version = 1; 35 | test->binding.tx = mctp_binding_test_tx; 36 | test->binding.pkt_size = MCTP_PACKET_SIZE(MCTP_BTU); 37 | test->binding.pkt_header = 0; 38 | test->binding.pkt_trailer = 0; 39 | test->binding.tx_storage = test->tx_storage; 40 | return test; 41 | } 42 | 43 | void mctp_binding_test_destroy(struct mctp_binding_test *test) 44 | { 45 | __mctp_free(test); 46 | } 47 | 48 | void mctp_binding_test_rx_raw(struct mctp_binding_test *test, void *buf, 49 | size_t len) 50 | { 51 | struct mctp_pktbuf *pkt; 52 | 53 | pkt = mctp_pktbuf_alloc(&test->binding, len); 54 | assert(pkt); 55 | memcpy(mctp_pktbuf_hdr(pkt), buf, len); 56 | mctp_bus_rx(&test->binding, pkt); 57 | mctp_pktbuf_free(pkt); 58 | } 59 | 60 | void mctp_binding_test_register_bus(struct mctp_binding_test *binding, 61 | struct mctp *mctp, mctp_eid_t eid) 62 | { 63 | mctp_register_bus(mctp, &binding->binding, eid); 64 | } 65 | 66 | void mctp_test_stack_init(struct mctp **mctp, 67 | struct mctp_binding_test **binding, mctp_eid_t eid) 68 | { 69 | *mctp = mctp_init(); 70 | assert(*mctp); 71 | 72 | *binding = mctp_binding_test_init(); 73 | assert(*binding); 74 | 75 | mctp_binding_test_register_bus(*binding, *mctp, eid); 76 | mctp_binding_set_tx_enabled(&(*binding)->binding, true); 77 | } 78 | -------------------------------------------------------------------------------- /docs/fuzzing.md: -------------------------------------------------------------------------------- 1 | # Fuzzing libmctp 2 | 3 | ## Build 4 | 5 | From the top level libmctp directory, run `./tests/fuzz/fuzz-build.py`. That 6 | will produce several build variants required for different fuzz engines/stages. 7 | 8 | ## Honggfuzz 9 | 10 | [Honggfuzz](https://github.com/google/honggfuzz) handles running across multiple 11 | threads itself with a single corpus directory, which is easy to work with. It 12 | needs to be built from source. 13 | 14 | Run with 15 | 16 | ```shell 17 | nice honggfuzz -T -i corpusdir --linux_perf_branch -- ./bhf/tests/fuzz/i2c-fuzz 18 | ``` 19 | 20 | The `--linux_perf_branch` switch is optional, it requires permissions for perf 21 | counters: 22 | 23 | ```shell 24 | echo 0 | sudo tee /proc/sys/kernel/perf_event_paranoid 25 | ``` 26 | 27 | Optionally a thread count can be given, 24 threads on a 12 core system seems to 28 | give best utilisation (`--threads 24`). 29 | 30 | The corpus directory can be reused between runs with different fuzzers. 31 | 32 | ## AFL++ 33 | 34 | Running a single instance (just for testing): 35 | 36 | ```shell 37 | afl-fuzz -i fuzzrun/hf11/ -o fuzzrun/out12single ./bfuzz/tests/fuzz/i2c-fuzz 38 | ``` 39 | 40 | AFL++ requires a separate TUI instantiation for each CPU thread. The helper 41 | [AFL Runner](https://github.com/0xricksanchez/afl_runner) makes that easier. 42 | 43 | Running with 20 threads: 44 | 45 | ```shell 46 | nice aflr run -t bfuzz/tests/fuzz/i2c-fuzz -i workdir/out5/m_i2c-fuzz/queue -o workdir/out6 -c bcmplog/tests/fuzz/i2c-fuzz -s bfuzzasan/tests/fuzz/i2c-fuzz -n 20 --session-name fuzz 47 | ``` 48 | 49 | Kill it with `aflr kill fuzz`. 50 | 51 | `aflr tui workdir/out6` could be used to view progress, though its calculations 52 | may be inaccurate if some runners are idle. Another option is 53 | `afl-whatsup workdir/out6`. 54 | 55 | ## Coverage 56 | 57 | The coverage provided by a corpus directory can be reported using 58 | `tests/fuzz/fuzz-coverage.py`. 59 | 60 | It will: 61 | 62 | - Run a binary compiled with `--coverage` against each corpus file 63 | - Use [grcov](https://github.com/mozilla/grcov) to aggregate the coverage traces 64 | (much faster than lcov). 65 | - Use `genhtml` to create a report 66 | 67 | Typical usage, with corpus in `fuzzrun/corpus`: 68 | 69 | ```shell 70 | ./tests/fuzz/fuzz-coverage.py fuzzrun/corpus bnoopt/tests/fuzz/i2c-fuzz . bnoopt/ coverage-output 71 | ``` 72 | 73 | ## Reproducing crashes 74 | 75 | When the fuzz run encounters a crash, the testcase can be run against the built 76 | target manually, and stepped through with GDB etc. 77 | 78 | ```shell 79 | ./bnoopt/tests/fuzz/i2c-fuzz < crashing.bin 80 | ``` 81 | -------------------------------------------------------------------------------- /tests/fuzz/fuzz-coverage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 3 | 4 | # usage: fuzz-coverage.py [-h] corpus program srcdir builddir outdir 5 | 6 | # Runs corpus (directory of testcases) against a program 7 | # built with coverage, and produces a html report. 8 | 9 | # The program should be built with --coverage -fprofile-abs-path 10 | # -O0 may make the html report more legible? 11 | 12 | # Requires lcov and https://github.com/mozilla/grcov 13 | 14 | import argparse 15 | import subprocess 16 | import sys 17 | from pathlib import Path 18 | 19 | 20 | def run(args): 21 | corpus = Path(args.corpus) 22 | outdir = Path(args.outdir) 23 | 24 | for c in Path(args.builddir).glob("**/*.gcda"): 25 | print(f"Removed old coverage {c}", file=sys.stderr) 26 | c.unlink() 27 | 28 | print("Running corpus", file=sys.stderr) 29 | for c in corpus.glob("*"): 30 | c = c.open("rb").read() 31 | subprocess.run([args.program], input=c) 32 | 33 | print("Running grcov", file=sys.stderr) 34 | outdir.mkdir(parents=True, exist_ok=True) 35 | coverage_paths = [args.builddir] 36 | lcov_file = outdir / "lcov.info" 37 | 38 | subprocess.run( 39 | [ 40 | "grcov", 41 | "-b", 42 | args.program, 43 | "-o", 44 | lcov_file, 45 | "-t", 46 | "lcov", 47 | "-s", 48 | args.srcdir, 49 | ] 50 | + coverage_paths, 51 | check=True, 52 | ) 53 | 54 | print("Running genhtml", file=sys.stderr) 55 | subprocess.run( 56 | [ 57 | "genhtml", 58 | "-o", 59 | outdir, 60 | "--show-details", 61 | "--highlight", 62 | "--ignore-errors", 63 | "source", 64 | "--ignore-errors", 65 | "unmapped", 66 | "--legend", 67 | lcov_file, 68 | ], 69 | check=True, 70 | ) 71 | 72 | html = outdir / "index.html" 73 | print(f"\n\nOutput is file://{html.absolute()}", file=sys.stderr) 74 | 75 | 76 | def main(): 77 | parser = argparse.ArgumentParser() 78 | parser.add_argument("corpus", type=str, help="Corpus directory") 79 | parser.add_argument("program", type=str, help="Target Program") 80 | parser.add_argument("srcdir", type=str, help="Source directory") 81 | parser.add_argument("builddir", type=str) 82 | parser.add_argument("outdir", type=str) 83 | args = parser.parse_args() 84 | 85 | run(args) 86 | 87 | 88 | if __name__ == "__main__": 89 | main() 90 | -------------------------------------------------------------------------------- /utils/mctp-astlpc-daemon.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "libmctp.h" 12 | #include "libmctp-astlpc.h" 13 | 14 | static const mctp_eid_t local_eid = 8; 15 | static const mctp_eid_t remote_eid = 9; 16 | 17 | static const uint8_t echo_req = 1; 18 | static const uint8_t echo_resp = 2; 19 | 20 | struct ctx { 21 | struct mctp *mctp; 22 | }; 23 | 24 | static void tx_message(struct ctx *ctx, mctp_eid_t eid, void *msg, size_t len) 25 | { 26 | uint8_t type; 27 | 28 | type = len > 0 ? *(uint8_t *)(msg) : 0x00; 29 | 30 | fprintf(stderr, "TX: dest EID 0x%02x: %zd bytes, first byte [0x%02x]\n", 31 | eid, len, type); 32 | mctp_message_tx(ctx->mctp, eid, 0, MCTP_MESSAGE_TO_SRC, msg, len); 33 | } 34 | 35 | static void rx_message(uint8_t eid, uint8_t msg_tag, bool tag_owner, void *data, 36 | void *msg, size_t len) 37 | { 38 | struct ctx *ctx = data; 39 | uint8_t type; 40 | 41 | type = len > 0 ? *(uint8_t *)(msg) : 0x00; 42 | 43 | fprintf(stderr, "RX: src EID 0x%02x: %zd bytes, first byte [0x%02x]\n", 44 | eid, len, type); 45 | 46 | if (type == echo_req) { 47 | *(uint8_t *)(msg) = echo_resp; 48 | tx_message(ctx, eid, msg, len); 49 | } 50 | } 51 | 52 | int main(void) 53 | { 54 | struct mctp_binding_astlpc *astlpc; 55 | struct mctp *mctp; 56 | struct ctx *ctx, _ctx; 57 | int rc; 58 | 59 | mctp = mctp_init(); 60 | assert(mctp); 61 | 62 | astlpc = mctp_astlpc_init_fileio(); 63 | assert(astlpc); 64 | 65 | mctp_astlpc_register_bus(astlpc, mctp, local_eid); 66 | 67 | ctx = &_ctx; 68 | ctx->mctp = mctp; 69 | 70 | mctp_set_rx_all(mctp, rx_message, ctx); 71 | 72 | for (;;) { 73 | #if 0 74 | 75 | struct pollfd pollfds[2]; 76 | 77 | pollfds[0].fd = STDIN_FILENO; 78 | pollfds[0].events = POLLIN; 79 | 80 | mctp_astlpc_init_pollfd(astlpc, &pollfds[1]); 81 | 82 | rc = poll(pollfds, 2, -1); 83 | if (rc < 0) 84 | err(EXIT_FAILURE, "poll"); 85 | 86 | if (pollfds[0].revents) { 87 | uint8_t buf[1024]; 88 | rc = read(STDIN_FILENO, buf, sizeof(buf)); 89 | if (rc == 0) 90 | break; 91 | if (rc < 0) 92 | err(EXIT_FAILURE, "read"); 93 | tx_message(ctx, remote_eid, buf, rc); 94 | } 95 | 96 | if (pollfds[1].revents) { 97 | rc = mctp_astlpc_poll(astlpc); 98 | if (rc) 99 | break; 100 | } 101 | #else 102 | (void)remote_eid; 103 | rc = mctp_astlpc_poll(astlpc); 104 | if (rc) 105 | break; 106 | 107 | #endif 108 | } 109 | 110 | return EXIT_SUCCESS; 111 | } 112 | -------------------------------------------------------------------------------- /alloc.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include 4 | 5 | #include "libmctp.h" 6 | #include "libmctp-alloc.h" 7 | 8 | #ifdef HAVE_CONFIG_H 9 | #include "config.h" 10 | #endif 11 | 12 | #include "compiler.h" 13 | 14 | #if defined(MCTP_DEFAULT_ALLOC) && defined(MCTP_CUSTOM_ALLOC) 15 | #error Default and Custom alloc are incompatible 16 | #endif 17 | 18 | #ifdef MCTP_DEFAULT_ALLOC 19 | static void *default_msg_malloc(size_t size, void *ctx __unused) 20 | { 21 | void *ptr = __mctp_alloc(size); 22 | return ptr; 23 | } 24 | 25 | static void default_msg_free(void *msg, void *ctx __unused) 26 | { 27 | __mctp_free(msg); 28 | } 29 | #endif 30 | 31 | /* Allocators provided as functions to call */ 32 | #ifdef MCTP_CUSTOM_ALLOC 33 | extern void *mctp_custom_malloc(size_t size); 34 | extern void mctp_custom_free(void *ptr); 35 | extern void *mctp_custom_msg_alloc(size_t size, void *ctx); 36 | extern void mctp_custom_msg_free(void *msg, void *ctx); 37 | #endif 38 | 39 | #ifdef MCTP_CUSTOM_ALLOC 40 | const 41 | #endif 42 | struct { 43 | void *(*m_alloc)(size_t); 44 | void (*m_free)(void *); 45 | /* Final argument is ctx */ 46 | void *(*m_msg_alloc)(size_t, void *); 47 | void (*m_msg_free)(void *, void *); 48 | } alloc_ops = { 49 | #ifdef MCTP_DEFAULT_ALLOC 50 | malloc, 51 | free, 52 | default_msg_malloc, 53 | default_msg_free, 54 | #endif 55 | #ifdef MCTP_CUSTOM_ALLOC 56 | mctp_custom_malloc, 57 | mctp_custom_free, 58 | mctp_custom_msg_alloc, 59 | mctp_custom_msg_free, 60 | #endif 61 | }; 62 | 63 | /* internal-only allocation functions */ 64 | void *__mctp_alloc(size_t size) 65 | { 66 | if (alloc_ops.m_alloc) 67 | return alloc_ops.m_alloc(size); 68 | assert(0); 69 | return NULL; 70 | } 71 | 72 | void __mctp_free(void *ptr) 73 | { 74 | if (alloc_ops.m_free) 75 | alloc_ops.m_free(ptr); 76 | else 77 | assert(0); 78 | } 79 | 80 | void *__mctp_msg_alloc(size_t size, struct mctp *mctp) 81 | { 82 | void *ctx = mctp_get_alloc_ctx(mctp); 83 | if (alloc_ops.m_msg_alloc) 84 | return alloc_ops.m_msg_alloc(size, ctx); 85 | assert(0); 86 | return NULL; 87 | } 88 | 89 | void __mctp_msg_free(void *ptr, struct mctp *mctp) 90 | { 91 | void *ctx = mctp_get_alloc_ctx(mctp); 92 | if (alloc_ops.m_msg_free) 93 | alloc_ops.m_msg_free(ptr, ctx); 94 | } 95 | 96 | #ifndef MCTP_CUSTOM_ALLOC 97 | void mctp_set_alloc_ops(void *(*m_alloc)(size_t), void (*m_free)(void *), 98 | void *(*m_msg_alloc)(size_t, void *), 99 | void (*m_msg_free)(void *, void *)) 100 | { 101 | alloc_ops.m_alloc = m_alloc; 102 | alloc_ops.m_free = m_free; 103 | alloc_ops.m_msg_alloc = m_msg_alloc; 104 | alloc_ops.m_msg_free = m_msg_free; 105 | } 106 | #endif // MCTP_CUSTOM_ALLOC 107 | -------------------------------------------------------------------------------- /utils/mctp-pipe.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include "compiler.h" 4 | #include "libmctp.h" 5 | #include "libmctp-serial.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static void rx_message(uint8_t eid __unused, bool tag_owner __unused, 16 | uint8_t msg_tag __unused, void *data __unused, void *msg, 17 | size_t len) 18 | { 19 | ssize_t rc; 20 | 21 | rc = write(STDOUT_FILENO, msg, len); 22 | if (rc < 0) 23 | warn("Write failed"); 24 | else if ((size_t)rc < len) 25 | warnx("Short write of length %zd, requested %zd", rc, len); 26 | } 27 | 28 | int main(void) 29 | { 30 | struct mctp_binding_serial *serial[2]; 31 | mctp_eid_t eids[] = { 8, 9 }; 32 | struct pollfd pollfds[3]; 33 | int rc, n, mctp_fds[2]; 34 | struct mctp *mctp[2]; 35 | 36 | mctp[0] = mctp_init(); 37 | mctp[1] = mctp_init(); 38 | 39 | assert(mctp[0] && mctp[1]); 40 | 41 | serial[0] = mctp_serial_init(); 42 | serial[1] = mctp_serial_init(); 43 | 44 | assert(serial[0] && serial[1]); 45 | 46 | rc = socketpair(AF_UNIX, SOCK_STREAM, 0, mctp_fds); 47 | if (rc) 48 | err(EXIT_FAILURE, "Can't create sockets"); 49 | 50 | mctp_serial_open_fd(serial[0], mctp_fds[0]); 51 | mctp_serial_open_fd(serial[1], mctp_fds[1]); 52 | 53 | mctp_register_bus(mctp[0], mctp_binding_serial_core(serial[0]), 54 | eids[0]); 55 | mctp_register_bus(mctp[1], mctp_binding_serial_core(serial[1]), 56 | eids[1]); 57 | 58 | mctp_set_rx_all(mctp[1], rx_message, NULL); 59 | 60 | pollfds[0].fd = mctp_fds[0]; 61 | pollfds[0].events = POLLIN; 62 | pollfds[1].fd = mctp_fds[1]; 63 | pollfds[1].events = POLLIN; 64 | pollfds[2].fd = STDIN_FILENO; 65 | pollfds[2].events = POLLIN; 66 | n = 3; 67 | 68 | for (;;) { 69 | uint8_t buf[1024]; 70 | 71 | rc = poll(pollfds, n, -1); 72 | if (rc < 0) 73 | return EXIT_FAILURE; 74 | 75 | if (pollfds[0].revents) { 76 | rc = mctp_serial_read(serial[0]); 77 | if (rc) 78 | pollfds[0].fd = -1; 79 | } 80 | 81 | if (pollfds[1].revents) { 82 | rc = mctp_serial_read(serial[1]); 83 | if (rc) 84 | pollfds[1].fd = -1; 85 | } 86 | 87 | if (n > 2 && pollfds[2].revents) { 88 | rc = read(STDIN_FILENO, buf, sizeof(buf)); 89 | if (rc == 0) { 90 | n = 2; 91 | close(mctp_fds[0]); 92 | pollfds[0].fd = -1; 93 | } else if (rc < 0) { 94 | err(EXIT_FAILURE, "read"); 95 | } else { 96 | mctp_message_tx(mctp[0], eids[1], 97 | MCTP_MESSAGE_TO_SRC, 0, buf, 98 | rc); 99 | } 100 | } 101 | 102 | if (n == 2 && pollfds[0].fd < 0 && pollfds[1].fd < 0) 103 | break; 104 | } 105 | 106 | return EXIT_SUCCESS; 107 | } 108 | -------------------------------------------------------------------------------- /crc-16-ccitt.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include "crc-16-ccitt.h" 4 | 5 | /* 6 | * Implementation detail and FCS lookup table is based 7 | * on https://www.ietf.org/rfc/rfc1662.txt Appendix C. 8 | * 9 | * FCS lookup table as calculated by the table generator. 10 | */ 11 | static const uint16_t crc_16_ccitt_table[256] = { 12 | 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 13 | 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 14 | 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 15 | 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 16 | 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 17 | 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 18 | 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 19 | 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 20 | 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 21 | 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 22 | 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 23 | 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 24 | 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 25 | 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 26 | 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 27 | 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 28 | 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 29 | 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 30 | 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 31 | 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 32 | 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 33 | 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 34 | 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 35 | 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 36 | 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 37 | 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 38 | 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 39 | 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 40 | 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 41 | }; 42 | 43 | /* 44 | * Calculate a new fcs given the current fcs and the new data. 45 | */ 46 | uint16_t crc_16_ccitt(uint16_t fcs, const uint8_t *cp, uint32_t len) 47 | { 48 | while (len--) 49 | fcs = crc_16_ccitt_byte(fcs, *cp++); 50 | 51 | return fcs; 52 | } 53 | 54 | uint16_t crc_16_ccitt_byte(uint16_t fcs, const uint8_t c) 55 | { 56 | return (fcs >> 8) ^ crc_16_ccitt_table[(fcs ^ c) & 0xff]; 57 | } 58 | -------------------------------------------------------------------------------- /tests/fuzz/fuzz-build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 3 | 4 | # Builds fuzzing variants. Run this from the toplevel directory. 5 | # Beware this will wipe build directories. 6 | 7 | # Requires honggfuzz and afl++ installed 8 | 9 | # Builds are: 10 | # * AFL (normal, asan, cmplog) 11 | # * honggfuzz (asan, msan, ubsan) 12 | # * -O0, with coverage 13 | 14 | import os 15 | import subprocess 16 | 17 | # reduce warning level since tests since gtest is noisy 18 | BASE_MESONFLAGS = "-Dwarning_level=2 -Ddefault_library=static --wipe".split() 19 | FUZZ_PROGRAMS = ["tests/fuzz/i2c-fuzz"] 20 | 21 | 22 | def build( 23 | build_dir: str, 24 | cc: str = None, 25 | cxx: str = None, 26 | cflags="", 27 | cxxflags="", 28 | opt="3", 29 | env={}, 30 | mesonflags=[], 31 | ): 32 | env = os.environ | env 33 | env["CFLAGS"] = cflags 34 | env["CXXFLAGS"] = cxxflags 35 | 36 | # Meson sets CC="ccache cc" by default, but ccache removes -fprofile-arcs 37 | # so coverage breaks (ccache #1531). Prevent that by setting CC/CXX. 38 | env["CC"] = cc if cc else "cc" 39 | env["CXX"] = cxx if cxx else "c++" 40 | 41 | meson_cmd = ["meson"] + BASE_MESONFLAGS + mesonflags 42 | meson_cmd += [f"-Doptimization={opt}"] 43 | meson_cmd += [build_dir] 44 | subprocess.run(meson_cmd, env=env, check=True) 45 | 46 | ninja_cmd = ["ninja", "-C", build_dir] + FUZZ_PROGRAMS 47 | subprocess.run(ninja_cmd, env=env, check=True) 48 | 49 | 50 | def build_afl(): 51 | env = { 52 | # seems to be required for afl-clang-lto? 53 | "AFL_REAL_LD": "ld.lld", 54 | } 55 | cc = "afl-clang-lto" 56 | cxx = "afl-clang-lto++" 57 | 58 | # normal 59 | build("bfuzz", cc=cc, cxx=cxx, env=env) 60 | # ASAN 61 | build( 62 | "bfuzzasan", 63 | cc=cc, 64 | cxx=cxx, 65 | mesonflags=["-Db_sanitize=address"], 66 | env=env, 67 | ) 68 | # cmplog 69 | build("bcmplog", cc=cc, cxx=cxx, env={"AFL_LLVM_CMPLOG": "1"} | env) 70 | 71 | 72 | def main(): 73 | # No profiling, has coverage 74 | build( 75 | "bnoopt", 76 | cflags="-fprofile-abs-path", 77 | cxxflags="-fprofile-abs-path", 78 | opt="0", 79 | mesonflags=["-Db_coverage=true"], 80 | ) 81 | 82 | # AFL 83 | build_afl() 84 | 85 | # Honggfuzz 86 | # asan by default 87 | build( 88 | "bhf", 89 | cc="hfuzz-clang", 90 | cxx="hfuzz-clang++", 91 | env={"HFUZZ_CC_ASAN": "1"}, 92 | ) 93 | # msan 94 | build( 95 | "bhf-msan", 96 | cc="hfuzz-clang", 97 | cxx="hfuzz-clang++", 98 | env={"HFUZZ_CC_MSAN": "1"}, 99 | ) 100 | # ubsan 101 | build( 102 | "bhf-ubsan", 103 | cc="hfuzz-clang", 104 | cxx="hfuzz-clang++", 105 | env={"HFUZZ_CC_UBSAN": "1"}, 106 | ) 107 | 108 | 109 | if __name__ == "__main__": 110 | main() 111 | -------------------------------------------------------------------------------- /core-internal.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | #pragma once 3 | 4 | #include "libmctp.h" 5 | 6 | /* 64kb should be sufficient for a single message. Applications 7 | * requiring higher sizes can override by setting max_message_size.*/ 8 | #ifndef MCTP_MAX_MESSAGE_SIZE 9 | #define MCTP_MAX_MESSAGE_SIZE 65536 10 | #endif 11 | 12 | /* Must be >= 2 for bridge busses */ 13 | #ifndef MCTP_MAX_BUSSES 14 | #define MCTP_MAX_BUSSES 2 15 | #endif 16 | 17 | /* Concurrent reassembly contexts. */ 18 | #ifndef MCTP_REASSEMBLY_CTXS 19 | #define MCTP_REASSEMBLY_CTXS 16 20 | #endif 21 | 22 | /* Outbound request tags */ 23 | #ifndef MCTP_REQ_TAGS 24 | #define MCTP_REQ_TAGS MCTP_REASSEMBLY_CTXS 25 | #endif 26 | 27 | #ifndef MCTP_DEFAULT_CLOCK_GETTIME 28 | #define MCTP_DEFAULT_CLOCK_GETTIME 1 29 | #endif 30 | 31 | #ifndef MCTP_CONTROL_HANDLER 32 | #define MCTP_CONTROL_HANDLER 1 33 | #endif 34 | 35 | /* Tag expiry timeout, in milliseconds */ 36 | static const uint64_t MCTP_TAG_TIMEOUT = 6000; 37 | 38 | /* Internal data structures */ 39 | 40 | enum mctp_bus_state { 41 | mctp_bus_state_constructed = 0, 42 | mctp_bus_state_tx_enabled, 43 | mctp_bus_state_tx_disabled, 44 | }; 45 | 46 | struct mctp_bus { 47 | mctp_eid_t eid; 48 | struct mctp_binding *binding; 49 | enum mctp_bus_state state; 50 | struct mctp *mctp; 51 | 52 | /* Current message to transmit */ 53 | void *tx_msg; 54 | /* Position in tx_msg */ 55 | size_t tx_msgpos; 56 | /* Length of tx_msg */ 57 | size_t tx_msglen; 58 | /* Length of current packet payload */ 59 | size_t tx_pktlen; 60 | uint8_t tx_seq; 61 | uint8_t tx_src; 62 | uint8_t tx_dest; 63 | bool tx_to; 64 | uint8_t tx_tag; 65 | 66 | /* todo: routing */ 67 | }; 68 | 69 | struct mctp_msg_ctx { 70 | /* NULL buf indicates an unused mctp_msg_ctx */ 71 | void *buf; 72 | 73 | uint8_t src; 74 | uint8_t dest; 75 | uint8_t tag; 76 | uint8_t last_seq; 77 | size_t buf_size; 78 | size_t buf_alloc_size; 79 | size_t fragment_size; 80 | }; 81 | 82 | struct mctp_req_tag { 83 | /* 0 is an unused entry */ 84 | mctp_eid_t local; 85 | mctp_eid_t remote; 86 | uint8_t tag; 87 | /* time of tag expiry */ 88 | uint64_t expiry; 89 | }; 90 | 91 | #define MCTP_CONTROL_MAX_TYPES 10 92 | 93 | struct mctp_control { 94 | /* Types to report from Get MCTP Version Support */ 95 | uint8_t msg_types[MCTP_CONTROL_MAX_TYPES]; 96 | size_t num_msg_types; 97 | }; 98 | 99 | struct mctp { 100 | int n_busses; 101 | struct mctp_bus busses[MCTP_MAX_BUSSES]; 102 | 103 | /* Message RX callback */ 104 | mctp_rx_fn message_rx; 105 | void *message_rx_data; 106 | 107 | /* Packet capture callback */ 108 | mctp_capture_fn capture; 109 | void *capture_data; 110 | 111 | /* Message reassembly. */ 112 | struct mctp_msg_ctx msg_ctxs[MCTP_REASSEMBLY_CTXS]; 113 | 114 | /* Allocated outbound TO tags */ 115 | struct mctp_req_tag req_tags[MCTP_REQ_TAGS]; 116 | /* used to avoid always allocating tag 0 */ 117 | uint8_t tag_round_robin; 118 | 119 | enum { 120 | ROUTE_ENDPOINT, 121 | ROUTE_BRIDGE, 122 | } route_policy; 123 | size_t max_message_size; 124 | 125 | #if MCTP_CONTROL_HANDLER 126 | struct mctp_control control; 127 | #endif 128 | 129 | void *alloc_ctx; 130 | 131 | uint64_t (*platform_now)(void *); 132 | void *platform_now_ctx; 133 | }; 134 | -------------------------------------------------------------------------------- /tests/test_bridge.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifdef NDEBUG 4 | #undef NDEBUG 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "test-utils.h" 16 | 17 | struct mctp_binding_bridge { 18 | struct mctp_binding binding; 19 | int rx_count; 20 | int tx_count; 21 | uint8_t last_pkt_data; 22 | uint8_t tx_storage[MCTP_PKTBUF_SIZE(MCTP_BTU)] PKTBUF_STORAGE_ALIGN; 23 | }; 24 | 25 | struct test_ctx { 26 | struct mctp *mctp; 27 | struct mctp_binding_bridge *bindings[2]; 28 | }; 29 | 30 | static int mctp_binding_bridge_tx(struct mctp_binding *b, 31 | struct mctp_pktbuf *pkt) 32 | { 33 | struct mctp_binding_bridge *binding = 34 | container_of(b, struct mctp_binding_bridge, binding); 35 | 36 | binding->tx_count++; 37 | assert(mctp_pktbuf_size(pkt) == sizeof(struct mctp_hdr) + 1); 38 | binding->last_pkt_data = *(uint8_t *)mctp_pktbuf_data(pkt); 39 | 40 | return 0; 41 | } 42 | 43 | static void mctp_binding_bridge_rx(struct mctp_binding_bridge *binding, 44 | uint8_t key) 45 | { 46 | struct mctp_pktbuf *pkt; 47 | struct mctp_hdr *hdr; 48 | uint8_t *buf; 49 | 50 | pkt = mctp_pktbuf_alloc(&binding->binding, sizeof(struct mctp_hdr) + 1); 51 | assert(pkt); 52 | 53 | hdr = mctp_pktbuf_hdr(pkt); 54 | hdr->flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM; 55 | 56 | /* arbitrary src/dest, as we're bridging */ 57 | hdr->src = 1; 58 | hdr->dest = 2; 59 | 60 | buf = mctp_pktbuf_data(pkt); 61 | *buf = key; 62 | 63 | binding->rx_count++; 64 | mctp_bus_rx(&binding->binding, pkt); 65 | mctp_pktbuf_free(pkt); 66 | } 67 | 68 | static struct mctp_binding_bridge *mctp_binding_bridge_init(char *name) 69 | { 70 | struct mctp_binding_bridge *binding; 71 | 72 | binding = __mctp_alloc(sizeof(*binding)); 73 | memset(binding, 0, sizeof(*binding)); 74 | binding->binding.name = name; 75 | binding->binding.version = 1; 76 | binding->binding.tx = mctp_binding_bridge_tx; 77 | binding->binding.pkt_size = MCTP_PACKET_SIZE(MCTP_BTU); 78 | binding->binding.pkt_header = 0; 79 | binding->binding.pkt_trailer = 0; 80 | binding->binding.tx_storage = binding->tx_storage; 81 | return binding; 82 | } 83 | 84 | int main(void) 85 | { 86 | struct test_ctx _ctx, *ctx = &_ctx; 87 | ctx->mctp = mctp_init(); 88 | 89 | ctx->bindings[0] = mctp_binding_bridge_init("binding0"); 90 | ctx->bindings[1] = mctp_binding_bridge_init("binding1"); 91 | mctp_bridge_busses(ctx->mctp, &ctx->bindings[0]->binding, 92 | &ctx->bindings[1]->binding); 93 | 94 | mctp_binding_set_tx_enabled(&ctx->bindings[0]->binding, true); 95 | mctp_binding_set_tx_enabled(&ctx->bindings[1]->binding, true); 96 | 97 | mctp_binding_bridge_rx(ctx->bindings[0], 0xaa); 98 | assert(ctx->bindings[0]->tx_count == 0); 99 | assert(ctx->bindings[1]->tx_count == 1); 100 | assert(ctx->bindings[1]->last_pkt_data == 0xaa); 101 | 102 | mctp_binding_bridge_rx(ctx->bindings[1], 0x55); 103 | assert(ctx->bindings[1]->tx_count == 1); 104 | assert(ctx->bindings[0]->tx_count == 1); 105 | assert(ctx->bindings[0]->last_pkt_data == 0x55); 106 | 107 | __mctp_free(ctx->bindings[1]); 108 | __mctp_free(ctx->bindings[0]); 109 | mctp_destroy(ctx->mctp); 110 | 111 | return EXIT_SUCCESS; 112 | } 113 | -------------------------------------------------------------------------------- /tests/test_cmds.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include "test-utils.h" 4 | 5 | #include "compiler.h" 6 | #include "libmctp.h" 7 | #include "libmctp-alloc.h" 8 | #include "libmctp-cmds.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef NDEBUG 15 | #undef NDEBUG 16 | #endif 17 | 18 | static const mctp_eid_t eid_1 = 9; 19 | static const mctp_eid_t eid_2 = 10; 20 | 21 | struct msg_payload { 22 | struct mctp_hdr hdr; 23 | struct mctp_ctrl_msg_hdr ctrl_hdr; 24 | }; 25 | 26 | struct callback_data { 27 | uint8_t invoked; 28 | union { 29 | uint8_t command_code; 30 | uint8_t completion_code; 31 | }; 32 | }; 33 | 34 | static void control_message_transport_callback(mctp_eid_t src __unused, 35 | bool tag_owner __unused, 36 | uint8_t msg_tag __unused, 37 | void *data, void *buf, 38 | size_t len __unused) 39 | { 40 | struct callback_data *ctx = data; 41 | struct mctp_ctrl_msg_hdr *msg_hdr = buf; 42 | printf("Transport control message received - command code: 0x%X\n", 43 | msg_hdr->command_code); 44 | ctx->invoked++; 45 | assert(msg_hdr->command_code == ctx->command_code); 46 | } 47 | 48 | static void rcv_ctrl_msg(struct mctp_binding *b, const void *buf, size_t len) 49 | { 50 | struct mctp_pktbuf *pkt = mctp_pktbuf_alloc(b, len); 51 | memcpy(mctp_pktbuf_hdr(pkt), buf, len); 52 | mctp_bus_rx(b, pkt); 53 | mctp_pktbuf_free(pkt); 54 | } 55 | 56 | static void setup_test_binding(struct mctp_binding *test_binding, 57 | struct mctp *test_endpoint, void *callback_ctx) 58 | { 59 | assert(test_binding != NULL); 60 | assert(test_endpoint != NULL); 61 | assert(callback_ctx != NULL); 62 | 63 | uint8_t tx_storage[MCTP_PKTBUF_SIZE(MCTP_BTU)] PKTBUF_STORAGE_ALIGN; 64 | memset(test_binding, 0, sizeof(*test_binding)); 65 | test_binding->name = "test"; 66 | test_binding->version = 1; 67 | test_binding->tx = NULL; 68 | test_binding->pkt_size = MCTP_PACKET_SIZE(MCTP_BTU); 69 | test_binding->pkt_header = 0; 70 | test_binding->pkt_trailer = 0; 71 | test_binding->control_rx = control_message_transport_callback; 72 | test_binding->control_rx_data = callback_ctx; 73 | test_binding->tx_storage = tx_storage; 74 | 75 | mctp_register_bus(test_endpoint, test_binding, eid_1); 76 | mctp_binding_set_tx_enabled(test_binding, true); 77 | } 78 | 79 | static void send_transport_control_message(void) 80 | { 81 | struct mctp *endpoint = mctp_init(); 82 | struct mctp_binding binding; 83 | struct callback_data ctx; 84 | static const struct msg_payload send_control_message_payload = { 85 | .hdr = { 86 | .dest = eid_1, 87 | .src = eid_2, 88 | .flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM, 89 | }, 90 | .ctrl_hdr = { 91 | .ic_msg_type = MCTP_CTRL_HDR_MSG_TYPE, 92 | .rq_dgram_inst = MCTP_CTRL_HDR_FLAG_REQUEST, 93 | .command_code = 0xF2, 94 | }, 95 | }; 96 | 97 | memset(&ctx, 0, sizeof(ctx)); 98 | setup_test_binding(&binding, endpoint, &ctx); 99 | ctx.command_code = send_control_message_payload.ctrl_hdr.command_code; 100 | printf("Sending transport control message: 0x%X\n", 101 | send_control_message_payload.ctrl_hdr.command_code); 102 | rcv_ctrl_msg(&binding, (void *)&send_control_message_payload, 103 | sizeof(send_control_message_payload)); 104 | assert(ctx.invoked == 1); 105 | 106 | mctp_destroy(endpoint); 107 | } 108 | 109 | int main(void) 110 | { 111 | send_transport_control_message(); 112 | 113 | return EXIT_SUCCESS; 114 | } 115 | -------------------------------------------------------------------------------- /tests/test_seq.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifdef NDEBUG 4 | #undef NDEBUG 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "compiler.h" 13 | #include "libmctp.h" 14 | #include "test-utils.h" 15 | 16 | #ifndef ARRAY_SIZE 17 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 18 | #endif 19 | 20 | struct test_ctx { 21 | struct mctp *mctp; 22 | struct mctp_binding_test *binding; 23 | int rx_count; 24 | uint8_t rx_data[4]; 25 | size_t rx_len; 26 | }; 27 | 28 | static void test_rx(uint8_t eid __unused, bool tag_owner __unused, 29 | uint8_t msg_tag __unused, void *data, void *msg, size_t len) 30 | { 31 | struct test_ctx *ctx = data; 32 | 33 | ctx->rx_count++; 34 | 35 | /* append incoming message data to the existing rx_data */ 36 | assert(len <= sizeof(ctx->rx_data)); 37 | assert(ctx->rx_len + len <= sizeof(ctx->rx_data)); 38 | 39 | memcpy(ctx->rx_data + ctx->rx_len, msg, len); 40 | ctx->rx_len += len; 41 | } 42 | 43 | #define SEQ(x) (x << MCTP_HDR_SEQ_SHIFT) 44 | 45 | struct test { 46 | int n_packets; 47 | uint8_t flags_seq_tags[4]; 48 | int exp_rx_count; 49 | size_t exp_rx_len; 50 | } tests[] = { 51 | { 52 | /* single packet */ 53 | .n_packets = 1, 54 | .flags_seq_tags = { 55 | SEQ(1) | MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM, 56 | }, 57 | .exp_rx_count = 1, 58 | .exp_rx_len = 1, 59 | }, 60 | { 61 | /* two packets: one start, one end */ 62 | .n_packets = 2, 63 | .flags_seq_tags = { 64 | SEQ(1) | MCTP_HDR_FLAG_SOM, 65 | SEQ(2) | MCTP_HDR_FLAG_EOM, 66 | }, 67 | .exp_rx_count = 1, 68 | .exp_rx_len = 2, 69 | }, 70 | { 71 | /* three packets: one start, one no flags, one end */ 72 | .n_packets = 3, 73 | .flags_seq_tags = { 74 | SEQ(1) | MCTP_HDR_FLAG_SOM, 75 | SEQ(2), 76 | SEQ(3) | MCTP_HDR_FLAG_EOM, 77 | }, 78 | .exp_rx_count = 1, 79 | .exp_rx_len = 3, 80 | }, 81 | { 82 | /* two packets, wrapping sequence numbers */ 83 | .n_packets = 2, 84 | .flags_seq_tags = { 85 | SEQ(3) | MCTP_HDR_FLAG_SOM, 86 | SEQ(0) | MCTP_HDR_FLAG_EOM, 87 | }, 88 | .exp_rx_count = 1, 89 | .exp_rx_len = 2, 90 | }, 91 | { 92 | /* two packets, invalid sequence number */ 93 | .n_packets = 2, 94 | .flags_seq_tags = { 95 | SEQ(1) | MCTP_HDR_FLAG_SOM, 96 | SEQ(3) | MCTP_HDR_FLAG_EOM, 97 | }, 98 | .exp_rx_count = 0, 99 | .exp_rx_len = 0, 100 | }, 101 | }; 102 | 103 | static void run_one_test(struct test_ctx *ctx, struct test *test) 104 | { 105 | const mctp_eid_t local_eid = 8; 106 | const mctp_eid_t remote_eid = 9; 107 | struct { 108 | struct mctp_hdr hdr; 109 | uint8_t payload[1]; 110 | } pktbuf; 111 | int i; 112 | 113 | ctx->rx_count = 0; 114 | ctx->rx_len = 0; 115 | 116 | mctp_test_stack_init(&ctx->mctp, &ctx->binding, local_eid); 117 | 118 | mctp_set_rx_all(ctx->mctp, test_rx, ctx); 119 | 120 | for (i = 0; i < test->n_packets; i++) { 121 | memset(&pktbuf, 0, sizeof(pktbuf)); 122 | pktbuf.hdr.dest = local_eid; 123 | pktbuf.hdr.src = remote_eid; 124 | pktbuf.hdr.flags_seq_tag = test->flags_seq_tags[i]; 125 | pktbuf.payload[0] = i; 126 | 127 | mctp_binding_test_rx_raw(ctx->binding, &pktbuf, sizeof(pktbuf)); 128 | } 129 | 130 | assert(ctx->rx_count == test->exp_rx_count); 131 | assert(ctx->rx_len == test->exp_rx_len); 132 | 133 | /* ensure the payload data was reconstructed correctly */ 134 | for (i = 0; i < (int)ctx->rx_len; i++) 135 | assert(ctx->rx_data[i] == i); 136 | 137 | mctp_binding_test_destroy(ctx->binding); 138 | mctp_destroy(ctx->mctp); 139 | } 140 | 141 | int main(void) 142 | { 143 | struct test_ctx ctx; 144 | unsigned int i; 145 | 146 | for (i = 0; i < ARRAY_SIZE(tests); i++) 147 | run_one_test(&ctx, &tests[i]); 148 | 149 | return EXIT_SUCCESS; 150 | } 151 | -------------------------------------------------------------------------------- /utils/mctp-capture.c: -------------------------------------------------------------------------------- 1 | #include "utils/mctp-capture.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #if HAVE_PCAP 8 | #include 9 | #include 10 | #include "libmctp-alloc.h" 11 | 12 | #ifndef ETH_P_MCTP 13 | #define ETH_P_MCTP 0xfa 14 | #endif 15 | 16 | #endif 17 | 18 | int capture_init(void) 19 | { 20 | char errbuf[PCAP_ERRBUF_SIZE]; 21 | int rc; 22 | 23 | if ((rc = pcap_init(PCAP_CHAR_ENC_UTF_8, errbuf)) == -1) { 24 | fprintf(stderr, "pcap_init: %s\n", errbuf); 25 | return -1; 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | int capture_prepare(struct capture *cap) 32 | { 33 | if (!(cap->pcap = pcap_open_dead(CAPTURE_LINKTYPE_LINUX_SLL2, 34 | UINT16_MAX))) { 35 | fprintf(stderr, "pcap_open_dead: failed\n"); 36 | return -1; 37 | } 38 | if (!(cap->dumper = pcap_dump_open(cap->pcap, cap->path))) { 39 | fprintf(stderr, "pcap_dump_open: failed\n"); 40 | return -1; 41 | } 42 | 43 | return 0; 44 | } 45 | 46 | void capture_close(struct capture *cap) 47 | { 48 | pcap_dump_close(cap->dumper); 49 | 50 | pcap_close(cap->pcap); 51 | } 52 | 53 | void capture_binding(struct mctp_pktbuf *pkt, bool outgoing, void *user) 54 | { 55 | pcap_dumper_t *dumper = user; 56 | struct pcap_pkthdr hdr; 57 | int rc; 58 | uint8_t *pktbuf = NULL; 59 | size_t size; 60 | 61 | if ((rc = gettimeofday(&hdr.ts, NULL)) == -1) 62 | return; 63 | 64 | /* Write sll2 header */ 65 | size = sizeof(struct sll2_header) + mctp_pktbuf_size(pkt); 66 | pktbuf = __mctp_alloc(size); 67 | if (!pktbuf) 68 | return; 69 | 70 | struct sll2_header *sll2 = (struct sll2_header *)pktbuf; 71 | sll2->sll2_protocol = htons(ETH_P_MCTP); 72 | if (outgoing) 73 | sll2->sll2_pkttype = LINUX_SLL_OUTGOING; 74 | else 75 | sll2->sll2_pkttype = LINUX_SLL_HOST; 76 | sll2->sll2_reserved_mbz = 0x0; 77 | sll2->sll2_if_index = 0x0; 78 | sll2->sll2_hatype = 0x0; 79 | sll2->sll2_halen = 0x0; 80 | memset(sll2->sll2_addr, 0, SLL_ADDRLEN); 81 | 82 | memcpy(pktbuf + sizeof(struct sll2_header), mctp_pktbuf_hdr(pkt), 83 | mctp_pktbuf_size(pkt)); 84 | 85 | hdr.caplen = size; 86 | hdr.len = size; 87 | 88 | pcap_dump((u_char *)dumper, &hdr, (const u_char *)pktbuf); 89 | __mctp_free(pktbuf); 90 | } 91 | 92 | void capture_socket(pcap_dumper_t *dumper, const void *buf, size_t len, 93 | bool outgoing, int eid) 94 | { 95 | struct pcap_pkthdr hdr; 96 | int rc; 97 | uint8_t *pktbuf = NULL; 98 | size_t size; 99 | 100 | if ((rc = gettimeofday(&hdr.ts, NULL)) == -1) 101 | return; 102 | 103 | /* Write sll2 header */ 104 | size = sizeof(struct sll2_header) + sizeof(struct mctp_hdr) + len; 105 | pktbuf = __mctp_alloc(size); 106 | if (!pktbuf) 107 | return; 108 | 109 | struct sll2_header *sll2 = (struct sll2_header *)pktbuf; 110 | sll2->sll2_protocol = htons(ETH_P_MCTP); 111 | if (outgoing) 112 | sll2->sll2_pkttype = LINUX_SLL_OUTGOING; 113 | else 114 | sll2->sll2_pkttype = LINUX_SLL_HOST; 115 | sll2->sll2_reserved_mbz = 0x0; 116 | sll2->sll2_if_index = 0x0; 117 | sll2->sll2_hatype = 0x0; 118 | sll2->sll2_halen = 0x0; 119 | memset(sll2->sll2_addr, 0, SLL_ADDRLEN); 120 | 121 | /* Write fake mctp header */ 122 | struct mctp_hdr *mctp = 123 | (struct mctp_hdr *)(pktbuf + sizeof(struct sll2_header)); 124 | mctp->ver = 1; 125 | mctp->flags_seq_tag = 0xc0; //set SOM and EOM 126 | if (outgoing) { 127 | mctp->dest = eid; 128 | mctp->src = 0; 129 | } else { 130 | mctp->dest = 0; 131 | mctp->src = eid; 132 | } 133 | 134 | /* Ignore the eid at start of buf */ 135 | memcpy(pktbuf + sizeof(struct sll2_header) + sizeof(struct mctp_hdr), 136 | (const uint8_t *)buf + 1, len - 1); 137 | 138 | hdr.caplen = size; 139 | hdr.len = size; 140 | 141 | pcap_dump((u_char *)dumper, &hdr, (const u_char *)pktbuf); 142 | __mctp_free(pktbuf); 143 | } 144 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | # Originally from Linux v5.6 4 | --- 5 | AccessModifierOffset: -4 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveMacros: true 8 | AlignConsecutiveAssignments: false 9 | AlignConsecutiveDeclarations: false 10 | #AlignEscapedNewlines: Left # Unknown to clang-format-4.0 11 | AlignOperands: Align 12 | AlignTrailingComments: 13 | Kind: Always 14 | OverEmptyLines: 1 15 | AllowAllParametersOfDeclarationOnNextLine: false 16 | AllowShortBlocksOnASingleLine: false 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: None 19 | AllowShortIfStatementsOnASingleLine: false 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: false 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterClass: false 29 | AfterControlStatement: false 30 | AfterEnum: false 31 | AfterFunction: true 32 | AfterNamespace: true 33 | AfterObjCDeclaration: false 34 | AfterStruct: false 35 | AfterUnion: false 36 | #AfterExternBlock: false # Unknown to clang-format-5.0 37 | BeforeCatch: false 38 | BeforeElse: false 39 | IndentBraces: false 40 | #SplitEmptyFunction: true # Unknown to clang-format-4.0 41 | #SplitEmptyRecord: true # Unknown to clang-format-4.0 42 | #SplitEmptyNamespace: true # Unknown to clang-format-4.0 43 | BreakAfterAttributes: Never 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Custom 46 | #BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 47 | BreakBeforeTernaryOperators: false 48 | BreakConstructorInitializersBeforeComma: false 49 | #BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 50 | BreakAfterJavaFieldAnnotations: false 51 | BreakStringLiterals: false 52 | ColumnLimit: 80 53 | CommentPragmas: '^ IWYU pragma:' 54 | #CompactNamespaces: false # Unknown to clang-format-4.0 55 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 56 | ConstructorInitializerIndentWidth: 8 57 | ContinuationIndentWidth: 8 58 | Cpp11BracedListStyle: false 59 | DeriveLineEnding: false 60 | DerivePointerAlignment: false 61 | DisableFormat: false 62 | ExperimentalAutoDetectBinPacking: false 63 | #FixNamespaceComments: false # Unknown to clang-format-4.0 64 | #IncludeBlocks: Preserve # Unknown to clang-format-5.0 65 | IncludeCategories: 66 | - Regex: '.*' 67 | Priority: 1 68 | IncludeIsMainRegex: '(Test)?$' 69 | IndentCaseLabels: false 70 | IndentExternBlock: NoIndent 71 | #IndentPPDirectives: None # Unknown to clang-format-5.0 72 | IndentWidth: 8 73 | IndentWrappedFunctionNames: false 74 | InsertNewlineAtEOF: true 75 | JavaScriptQuotes: Leave 76 | JavaScriptWrapImports: true 77 | KeepEmptyLinesAtTheStartOfBlocks: false 78 | LineEnding: LF 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: Inner 83 | #ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 84 | ObjCBlockIndentWidth: 8 85 | ObjCSpaceAfterProperty: true 86 | ObjCSpaceBeforeProtocolList: true 87 | 88 | # Taken from git's rules 89 | PenaltyBreakAssignment: 10 90 | PenaltyBreakBeforeFirstCallParameter: 30 91 | PenaltyBreakComment: 10 92 | PenaltyBreakFirstLessLess: 0 93 | PenaltyBreakString: 10 94 | PenaltyExcessCharacter: 100 95 | PenaltyReturnTypeOnItsOwnLine: 60 96 | 97 | PointerAlignment: Right 98 | ReflowComments: false 99 | SortIncludes: false 100 | #SortUsingDeclarations: false # Unknown to clang-format-4.0 101 | SpaceAfterCStyleCast: false 102 | SpaceAfterTemplateKeyword: true 103 | SpaceBeforeAssignmentOperators: true 104 | #SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 105 | #SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 106 | SpaceBeforeParens: ControlStatements 107 | #SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 108 | SpaceInEmptyParentheses: false 109 | SpacesBeforeTrailingComments: 1 110 | SpacesInAngles: false 111 | SpacesInContainerLiterals: false 112 | SpacesInCStyleCastParentheses: false 113 | SpacesInParentheses: false 114 | SpacesInSquareBrackets: false 115 | Standard: Cpp03 116 | TabWidth: 8 117 | UseTab: Always 118 | ... 119 | -------------------------------------------------------------------------------- /tests/test_serial.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #define _GNU_SOURCE 4 | 5 | #if HAVE_CONFIG_H 6 | #include "config.h" 7 | #endif 8 | 9 | #include "compiler.h" 10 | #include "libmctp-log.h" 11 | #include "libmctp-serial.h" 12 | 13 | #ifdef NDEBUG 14 | #undef NDEBUG 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | struct mctp_binding_serial_pipe { 26 | int ingress; 27 | int egress; 28 | 29 | struct mctp_binding_serial *serial; 30 | }; 31 | 32 | static int mctp_binding_serial_pipe_tx(void *data, void *buf, size_t len) 33 | { 34 | struct mctp_binding_serial_pipe *ctx = data; 35 | ssize_t rc; 36 | 37 | rc = write(ctx->egress, buf, len); 38 | assert(rc >= 0); 39 | assert((size_t)rc == len); 40 | 41 | return rc; 42 | } 43 | 44 | uint8_t mctp_msg_src[2 * MCTP_BTU]; 45 | 46 | static bool seen; 47 | static bool received_tag_owner; 48 | static uint8_t received_msg_tag; 49 | 50 | static void rx_message(uint8_t eid __unused, bool tag_owner, uint8_t msg_tag, 51 | void *data __unused, void *msg, size_t len) 52 | { 53 | uint8_t type; 54 | 55 | type = *(uint8_t *)msg; 56 | 57 | mctp_prdebug("MCTP message received: len %zd, type %d, tag %d", len, 58 | type, msg_tag); 59 | 60 | assert(sizeof(mctp_msg_src) == len); 61 | assert(!memcmp(mctp_msg_src, msg, len)); 62 | 63 | seen = true; 64 | received_msg_tag = msg_tag; 65 | received_tag_owner = tag_owner; 66 | } 67 | 68 | struct serial_test { 69 | struct mctp_binding_serial_pipe binding; 70 | struct mctp *mctp; 71 | }; 72 | 73 | int main(void) 74 | { 75 | struct serial_test scenario[2]; 76 | 77 | struct mctp_binding_serial_pipe *a; 78 | struct mctp_binding_serial_pipe *b; 79 | uint8_t msg_tag = 2; 80 | bool tag_owner = false; 81 | int p[2][2]; 82 | int rc; 83 | 84 | mctp_set_log_stdio(MCTP_LOG_DEBUG); 85 | 86 | /* 87 | * Adding data bytes (0x7e & 0x7d) for testing FCS calculation while the 88 | * escaped data is presented. 89 | * 90 | * Refer to DSP0253 chapter 7, data byte 0x7e / 0x7d should be replaced 91 | * to escape sequence 0x7d 0x5e / 0x7d 0x5d. 92 | * 93 | * For sender, FCS calculation should count data byte (0x7e / 0x7d) only, 94 | * not the escape sequece. For receiver, the escape sequence should be 95 | * translated back to data byte and put it in FCS calculation. 96 | * 97 | * If FCS calculation is not expected, similiar error msg 98 | * `serial: invalid fcs : 0xf5c1, expect 0x1d3e` will be observed. 99 | */ 100 | memset(&mctp_msg_src[0], 0x7e, 1); 101 | memset(&mctp_msg_src[1], 0x7d, 1); 102 | memset(&mctp_msg_src[2], 0x5a, MCTP_BTU - 2); 103 | memset(&mctp_msg_src[MCTP_BTU], 0xa5, MCTP_BTU); 104 | 105 | rc = pipe(p[0]); 106 | assert(!rc); 107 | 108 | rc = pipe(p[1]); 109 | assert(!rc); 110 | 111 | /* Instantiate the A side of the serial pipe */ 112 | scenario[0].mctp = mctp_init(); 113 | assert(scenario[0].mctp); 114 | scenario[0].binding.serial = mctp_serial_init(); 115 | assert(scenario[0].binding.serial); 116 | a = &scenario[0].binding; 117 | a->ingress = p[0][0]; 118 | a->egress = p[1][1]; 119 | mctp_serial_open_fd(a->serial, a->ingress); 120 | mctp_serial_set_tx_fn(a->serial, mctp_binding_serial_pipe_tx, a); 121 | mctp_register_bus(scenario[0].mctp, mctp_binding_serial_core(a->serial), 122 | 8); 123 | 124 | /* Instantiate the B side of the serial pipe */ 125 | scenario[1].mctp = mctp_init(); 126 | assert(scenario[1].mctp); 127 | mctp_set_rx_all(scenario[1].mctp, rx_message, NULL); 128 | scenario[1].binding.serial = mctp_serial_init(); 129 | assert(scenario[1].binding.serial); 130 | b = &scenario[1].binding; 131 | b->ingress = p[1][0]; 132 | b->egress = p[0][1]; 133 | mctp_serial_open_fd(b->serial, b->ingress); 134 | mctp_serial_set_tx_fn(b->serial, mctp_binding_serial_pipe_tx, a); 135 | mctp_register_bus(scenario[1].mctp, mctp_binding_serial_core(b->serial), 136 | 9); 137 | 138 | /* Transmit a message from A to B, with message tag */ 139 | rc = mctp_message_tx(scenario[0].mctp, 9, tag_owner, msg_tag, 140 | mctp_msg_src, sizeof(mctp_msg_src)); 141 | assert(rc == 0); 142 | 143 | /* Read the message at B from A */ 144 | seen = false; 145 | received_tag_owner = true; 146 | received_msg_tag = 0; 147 | mctp_serial_read(b->serial); 148 | assert(seen); 149 | assert(received_tag_owner == tag_owner); 150 | assert(received_msg_tag == msg_tag); 151 | 152 | mctp_serial_destroy(scenario[1].binding.serial); 153 | mctp_destroy(scenario[1].mctp); 154 | mctp_serial_destroy(scenario[0].binding.serial); 155 | mctp_destroy(scenario[0].mctp); 156 | 157 | return 0; 158 | } 159 | -------------------------------------------------------------------------------- /libmctp-cmds.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | #ifndef _LIBMCTP_CMDS_H 3 | #define _LIBMCTP_CMDS_H 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include "libmctp.h" 10 | 11 | /* 12 | * Helper structs and functions for MCTP control messages. 13 | * See DSP0236 v1.3.0 sec. 11 for reference. 14 | */ 15 | 16 | struct mctp_ctrl_msg_hdr { 17 | uint8_t ic_msg_type; 18 | uint8_t rq_dgram_inst; 19 | uint8_t command_code; 20 | }; 21 | 22 | #define MCTP_CTRL_HDR_MSG_TYPE 0 23 | #define MCTP_CTRL_HDR_FLAG_REQUEST (1 << 7) 24 | #define MCTP_CTRL_HDR_FLAG_DGRAM (1 << 6) 25 | #define MCTP_CTRL_HDR_INSTANCE_ID_MASK 0x1F 26 | 27 | /* 28 | * MCTP Control Command IDs 29 | * See DSP0236 v1.3.0 Table 12. 30 | */ 31 | #define MCTP_CTRL_CMD_RESERVED 0x00 32 | #define MCTP_CTRL_CMD_SET_ENDPOINT_ID 0x01 33 | #define MCTP_CTRL_CMD_GET_ENDPOINT_ID 0x02 34 | #define MCTP_CTRL_CMD_GET_ENDPOINT_UUID 0x03 35 | #define MCTP_CTRL_CMD_GET_VERSION_SUPPORT 0x04 36 | #define MCTP_CTRL_CMD_GET_MESSAGE_TYPE_SUPPORT 0x05 37 | #define MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT 0x06 38 | #define MCTP_CTRL_CMD_RESOLVE_ENDPOINT_ID 0x07 39 | #define MCTP_CTRL_CMD_ALLOCATE_ENDPOINT_IDS 0x08 40 | #define MCTP_CTRL_CMD_ROUTING_INFO_UPDATE 0x09 41 | #define MCTP_CTRL_CMD_GET_ROUTING_TABLE_ENTRIES 0x0A 42 | #define MCTP_CTRL_CMD_PREPARE_ENDPOINT_DISCOVERY 0x0B 43 | #define MCTP_CTRL_CMD_ENDPOINT_DISCOVERY 0x0C 44 | #define MCTP_CTRL_CMD_DISCOVERY_NOTIFY 0x0D 45 | #define MCTP_CTRL_CMD_GET_NETWORK_ID 0x0E 46 | #define MCTP_CTRL_CMD_QUERY_HOP 0x0F 47 | #define MCTP_CTRL_CMD_RESOLVE_UUID 0x10 48 | #define MCTP_CTRL_CMD_QUERY_RATE_LIMIT 0x11 49 | #define MCTP_CTRL_CMD_REQUEST_TX_RATE_LIMIT 0x12 50 | #define MCTP_CTRL_CMD_UPDATE_RATE_LIMIT 0x13 51 | #define MCTP_CTRL_CMD_QUERY_SUPPORTED_INTERFACES 0x14 52 | #define MCTP_CTRL_CMD_MAX 0x15 53 | /* 0xF0 - 0xFF are transport specific */ 54 | #define MCTP_CTRL_CMD_FIRST_TRANSPORT 0xF0 55 | #define MCTP_CTRL_CMD_LAST_TRANSPORT 0xFF 56 | 57 | /* 58 | * MCTP Control Completion Codes 59 | * See DSP0236 v1.3.0 Table 13. 60 | */ 61 | #define MCTP_CTRL_CC_SUCCESS 0x00 62 | #define MCTP_CTRL_CC_ERROR 0x01 63 | #define MCTP_CTRL_CC_ERROR_INVALID_DATA 0x02 64 | #define MCTP_CTRL_CC_ERROR_INVALID_LENGTH 0x03 65 | #define MCTP_CTRL_CC_ERROR_NOT_READY 0x04 66 | #define MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD 0x05 67 | /* 0x80 - 0xFF are command specific */ 68 | 69 | struct mctp_ctrl_cmd_empty_resp { 70 | struct mctp_ctrl_msg_hdr hdr; 71 | uint8_t completion_code; 72 | } __attribute__((packed)); 73 | 74 | /* Set Endpoint ID request, Operation. Bits [1:0] */ 75 | #define MCTP_CTRL_SET_EID_OP_MASK 0x03 76 | #define MCTP_CTRL_SET_EID_OP_SET 0x00 77 | #define MCTP_CTRL_SET_EID_OP_FORCE 0x01 78 | #define MCTP_CTRL_SET_EID_OP_RESET 0x02 79 | #define MCTP_CTRL_SET_EID_OP_SET_DISCOVERED 0x03 80 | 81 | struct mctp_ctrl_cmd_set_endpoint_id_req { 82 | struct mctp_ctrl_msg_hdr hdr; 83 | uint8_t operation; 84 | uint8_t eid; 85 | } __attribute__((packed)); 86 | 87 | /* Set Endpoint ID response, assignment status. Bits [1:0] */ 88 | #define MCTP_CTRL_SET_EID_STATUS_ACCEPTED 0x00 89 | #define MCTP_CTRL_SET_EID_STATUS_REJECTED 0x01 90 | 91 | struct mctp_ctrl_cmd_set_endpoint_id_resp { 92 | struct mctp_ctrl_msg_hdr hdr; 93 | uint8_t completion_code; 94 | uint8_t status; 95 | uint8_t eid; 96 | uint8_t pool_size; 97 | } __attribute__((packed)); 98 | 99 | /* Get Endpoint ID, Endpoint Type. Bits [5:4] */ 100 | #define MCTP_CTRL_ENDPOINT_TYPE_SIMPLE 0x00 101 | #define MCTP_CTRL_ENDPOINT_TYPE_BUSOWNER_BRIDGE 0x10 102 | 103 | /* Get Endpoint ID, Endpoint ID Type. Bits [1:0] */ 104 | #define MCTP_CTRL_ENDPOINT_ID_TYPE_DYNAMIC_ONLY 0x00 105 | #define MCTP_CTRL_ENDPOINT_ID_TYPE_STATIC 0x01 106 | #define MCTP_CTRL_ENDPOINT_ID_TYPE_STATIC_SAME 0x02 107 | #define MCTP_CTRL_ENDPOINT_ID_TYPE_STATIC_DIFFERENT 0x03 108 | 109 | struct mctp_ctrl_cmd_get_endpoint_id_resp { 110 | struct mctp_ctrl_msg_hdr hdr; 111 | uint8_t completion_code; 112 | uint8_t endpoint_id; 113 | uint8_t endpoint_type; 114 | uint8_t medium_specific; 115 | } __attribute__((packed)); 116 | 117 | #define MCTP_CTRL_VERSIONS_NOT_SUPPORTED 0x80 118 | 119 | struct mctp_ctrl_cmd_get_version_req { 120 | struct mctp_ctrl_msg_hdr hdr; 121 | uint8_t msg_type; 122 | } __attribute__((packed)); 123 | 124 | struct mctp_ctrl_cmd_get_version_resp { 125 | struct mctp_ctrl_msg_hdr hdr; 126 | uint8_t completion_code; 127 | uint8_t version_count; 128 | uint32_t versions[]; 129 | } __attribute__((packed)); 130 | 131 | struct mctp_ctrl_cmd_get_types_resp { 132 | struct mctp_ctrl_msg_hdr hdr; 133 | uint8_t completion_code; 134 | uint8_t type_count; 135 | uint8_t types[]; 136 | } __attribute__((packed)); 137 | 138 | #ifdef __cplusplus 139 | } 140 | #endif 141 | 142 | #endif /* _LIBMCTP_CMDS_H */ 143 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # Initialization 2 | AC_PREREQ([2.71]) 3 | AC_INIT([libmctp],[0.11],[https://github.com/openbmc/libmctp/issues]) 4 | AC_CONFIG_HEADERS([config.h]) 5 | AC_CONFIG_MACRO_DIRS([m4]) 6 | AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror -Wno-portability foreign dist-xz]) 7 | AM_SILENT_RULES([yes]) 8 | 9 | # Checks for programs. 10 | AC_PROG_CC 11 | AM_PROG_AR 12 | AC_PROG_INSTALL 13 | AC_PROG_MAKE_SET 14 | 15 | # libtool init 16 | LT_INIT 17 | 18 | AC_CHECK_HEADERS_ONCE([endian.h]) 19 | AC_CHECK_HEADERS_ONCE([unistd.h fcntl.h]) 20 | 21 | # pkg-config 22 | PKG_PROG_PKG_CONFIG 23 | PKG_INSTALLDIR 24 | 25 | AC_ARG_ENABLE([capture], 26 | [AS_HELP_STRING([--enable-capture],[Use libpcap to capture messages and packets])]) 27 | AS_IF([test "x$enable_capture" = "xyes"], 28 | [PKG_CHECK_MODULES(pcap, libpcap, 29 | [AC_DEFINE([HAVE_PCAP], [1], 30 | [Define to 1 if you have libpcap])], 31 | [])], 32 | []) 33 | AC_SUBST([pcap_CFLAGS]) 34 | AC_SUBST([pcap_LIBS]) 35 | AM_CONDITIONAL([HAVE_PCAP], [test "x$enable_capture" = "xyes"]) 36 | 37 | AC_ARG_WITH([systemdsystemunitdir], 38 | [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])], 39 | [], 40 | [with_systemdsystemunitdir=auto] 41 | ) 42 | AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], 43 | [def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) 44 | AS_IF([test "x$def_systemdsystemunitdir" = "x"], 45 | [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], 46 | [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])] 47 | ) 48 | with_systemdsystemunitdir=no], 49 | [with_systemdsystemunitdir="$def_systemdsystemunitdir"] 50 | )] 51 | ) 52 | 53 | AC_CHECK_HEADER([systemd/sd-daemon.h], 54 | [AC_DEFINE([HAVE_SYSTEMD_SD_DAEMON_H], [1], 55 | [Define to 1 if you have .])], 56 | []) 57 | AC_CHECK_LIB([systemd], [sd_listen_fds]) 58 | AS_IF([test "x$with_systemdsystemunitdir" != "xno"], 59 | [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])] 60 | ) 61 | AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) 62 | 63 | AC_ARG_WITH([syslog], 64 | [AS_HELP_STRING([--with-syslog], [Support logging to syslog])], 65 | [], 66 | [with_syslog=check]) 67 | 68 | AS_IF([test "x$with_syslog" != "xno"], 69 | [AC_COMPILE_IFELSE( 70 | [AC_LANG_PROGRAM([[ 71 | #include 72 | #include 73 | 74 | void check_vsyslog(int level, const char *fmt, ...) 75 | { 76 | va_list ap; 77 | va_start(ap, fmt); 78 | vsyslog(0, fmt, ap); 79 | va_end(ap); 80 | } 81 | ]],[[ 82 | check_vsyslog(0, "\n"); 83 | ]])], 84 | [AC_DEFINE([MCTP_HAVE_SYSLOG], [1], [Define to enable syslog])], 85 | [])], 86 | []) 87 | 88 | AC_ARG_WITH([fileio], 89 | [AS_HELP_STRING([--with-fileio], 90 | [Support interfaces based on file-descriptors])], 91 | [], 92 | [with_fileio=check]) 93 | 94 | AS_IF([test "x$with_fileio" = "xcheck"], 95 | [AC_DEFINE([MCTP_HAVE_FILEIO], [(HAVE_UNISTD_H && HAVE_FCNTL_H)], 96 | [Support interfaces based on file-descriptors])], 97 | [AS_IF([test "x$with_fileio" = "xyes"], 98 | [AC_DEFINE([MCTP_HAVE_FILEIO], [1], 99 | [Support interfaces based on file-descriptors])], 100 | [])]) 101 | 102 | AC_ARG_WITH([stdio], 103 | [AS_HELP_STRING([--with-stdio], [Support logging to stdio])], 104 | [], 105 | [with_stdio=check]) 106 | 107 | AS_IF([test "x$with_stdio" != "xno"], 108 | [AC_COMPILE_IFELSE( 109 | [AC_LANG_PROGRAM([[ 110 | #include 111 | #include 112 | void check_vprintf(const char *fmt, ...) 113 | { 114 | va_list ap; 115 | va_start(ap, fmt); 116 | vprintf(fmt, ap); 117 | va_end(ap); 118 | } 119 | ]],[[ 120 | check_vprintf("\n"); 121 | ]])], 122 | [AC_DEFINE([MCTP_HAVE_STDIO], [1], [Define to enable stdio functions])], 123 | [])], 124 | []) 125 | 126 | AC_ARG_WITH([default-alloc], 127 | [AS_HELP_STRING([--with-default-alloc], 128 | [Use libc malloc and free for heap memory])], 129 | [], 130 | [with_default_alloc=check]) 131 | 132 | AS_IF([test "x$with_default_alloc" != "xno"], 133 | [AC_LINK_IFELSE( 134 | [AC_LANG_PROGRAM([[ 135 | #include 136 | ]], [[ 137 | free(malloc(4096)); 138 | ]])], 139 | [AC_DEFINE([MCTP_DEFAULT_ALLOC], 140 | [1], 141 | [Define to use libc malloc and free for heap memory])], 142 | [])], 143 | []) 144 | 145 | # Enable all bindings. AC_ARG_ENABLE in future. 146 | AM_CONDITIONAL([LIBMCTP_BINDING_serial], [true]) 147 | AM_CONDITIONAL([LIBMCTP_BINDING_astlpc], [true]) 148 | 149 | # Check for valgrind 150 | AS_IF([test "x$enable_tests" = "xno"], [enable_valgrind=no]) 151 | m4_foreach([vgtool], [valgrind_tool_list], 152 | [AX_VALGRIND_DFLT(vgtool, [off])]) 153 | AX_VALGRIND_DFLT([memcheck], [on]) 154 | AX_VALGRIND_CHECK 155 | AM_EXTRA_RECURSIVE_TARGETS([check-valgrind]) 156 | m4_foreach([vgtool], [valgrind_tool_list], 157 | [AM_EXTRA_RECURSIVE_TARGETS([check-valgrind-]vgtool)]) 158 | 159 | AX_CODE_COVERAGE 160 | m4_ifdef([_AX_CODE_COVERAGE_RULES], 161 | [AM_CONDITIONAL(AUTOCONF_CODE_COVERAGE_2019_01_06, [true])], 162 | [AM_CONDITIONAL(AUTOCONF_CODE_COVERAGE_2019_01_06, [false])]) 163 | AX_ADD_AM_MACRO_STATIC([]) 164 | 165 | AC_CONFIG_FILES([Makefile libmctp.pc]) 166 | AC_OUTPUT 167 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'libmctp', 3 | 'c', 4 | meson_version: '>= 1.1', 5 | version: '0.11', 6 | default_options: [ 7 | 'debug=true', 8 | 'optimization=g', 9 | 'warning_level=2', 10 | 'werror=true', 11 | 'tests=' + (meson.is_subproject() ? 'disabled' : 'enabled'), 12 | ], 13 | ) 14 | 15 | sources = ['core.c', 'alloc.c', 'control.c'] 16 | 17 | headers = ['libmctp.h'] 18 | 19 | serial_sources = ['serial.c', 'crc-16-ccitt.c'] 20 | 21 | serial_headers = ['libmctp-serial.h'] 22 | 23 | astlpc_sources = ['astlpc.c', 'crc32.c'] 24 | 25 | astlpc_headers = ['libmctp-astlpc.h'] 26 | 27 | i2c_sources = ['i2c.c'] 28 | 29 | i2c_headers = ['libmctp-i2c.h'] 30 | control_sources = ['control.c'] 31 | 32 | libmctp_sources = sources 33 | libmctp_headers = headers 34 | 35 | if get_option('bindings').contains('serial') 36 | libmctp_sources += serial_sources 37 | libmctp_headers += serial_headers 38 | endif 39 | if get_option('bindings').contains('astlpc') 40 | libmctp_sources += astlpc_sources 41 | libmctp_headers += astlpc_headers 42 | endif 43 | if get_option('bindings').contains('i2c') 44 | libmctp_sources += i2c_sources 45 | libmctp_headers += i2c_headers 46 | endif 47 | if get_option('control') 48 | libmctp_sources += control_sources 49 | endif 50 | 51 | compiler = meson.get_compiler('c') 52 | 53 | if not get_option('custom_alloc') and get_option('default_alloc').require( 54 | compiler.links( 55 | ''' 56 | #include 57 | void main() 58 | { 59 | free(malloc(4096)); 60 | } 61 | ''', 62 | ), 63 | ).allowed() 64 | add_project_arguments('-DMCTP_DEFAULT_ALLOC', language: 'c') 65 | endif 66 | 67 | if get_option('custom_alloc') 68 | add_project_arguments('-DMCTP_CUSTOM_ALLOC', language: 'c') 69 | endif 70 | 71 | if get_option('nolog') 72 | add_project_arguments('-DMCTP_NOLOG', language: 'c') 73 | else 74 | libmctp_sources += ['log.c'] 75 | endif 76 | 77 | option_args = [ 78 | '-DMCTP_MAX_MESSAGE_SIZE=@0@'.format(get_option('max_message_size')), 79 | '-DMCTP_REASSEMBLY_CTXS=@0@'.format(get_option('reassembly_contexts')), 80 | '-DMCTP_REQ_TAGS=@0@'.format(get_option('request_tags')), 81 | '-DMCTP_DEFAULT_CLOCK_GETTIME=@0@'.format( 82 | get_option('default_clock_gettime').to_int(), 83 | ), 84 | '-DMCTP_CONTROL_HANDLER=@0@'.format(get_option('control').to_int()), 85 | '-DI2C_BTU=@0@'.format(get_option('i2c_mtu')), 86 | '-DMCTP_I2C_NEIGH_COUNT=@0@'.format(get_option('i2c_neigh_count')), 87 | ] 88 | 89 | add_project_arguments(option_args, language: 'c') 90 | 91 | feat_fileio = get_option('fileio').require( 92 | compiler.links( 93 | ''' 94 | #include 95 | #include 96 | void main() 97 | { 98 | poll(NULL, 0, -1); 99 | } 100 | ''', 101 | ), 102 | ) 103 | if feat_fileio.allowed() 104 | add_project_arguments('-DMCTP_HAVE_FILEIO', language: 'c') 105 | endif 106 | 107 | if get_option('syslog').require( 108 | compiler.links( 109 | ''' 110 | #include 111 | #include 112 | void check_vsyslog(int level, const char *fmt, ...) 113 | { 114 | va_list ap; 115 | va_start(ap, fmt); 116 | vsyslog(0, fmt, ap); 117 | va_end(ap); 118 | } 119 | void main() 120 | { 121 | check_vsyslog(0, "\n"); 122 | } 123 | ''', 124 | ), 125 | ).allowed() 126 | add_project_arguments('-DMCTP_HAVE_SYSLOG', language: 'c') 127 | endif 128 | 129 | if get_option('stdio').require( 130 | compiler.links( 131 | ''' 132 | #include 133 | #include 134 | void check_vsyslog(const char *fmt, ...) 135 | { 136 | va_list ap; 137 | va_start(ap, fmt); 138 | vprintf(fmt, ap); 139 | va_end(ap); 140 | } 141 | void main() 142 | { 143 | check_vsyslog("\n"); 144 | } 145 | ''', 146 | ), 147 | ).allowed() 148 | add_project_arguments('-DMCTP_HAVE_STDIO', language: 'c') 149 | endif 150 | 151 | # pcap is necessary for mctp-demux-daemon to be functional 152 | pcap_dep = dependency('libpcap', required: false) 153 | 154 | systemd_dep = dependency('systemd', required: false) 155 | libsystemd_dep = dependency('libsystemd', required: false) 156 | 157 | libmctp_include_dir = include_directories('.', is_system: true) 158 | libmctp = library( 159 | 'mctp', 160 | libmctp_sources, 161 | include_directories: libmctp_include_dir, 162 | version: meson.project_version(), 163 | install: true, 164 | ) 165 | install_headers(libmctp_headers) 166 | 167 | if systemd_dep.found() 168 | unitdir = systemd_dep.get_variable(pkgconfig: 'systemd_system_unit_dir') 169 | install_data('systemd/system/mctp-demux.service', install_dir: unitdir) 170 | install_data('systemd/system/mctp-demux.socket', install_dir: unitdir) 171 | endif 172 | 173 | import('pkgconfig').generate( 174 | libmctp, 175 | name: 'libmctp', 176 | description: 'MCTP protocol implementation', 177 | version: meson.project_version(), 178 | ) 179 | 180 | libmctp_dep = declare_dependency( 181 | include_directories: libmctp_include_dir, 182 | link_with: libmctp, 183 | ) 184 | 185 | # TODO: these should depend on the -internal.h headers so they rebuild 186 | # on changes, unclear how to do that. 187 | sizeof_mctp = compiler.sizeof( 188 | 'struct mctp', 189 | include_directories: libmctp_include_dir, 190 | args: option_args, 191 | prefix: '#include "core-internal.h"', 192 | ) 193 | sizeof_binding_i2c = compiler.sizeof( 194 | 'struct mctp_binding_i2c', 195 | include_directories: libmctp_include_dir, 196 | args: option_args, 197 | prefix: '#include "i2c-internal.h"', 198 | ) 199 | sizes_h = configure_file( 200 | configuration: { 201 | 'sizeof_struct_mctp': sizeof_mctp, 202 | 'sizeof_binding_i2c': sizeof_binding_i2c, 203 | }, 204 | input: 'libmctp-sizes.h.in', 205 | output: 'libmctp-sizes.h', 206 | ) 207 | install_headers(sizes_h) 208 | 209 | if feat_fileio.allowed() 210 | subdir('utils') 211 | endif 212 | 213 | if get_option('tests').allowed() 214 | subdir('tests') 215 | endif 216 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libmctp: Implementation of MCTP (DTMF DSP0236) 2 | 3 | This library is intended to be a portable implementation of the Management 4 | Component Transport Protocol (MCTP), as defined by DMTF standard "DSP0236", plus 5 | transport binding specifications. 6 | 7 | ## Target usage 8 | 9 | `libmctp` is a library that implements a straightforward MCTP stack. It will be 10 | useful in a two main scenarios: 11 | 12 | - where you are implementing MCTP in an embedded device; or 13 | - where you have Linux system: 14 | - with no kernel MCTP support, 15 | - need a single application implementing all of the MCTP stack; and 16 | - you are providing your own hardware drivers for MCTP transports. 17 | 18 | Notably, if you are implementing an MCTP application on Linux, you _almost 19 | certainly_ want to use the in-kernel MCTP support, which gives you a standard 20 | sockets-based interface to transmit and receive MCTP messages. When using the 21 | Linux kernel MCTP support, you do not need to use `libmctp` at all, and can use 22 | the sockets directly. `libmctp` does not provide functions to interact with the 23 | kernel MCTP sockets. 24 | 25 | There is an overview and example code for the in-kernel support in the [MCTP 26 | kernel docs][kernel-mctp], and a general guide in an [introduction to MCTP on 27 | Linux][mctp-linux-intro] document. 28 | 29 | [kernel-mctp]: https://docs.kernel.org/networking/mctp.html 30 | [mctp-linux-intro]: 31 | https://codeconstruct.com.au/docs/mctp-on-linux-introduction/ 32 | 33 | ## Contact 34 | 35 | - Email: See [OWNERS](OWNERS). Please also Cc 36 | - Discord: #mctp on 37 | - IRC: #openbmc on Freenode 38 | 39 | ## API/ABI Stability 40 | 41 | The APIs and ABI of libmctp are not yet stablised as we continue to explore ways 42 | to present the MCTP protocol to firmware and applications. Please bear with us! 43 | 44 | When we approach a complete implementation of DSP0236 we will consider the 45 | suitability of the API/ABI for stabilisation. 46 | 47 | In the mean time, we'd like your feedback on the library's suitability for your 48 | environment. 49 | 50 | ## Core API 51 | 52 | To initialise the MCTP stack with a single hardware bus: 53 | 54 | - `mctp = mctp_init()`: Initialise the MCTP core 55 | - `binding = mctp__init()`: Initialise a hardware binding 56 | - `mctp_register_bus(mctp, binding, eid)`: Register the hardware binding with 57 | the core, using a predefined EID 58 | 59 | Then, register a function call to be invoked when a message is received: 60 | 61 | - `mctp_set_rx_all(mctp, function)`: Provide a callback to be invoked when a 62 | MCTP message is received 63 | 64 | Or transmit a message: 65 | 66 | - `mctp_message_tx(mctp, message, len)`: Transmit a MCTP message 67 | 68 | The binding may require you to notify it to receive packets. For example, for 69 | the serial binding, the `mctp_serial_read()` function should be invoked when the 70 | file-descriptor for the serial device has data available. 71 | 72 | ### Bridging 73 | 74 | libmctp implements basic support for bridging between two hardware bindings. In 75 | this mode, bindings may have different MTUs, so packets are reassembled into 76 | their messages, then the messages are re-packetised for the outgoing binding. 77 | 78 | For bridging between two endpoints, use the `mctp_bridge_busses()` function: 79 | 80 | - `mctp = mctp_init()`: Initialise the MCTP core 81 | - `b1 = mctp__init(); b2 = mctp__init()`: Initialise two 82 | hardware bindings 83 | - `mctp_bridge_busses(mctp, b1, b2)`: Setup bridge 84 | 85 | Note that no EIDs are defined here; the bridge does not deliver any messages to 86 | a local rx callback, and messages are bridged as-is. 87 | 88 | ## Binding API 89 | 90 | Hardware bindings provide a method for libmctp to send and receive packets 91 | to/from hardware. A binding defines a hardware specific structure 92 | (`struct mctp_binding_`), which wraps the generic binding 93 | (`struct mctp_binding`): 94 | 95 | struct mctp_binding_foo { 96 | struct mctp_binding binding; 97 | /* hardware-specific members here... */ 98 | }; 99 | 100 | The binding code then provides a method (`_init`) to allocate and initialise the 101 | binding; this may be of any prototype (calling code will know what arguments to 102 | pass): 103 | 104 | struct mctp_binding_foo *mctp_binding_foo_init(void); 105 | 106 | or maybe the `foo` binding needs a path argument: 107 | 108 | struct mctp_binding_foo *mctp_binding_foo_init(const char *path); 109 | 110 | The binding then needs to provide a function (`_core`) to convert the 111 | hardware-specific struct to the libmctp generic core struct 112 | 113 | struct mctp_binding *mctp_binding_foo_core(struct mctp_binding_foo *b); 114 | 115 | (Implementations of this will usually be fairly consistent, just returning 116 | `b->binding`). Callers can then use that generic pointer to register the binding 117 | with the core: 118 | 119 | struct mctp_binding *binding = mctp_binding_foo_core(foo); 120 | mctp_register_bus(mctp, binding, 8); 121 | 122 | ## Integration 123 | 124 | The libmctp code is intended to be integrated into other codebases by two 125 | methods: 126 | 127 | 1. as a simple library (`libmctp.{a,so}`) which can be compiled separately and 128 | linked into the containing project 129 | 130 | 2. as a set of sources to be included into the containing project (either 131 | imported, or as a git subtree/submodule) 132 | 133 | For (1), you can use the top-level makefile to produce `libmctp.a`. 134 | 135 | For (2), the `Makefile.inc` file provides the minimum set of dependencies to 136 | either build libmctp.a, or just the actual object files (`LIBMCTP_OBS`), which 137 | you can include into your existing make definitions. You'll want to set 138 | `LIBMTCP_DIR` to refer to the subdirectory that contains that makefile, so we 139 | can set the correct paths to sources. 140 | 141 | ## Environment configuration 142 | 143 | This library is intended to be portable to be used in a range of environments, 144 | but the main targets are: 145 | 146 | - Linux userspace, typically for BMC use-cases 147 | - Low-level firmware environments 148 | 149 | For the latter, we need to support customisation of the functions that libmctp 150 | uses (for example, POSIX file IO is not available). 151 | 152 | In order to support these, we have a few compile-time definitions: 153 | 154 | - `MCTP_HAVE_FILEIO`: define if POSIX file io is available, allowing the serial 155 | hardware binding to access char devices for IO. 156 | 157 | - `MCTP_HAVE_SYSLOG`: allow logging to syslog, through the `vsyslog` call. 158 | 159 | - `MCTP_DEFAULT_ALLOC`: set default allocator functions (malloc, free, realloc), 160 | so that applications do not have to provide their own. 161 | 162 | ## TODO 163 | 164 | - Partial packet queue transmit 165 | - Control messages 166 | - Message- and packet-buffer pools and preallocation 167 | - C++ API 168 | - Non-file-based serial binding 169 | -------------------------------------------------------------------------------- /i2c.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef HAVE_CONFIG_H 10 | #include "config.h" 11 | #endif 12 | 13 | #include "libmctp.h" 14 | #include "libmctp-alloc.h" 15 | #include "libmctp-log.h" 16 | #include "container_of.h" 17 | #include "libmctp-i2c.h" 18 | #include "i2c-internal.h" 19 | 20 | static const uint8_t MCTP_I2C_COMMAND = 0x0f; 21 | 22 | #define binding_to_i2c(b) container_of(b, struct mctp_binding_i2c, binding) 23 | 24 | static bool mctp_i2c_valid_addr(uint8_t addr) 25 | { 26 | return addr <= 0x7f; 27 | } 28 | 29 | static bool mctp_i2c_valid_eid(uint8_t eid) 30 | { 31 | /* Disallow reserved range */ 32 | return eid >= 8 && eid < 0xff; 33 | } 34 | 35 | static int mctp_i2c_core_start(struct mctp_binding *binding) 36 | { 37 | mctp_binding_set_tx_enabled(binding, true); 38 | return 0; 39 | } 40 | 41 | /* Returns 0 if an entry is found, or -ENOENT otherwise. 42 | * The last seen timestamp will be updated for found entries */ 43 | static int mctp_i2c_neigh_get(struct mctp_binding_i2c *i2c, uint8_t eid, 44 | uint8_t *ret_neigh_addr) 45 | { 46 | for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) { 47 | struct mctp_i2c_neigh *n = &i2c->neigh[i]; 48 | if (n->used && n->eid == eid) { 49 | n->last_seen_timestamp = mctp_now(i2c->binding.mctp); 50 | *ret_neigh_addr = n->addr; 51 | return 0; 52 | } 53 | } 54 | return -ENOENT; 55 | } 56 | 57 | /* Adds a new neighbour entry. If the table is full, the oldest 58 | * entry will be evicted. If eid already exists, that entry will 59 | * be replaced. */ 60 | static void mctp_i2c_neigh_add(struct mctp_binding_i2c *i2c, uint8_t eid, 61 | uint8_t addr) 62 | { 63 | assert(addr <= 0x7f); 64 | struct mctp_i2c_neigh *entry = NULL; 65 | for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) { 66 | struct mctp_i2c_neigh *n = &i2c->neigh[i]; 67 | if (!n->used) { 68 | /* Spare entry, use it */ 69 | entry = n; 70 | break; 71 | } 72 | 73 | if (n->eid == eid) { 74 | /* Replacing existing entry */ 75 | entry = n; 76 | break; 77 | } 78 | 79 | if (!entry || 80 | n->last_seen_timestamp < entry->last_seen_timestamp) { 81 | /* Use this as the provisional oldest, keep iterating */ 82 | entry = n; 83 | } 84 | } 85 | assert(entry); 86 | 87 | entry->addr = addr; 88 | entry->eid = eid; 89 | entry->used = true; 90 | entry->last_seen_timestamp = mctp_now(i2c->binding.mctp); 91 | } 92 | 93 | static int mctp_binding_i2c_tx(struct mctp_binding *b, struct mctp_pktbuf *pkt) 94 | { 95 | struct mctp_binding_i2c *i2c = binding_to_i2c(b); 96 | struct mctp_hdr *hdr = mctp_pktbuf_hdr(pkt); 97 | int rc; 98 | uint8_t neigh_addr; 99 | 100 | rc = mctp_i2c_neigh_get(i2c, hdr->dest, &neigh_addr); 101 | if (rc) { 102 | return rc; 103 | } 104 | 105 | struct mctp_i2c_hdr *i2c_hdr = 106 | mctp_pktbuf_alloc_start(pkt, sizeof(struct mctp_i2c_hdr)); 107 | i2c_hdr->dest = neigh_addr << 1; 108 | i2c_hdr->cmd = MCTP_I2C_COMMAND; 109 | size_t bytecount = mctp_pktbuf_size(pkt) - 110 | (offsetof(struct mctp_i2c_hdr, bytecount) + 1); 111 | if (bytecount > 0xff) { 112 | return -EINVAL; 113 | } 114 | i2c_hdr->bytecount = bytecount; 115 | i2c_hdr->source = i2c->own_addr << 1 | 1; 116 | 117 | rc = i2c->tx_fn(pkt->data + pkt->start, mctp_pktbuf_size(pkt), 118 | i2c->tx_ctx); 119 | switch (rc) { 120 | case -EMSGSIZE: 121 | case 0: 122 | break; 123 | case -EBUSY: 124 | default: 125 | mctp_binding_set_tx_enabled(&i2c->binding, false); 126 | } 127 | return rc; 128 | } 129 | 130 | int mctp_i2c_set_neighbour(struct mctp_binding_i2c *i2c, uint8_t eid, 131 | uint8_t addr) 132 | { 133 | if (!mctp_i2c_valid_eid(eid)) { 134 | return -EINVAL; 135 | } 136 | if (!mctp_i2c_valid_addr(addr)) { 137 | return -EINVAL; 138 | } 139 | 140 | mctp_i2c_neigh_add(i2c, eid, addr); 141 | return 0; 142 | } 143 | 144 | int mctp_i2c_setup(struct mctp_binding_i2c *i2c, uint8_t own_addr, 145 | mctp_i2c_tx_fn tx_fn, void *tx_ctx) 146 | { 147 | int rc; 148 | 149 | memset(i2c, 0x0, sizeof(*i2c)); 150 | 151 | rc = mctp_i2c_set_address(i2c, own_addr); 152 | if (rc) { 153 | return rc; 154 | } 155 | 156 | i2c->binding.name = "i2c"; 157 | i2c->binding.version = 1; 158 | i2c->binding.pkt_size = MCTP_PACKET_SIZE(I2C_BTU); 159 | i2c->binding.pkt_header = sizeof(struct mctp_i2c_hdr); 160 | i2c->binding.tx_storage = i2c->tx_storage; 161 | 162 | i2c->binding.start = mctp_i2c_core_start; 163 | i2c->binding.tx = mctp_binding_i2c_tx; 164 | 165 | i2c->tx_fn = tx_fn; 166 | i2c->tx_ctx = tx_ctx; 167 | 168 | return 0; 169 | } 170 | 171 | int mctp_i2c_set_address(struct mctp_binding_i2c *i2c, uint8_t own_addr) 172 | { 173 | if (!mctp_i2c_valid_addr(own_addr)) { 174 | return -EINVAL; 175 | } 176 | 177 | i2c->own_addr = own_addr; 178 | return 0; 179 | } 180 | 181 | struct mctp_binding *mctp_binding_i2c_core(struct mctp_binding_i2c *i2c) 182 | { 183 | return &i2c->binding; 184 | } 185 | 186 | static int mctp_i2c_hdr_validate(const struct mctp_i2c_hdr *hdr) 187 | { 188 | if (hdr->cmd != MCTP_I2C_COMMAND) { 189 | return -EINVAL; 190 | } 191 | if ((hdr->dest & 1) != 0) { 192 | return -EINVAL; 193 | } 194 | if ((hdr->source & 1) != 1) { 195 | return -EINVAL; 196 | } 197 | return 0; 198 | } 199 | 200 | void mctp_i2c_rx(struct mctp_binding_i2c *i2c, const void *data, size_t len) 201 | { 202 | int rc; 203 | 204 | if (len < sizeof(struct mctp_i2c_hdr)) { 205 | return; 206 | } 207 | const struct mctp_i2c_hdr *hdr = data; 208 | rc = mctp_i2c_hdr_validate(hdr); 209 | if (rc) { 210 | return; 211 | } 212 | 213 | if (hdr->bytecount != len - 3) { 214 | return; 215 | } 216 | 217 | if ((hdr->dest >> 1) != i2c->own_addr) { 218 | return; 219 | } 220 | 221 | uint8_t src = hdr->source >> 1; 222 | if (src == i2c->own_addr) { 223 | return; 224 | } 225 | 226 | struct mctp_pktbuf *pkt = 227 | mctp_pktbuf_init(&i2c->binding, i2c->rx_storage); 228 | rc = mctp_pktbuf_push( 229 | pkt, (const uint8_t *)data + sizeof(struct mctp_i2c_hdr), 230 | len - sizeof(struct mctp_i2c_hdr)); 231 | if (rc) { 232 | // Packet too large for I2C_BTU 233 | return; 234 | } 235 | 236 | if (mctp_pktbuf_size(pkt) < sizeof(struct mctp_hdr)) { 237 | return; 238 | } 239 | 240 | struct mctp_hdr *mctp_hdr = mctp_pktbuf_hdr(pkt); 241 | if (mctp_hdr->flags_seq_tag & MCTP_HDR_FLAG_TO) { 242 | /* Update neighbour entry */ 243 | mctp_i2c_neigh_add(i2c, mctp_hdr->src, src); 244 | } 245 | 246 | mctp_bus_rx(&i2c->binding, pkt); 247 | } 248 | 249 | int mctp_i2c_parse_hdr(const void *data, size_t len, uint8_t *src_addr, 250 | uint8_t *dest_addr, uint8_t *bytecount) 251 | { 252 | int rc; 253 | 254 | if (len < sizeof(struct mctp_i2c_hdr)) { 255 | return -EINVAL; 256 | } 257 | const struct mctp_i2c_hdr *hdr = data; 258 | rc = mctp_i2c_hdr_validate(hdr); 259 | if (rc) { 260 | return rc; 261 | } 262 | 263 | if (src_addr) { 264 | *src_addr = hdr->source >> 1; 265 | } 266 | if (dest_addr) { 267 | *dest_addr = hdr->dest >> 1; 268 | } 269 | if (bytecount) { 270 | *bytecount = hdr->bytecount; 271 | } 272 | return 0; 273 | } 274 | 275 | void mctp_i2c_tx_poll(struct mctp_binding_i2c *i2c) 276 | { 277 | mctp_binding_set_tx_enabled(&i2c->binding, true); 278 | } 279 | -------------------------------------------------------------------------------- /tests/fuzz/i2c-fuzz.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "compiler.h" 12 | #include "libmctp.h" 13 | #include "libmctp-i2c.h" 14 | #include "libmctp-sizes.h" 15 | #include "libmctp-alloc.h" 16 | 17 | #if NDEBUG 18 | static_assert(0, "fuzzing shouldn't build with NDEBUG"); 19 | #endif 20 | 21 | /* Limits memory used in tx path */ 22 | #define MAX_SEND 600 23 | 24 | /* Avoids wasting time traversing unreachable sizes */ 25 | #define MAX_RECEIVE 30 26 | 27 | static const size_t FUZZCTRL_SIZE = 0x400; 28 | 29 | static const uint8_t RX_CHANCE = 90; 30 | static const uint8_t TX_BUSY_CHANCE = 3; 31 | 32 | static const uint8_t OWN_I2C_ADDR = 0x20; 33 | static const uint8_t OWN_EID = 123; 34 | 35 | /* time step in milliseconds */ 36 | static const uint32_t MAX_TIME_STEP = 15000; 37 | 38 | struct fuzz_buf { 39 | size_t len; 40 | size_t pos; 41 | const uint8_t *data; 42 | }; 43 | 44 | struct fuzz_ctx { 45 | struct fuzz_buf *ctrl; 46 | struct fuzz_buf *input; 47 | 48 | struct mctp_binding_i2c *i2c; 49 | struct mctp *mctp; 50 | 51 | uint64_t now; 52 | 53 | bool done; 54 | }; 55 | 56 | static struct fuzz_buf *fuzz_buf_new(const void *data, size_t len) 57 | { 58 | struct fuzz_buf *buf = malloc(sizeof(struct fuzz_buf)); 59 | buf->pos = 0; 60 | buf->len = len; 61 | buf->data = data; 62 | return buf; 63 | } 64 | 65 | static const void *fuzz_buf_extract(struct fuzz_buf *buf, size_t len) 66 | { 67 | if (buf->pos + len > buf->len) { 68 | return NULL; 69 | } 70 | 71 | const void *ret = &buf->data[buf->pos]; 72 | buf->pos += len; 73 | return ret; 74 | } 75 | 76 | /* Returns true on success */ 77 | static bool fuzz_buf_extract_u32(struct fuzz_buf *buf, uint32_t *ret) 78 | { 79 | const void *r = fuzz_buf_extract(buf, sizeof(uint32_t)); 80 | if (!r) { 81 | return false; 82 | } 83 | 84 | uint32_t v; 85 | memcpy(&v, r, sizeof(v)); 86 | *ret = be32toh(v); 87 | return true; 88 | } 89 | 90 | /* Returns true with roughly `percent` chance */ 91 | static bool fuzz_chance(struct fuzz_ctx *ctx, uint8_t percent) 92 | { 93 | assert(percent <= 100); 94 | 95 | const uint8_t *v = fuzz_buf_extract(ctx->ctrl, sizeof(uint8_t)); 96 | if (!v) { 97 | return false; 98 | } 99 | 100 | uint8_t cutoff = (uint32_t)percent * UINT8_MAX / 100; 101 | return *v <= cutoff; 102 | } 103 | 104 | static int fuzz_i2c_tx(const void *buf, size_t len, void *c) 105 | { 106 | struct fuzz_ctx *ctx = c; 107 | (void)buf; 108 | (void)len; 109 | 110 | if (fuzz_chance(ctx, TX_BUSY_CHANCE)) { 111 | return -EBUSY; 112 | } 113 | 114 | return 0; 115 | } 116 | 117 | static void fuzz_i2c_rxmsg(uint8_t src_eid, bool tag_owner, uint8_t msg_tag, 118 | void *c, void *msg, size_t len) 119 | { 120 | struct fuzz_ctx *ctx = c; 121 | (void)ctx; 122 | (void)src_eid; 123 | (void)tag_owner; 124 | (void)msg_tag; 125 | (void)msg; 126 | (void)len; 127 | } 128 | 129 | static void do_rx(struct fuzz_ctx *ctx) 130 | { 131 | uint32_t len; 132 | if (!fuzz_buf_extract_u32(ctx->ctrl, &len)) { 133 | ctx->done = true; 134 | return; 135 | } 136 | 137 | if (len > MAX_RECEIVE) { 138 | ctx->done = true; 139 | return; 140 | } 141 | 142 | const uint8_t *data = fuzz_buf_extract(ctx->input, len); 143 | if (!data) { 144 | ctx->done = true; 145 | return; 146 | } 147 | 148 | mctp_i2c_rx(ctx->i2c, data, len); 149 | } 150 | 151 | static void do_tx(struct fuzz_ctx *ctx) 152 | { 153 | int rc; 154 | 155 | const uint8_t *e = fuzz_buf_extract(ctx->ctrl, sizeof(uint8_t)); 156 | if (!e) { 157 | ctx->done = true; 158 | return; 159 | } 160 | mctp_eid_t eid = *e; 161 | 162 | bool tag_owner = fuzz_chance(ctx, 50); 163 | /* `t` generates the dest eid in owner case, or tag in non-owner case */ 164 | const uint8_t *t = fuzz_buf_extract(ctx->ctrl, sizeof(uint8_t)); 165 | if (!t) { 166 | ctx->done = true; 167 | return; 168 | } 169 | 170 | uint32_t len; 171 | if (!fuzz_buf_extract_u32(ctx->ctrl, &len)) { 172 | ctx->done = true; 173 | return; 174 | } 175 | len = len % (MAX_SEND + 1); 176 | 177 | uint8_t *fake_send_data = __mctp_msg_alloc(len, ctx->mctp); 178 | 179 | mctp_i2c_tx_poll(ctx->i2c); 180 | 181 | if (tag_owner) { 182 | /* Random destination from a small set, reuse `t` */ 183 | mctp_eid_t dest = 10 + (*t % 5); 184 | uint8_t tag; 185 | rc = mctp_message_tx_request(ctx->mctp, dest, fake_send_data, 186 | len, &tag); 187 | if (rc == 0) { 188 | assert((tag & MCTP_HDR_TAG_MASK) == tag); 189 | } 190 | } else { 191 | uint8_t tag = *t % 8; 192 | mctp_message_tx_alloced(ctx->mctp, eid, tag_owner, tag, 193 | fake_send_data, len); 194 | } 195 | } 196 | 197 | static uint64_t fuzz_now(void *c) 198 | { 199 | struct fuzz_ctx *ctx = c; 200 | 201 | uint32_t step = 10; 202 | uint32_t s; 203 | if (fuzz_buf_extract_u32(ctx->ctrl, &s)) { 204 | step = s % (MAX_TIME_STEP + 1); 205 | } 206 | 207 | uint64_t prev = ctx->now; 208 | ctx->now += step; 209 | /* Notice if overflow occurs */ 210 | assert(ctx->now >= prev); 211 | return ctx->now; 212 | } 213 | 214 | int LLVMFuzzerTestOneInput(uint8_t *input, size_t len) 215 | { 216 | /* Split input into two parts. First FUZZCTRL_SIZE (0x400 bytes currently) 217 | * is used for fuzzing control (random choices etc). 218 | * The remainder is a PLDM packet stream, of length:data */ 219 | if (len < FUZZCTRL_SIZE) { 220 | return 0; 221 | } 222 | 223 | struct fuzz_ctx _ctx = { 224 | .ctrl = fuzz_buf_new(input, FUZZCTRL_SIZE), 225 | .input = fuzz_buf_new(&input[FUZZCTRL_SIZE], 226 | len - FUZZCTRL_SIZE), 227 | .now = 0, 228 | .done = false, 229 | }; 230 | struct fuzz_ctx *ctx = &_ctx; 231 | 232 | /* Instantiate the MCTP stack */ 233 | ctx->i2c = malloc(MCTP_SIZEOF_BINDING_I2C); 234 | mctp_i2c_setup(ctx->i2c, OWN_I2C_ADDR, fuzz_i2c_tx, ctx); 235 | ctx->mctp = mctp_init(); 236 | mctp_register_bus(ctx->mctp, mctp_binding_i2c_core(ctx->i2c), OWN_EID); 237 | mctp_set_rx_all(ctx->mctp, fuzz_i2c_rxmsg, ctx); 238 | mctp_set_now_op(ctx->mctp, fuzz_now, ctx); 239 | 240 | while (!ctx->done) { 241 | if (fuzz_chance(ctx, RX_CHANCE)) { 242 | do_rx(ctx); 243 | } else { 244 | do_tx(ctx); 245 | } 246 | } 247 | 248 | mctp_destroy(ctx->mctp); 249 | free(ctx->i2c); 250 | free(ctx->ctrl); 251 | free(ctx->input); 252 | 253 | return 0; 254 | } 255 | 256 | int LLVMFuzzerInitialize(int *argc __unused, char ***argv __unused) 257 | { 258 | return 0; 259 | } 260 | 261 | #ifdef HFND_FUZZING_ENTRY_FUNCTION 262 | #define USING_HONGGFUZZ 1 263 | #else 264 | #define USING_HONGGFUZZ 0 265 | #endif 266 | 267 | #ifdef __AFL_FUZZ_TESTCASE_LEN 268 | #define USING_AFL 1 269 | #else 270 | #define USING_AFL 0 271 | #endif 272 | 273 | #if USING_AFL 274 | __AFL_FUZZ_INIT(); 275 | #endif 276 | 277 | #if !USING_AFL && !USING_HONGGFUZZ 278 | /* Let it build without AFL taking stdin instead */ 279 | static void run_standalone() 280 | { 281 | while (true) { 282 | unsigned char buf[1024000]; 283 | ssize_t len = read(STDIN_FILENO, buf, sizeof(buf)); 284 | if (len <= 0) { 285 | break; 286 | } 287 | LLVMFuzzerTestOneInput(buf, len); 288 | } 289 | } 290 | #endif 291 | 292 | #if !USING_HONGGFUZZ 293 | int main(int argc, char **argv) 294 | { 295 | LLVMFuzzerInitialize(&argc, &argv); 296 | 297 | #if USING_AFL 298 | __AFL_INIT(); 299 | uint8_t *buf = __AFL_FUZZ_TESTCASE_BUF; 300 | 301 | while (__AFL_LOOP(100000)) { 302 | size_t len = __AFL_FUZZ_TESTCASE_LEN; 303 | LLVMFuzzerTestOneInput(buf, len); 304 | } 305 | #else 306 | run_standalone(); 307 | #endif 308 | 309 | return 0; 310 | } 311 | #endif // !USING_HONGGFUZZ 312 | -------------------------------------------------------------------------------- /tests/test_i2c.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #if HAVE_CONFIG_H 4 | #include "config.h" 5 | #endif 6 | 7 | #include "compiler.h" 8 | #include "range.h" 9 | #include "libmctp-log.h" 10 | #include "libmctp-i2c.h" 11 | #include "libmctp-sizes.h" 12 | #include "libmctp-alloc.h" 13 | 14 | /* For access to mctp_bninding_i2c internals */ 15 | #include "i2c-internal.h" 16 | 17 | #ifdef NDEBUG 18 | #undef NDEBUG 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | struct mctp_binding_serial_pipe { 31 | int ingress; 32 | int egress; 33 | 34 | struct mctp_binding_serial *serial; 35 | }; 36 | 37 | // Sized to test fragmentation and >8 bit length 38 | #define TEST_MSG_LEN 300 39 | static uint8_t mctp_msg_src[TEST_MSG_LEN]; 40 | 41 | struct i2c_test { 42 | struct mctp_binding_i2c *i2c; 43 | struct mctp *mctp; 44 | 45 | uint8_t rx_msg[TEST_MSG_LEN]; 46 | size_t rx_len; 47 | 48 | /* Physical addresses. These get set regardless of whether the packet 49 | * is dropped by the stack (no match etc) */ 50 | uint8_t last_rx_i2c_src; 51 | uint8_t last_tx_i2c_dst; 52 | }; 53 | 54 | static const uint8_t I2C_ADDR_A = 0x20; 55 | static const uint8_t I2C_ADDR_B = 0x21; 56 | static const uint8_t EID_A = 50; 57 | static const uint8_t EID_B = 51; 58 | 59 | static int test_i2c_tx(const void *buf, size_t len, void *ctx) 60 | { 61 | struct i2c_test *test_pair = ctx; 62 | struct i2c_test *tx_test = &test_pair[0]; 63 | struct i2c_test *rx_test = &test_pair[1]; 64 | 65 | mctp_prdebug("test_i2c_tx len %zu", len); 66 | 67 | const struct mctp_i2c_hdr *hdr = buf; 68 | tx_test->last_tx_i2c_dst = hdr->dest >> 1; 69 | rx_test->last_rx_i2c_src = hdr->source >> 1; 70 | 71 | mctp_i2c_rx(rx_test->i2c, buf, len); 72 | return 0; 73 | } 74 | 75 | static void test_i2c_rxmsg(uint8_t src_eid, bool tag_owner, uint8_t msg_tag, 76 | void *ctx, void *msg, size_t len) 77 | { 78 | struct i2c_test *test_pair = ctx; 79 | // struct i2c_test *tx_test = &test_pair[0]; 80 | struct i2c_test *rx_test = &test_pair[1]; 81 | 82 | mctp_prdebug("test_i2c_rx src %d len %zu tag %d owner %d", src_eid, len, 83 | msg_tag, tag_owner); 84 | 85 | // Must be cleared by previous test runs 86 | assert(rx_test->rx_len == 0); 87 | memcpy(rx_test->rx_msg, msg, len); 88 | rx_test->rx_len = len; 89 | } 90 | 91 | /* Transmits a MCTP message and checks the received message matches */ 92 | static void run_tx_test(struct i2c_test *tx_test, uint8_t dest_eid, 93 | size_t tx_len, struct i2c_test *rx_test) 94 | { 95 | int rc; 96 | const uint8_t msg_tag = 2; 97 | const bool tag_owner = false; 98 | 99 | assert(tx_len <= sizeof(mctp_msg_src)); 100 | rc = mctp_message_tx(tx_test->mctp, dest_eid, tag_owner, msg_tag, 101 | mctp_msg_src, tx_len); 102 | assert(rc == 0); 103 | 104 | while (!mctp_is_tx_ready(tx_test->mctp, dest_eid)) { 105 | mctp_i2c_tx_poll(tx_test->i2c); 106 | } 107 | 108 | assert(rx_test->rx_len == tx_len); 109 | assert(memcmp(rx_test->rx_msg, mctp_msg_src, tx_len) == 0); 110 | 111 | rx_test->rx_len = 0; 112 | } 113 | 114 | static void test_neigh_expiry(struct i2c_test *tx_test, 115 | struct i2c_test *rx_test) 116 | { 117 | const uint8_t msg_tag = 2; 118 | const bool tag_owner = true; 119 | const size_t msg_len = 5; 120 | int rc; 121 | 122 | (void)rx_test; 123 | 124 | /* Clear the tx neighbour table */ 125 | memset(tx_test->i2c->neigh, 0x0, sizeof(tx_test->i2c->neigh)); 126 | 127 | /* Check that all EIDs fail */ 128 | rx_test->rx_len = 0; 129 | for (size_t eid = 8; eid < 254; eid++) { 130 | mctp_message_tx(tx_test->mctp, eid, tag_owner, msg_tag, 131 | mctp_msg_src, msg_len); 132 | /* Not received */ 133 | assert(rx_test->rx_len == 0); 134 | } 135 | 136 | /* Add one entry */ 137 | rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B, 138 | rx_test->i2c->own_addr); 139 | assert(rc == 0); 140 | rx_test->rx_len = 0; 141 | mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src, 142 | msg_len); 143 | assert(rx_test->rx_len == msg_len); 144 | assert(tx_test->last_tx_i2c_dst == rx_test->i2c->own_addr); 145 | 146 | /* Replace the entry */ 147 | rx_test->i2c->own_addr++; 148 | rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B, 149 | rx_test->i2c->own_addr); 150 | assert(rc == 0); 151 | rx_test->rx_len = 0; 152 | mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src, 153 | msg_len); 154 | assert(rc == 0); 155 | assert(rx_test->rx_len == msg_len); 156 | assert(tx_test->last_tx_i2c_dst == rx_test->i2c->own_addr); 157 | 158 | /* Check only one entry is set */ 159 | size_t count = 0; 160 | for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) { 161 | struct mctp_i2c_neigh *n = &tx_test->i2c->neigh[i]; 162 | if (n->used) { 163 | assert(n->eid == EID_B); 164 | count++; 165 | } 166 | } 167 | assert(count == 1); 168 | 169 | /* Ensure we can iterate without overflow. 170 | * If MCTP_I2C_NEIGH_COUNT increases too large this test would need rethinking 171 | * (and eviction may become impossible) */ 172 | assert((int)EID_B + MCTP_I2C_NEIGH_COUNT < 254); 173 | assert((int)I2C_ADDR_B + MCTP_I2C_NEIGH_COUNT < 0x7f); 174 | 175 | /* Fill entries. -1 because one was already filled. */ 176 | for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT - 1; i++) { 177 | /* Unused addresses */ 178 | uint8_t addr = rx_test->i2c->own_addr + i + 1; 179 | uint8_t eid = EID_B + i + 1; 180 | rc = mctp_i2c_set_neighbour(tx_test->i2c, eid, addr); 181 | assert(rc == 0); 182 | } 183 | 184 | /* Check all are used */ 185 | for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) { 186 | struct mctp_i2c_neigh *n = &tx_test->i2c->neigh[i]; 187 | assert(n->used); 188 | } 189 | 190 | /* Test eviction */ 191 | { 192 | uint8_t addr = 193 | rx_test->i2c->own_addr + MCTP_I2C_NEIGH_COUNT + 1; 194 | uint8_t eid = EID_B + MCTP_I2C_NEIGH_COUNT + 1; 195 | rc = mctp_i2c_set_neighbour(tx_test->i2c, eid, addr); 196 | assert(rc == 0); 197 | 198 | /* EID_B got evicted, send should fail */ 199 | rx_test->rx_len = 0; 200 | mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, 201 | mctp_msg_src, msg_len); 202 | /* Not received */ 203 | assert(rx_test->rx_len == 0); 204 | } 205 | 206 | /* Add EID_B again */ 207 | rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B, 208 | rx_test->i2c->own_addr); 209 | assert(rc == 0); 210 | rx_test->rx_len = 0; 211 | mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src, 212 | msg_len); 213 | /* Is received */ 214 | assert(rx_test->rx_len == msg_len); 215 | } 216 | 217 | int main(void) 218 | { 219 | struct i2c_test scenario[2]; 220 | struct i2c_test *tx_test = &scenario[0]; 221 | struct i2c_test *rx_test = &scenario[1]; 222 | 223 | mctp_set_log_stdio(MCTP_LOG_DEBUG); 224 | 225 | memset(scenario, 0x0, sizeof(scenario)); 226 | 227 | /* Setup a source buffer */ 228 | for (size_t i = 0; i < sizeof(mctp_msg_src); i++) { 229 | mctp_msg_src[i] = i & 0xff; 230 | } 231 | 232 | tx_test->mctp = mctp_init(); 233 | assert(tx_test->mctp); 234 | tx_test->i2c = malloc(MCTP_SIZEOF_BINDING_I2C); 235 | assert(tx_test->i2c); 236 | rx_test->mctp = mctp_init(); 237 | assert(rx_test->mctp); 238 | rx_test->i2c = malloc(MCTP_SIZEOF_BINDING_I2C); 239 | assert(rx_test->i2c); 240 | 241 | /* TX side */ 242 | mctp_i2c_setup(tx_test->i2c, I2C_ADDR_A, test_i2c_tx, scenario); 243 | mctp_register_bus(tx_test->mctp, mctp_binding_i2c_core(tx_test->i2c), 244 | EID_A); 245 | mctp_set_rx_all(tx_test->mctp, NULL, NULL); 246 | mctp_i2c_set_neighbour(tx_test->i2c, EID_B, I2C_ADDR_B); 247 | 248 | /* RX side */ 249 | mctp_i2c_setup(rx_test->i2c, I2C_ADDR_B, NULL, NULL); 250 | mctp_register_bus(rx_test->mctp, mctp_binding_i2c_core(rx_test->i2c), 251 | EID_B); 252 | mctp_set_rx_all(rx_test->mctp, test_i2c_rxmsg, scenario); 253 | // mctp_i2c_set_neighbour(rx_test->i2c, EID_A, I2C_ADDR_A); 254 | 255 | /* Try all message sizes */ 256 | for (size_t i = 1; i < sizeof(mctp_msg_src); i++) { 257 | run_tx_test(tx_test, EID_B, i, rx_test); 258 | } 259 | 260 | test_neigh_expiry(tx_test, rx_test); 261 | 262 | free(tx_test->i2c); 263 | free(rx_test->i2c); 264 | mctp_destroy(tx_test->mctp); 265 | mctp_destroy(rx_test->mctp); 266 | 267 | return 0; 268 | } 269 | -------------------------------------------------------------------------------- /control.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "libmctp-cmds.h" 7 | #include "libmctp-alloc.h" 8 | #include "libmctp-log.h" 9 | #include "core-internal.h" 10 | 11 | #include "control.h" 12 | 13 | static void fill_resp(const void *req, struct mctp_ctrl_msg_hdr *hdr) 14 | { 15 | const struct mctp_ctrl_msg_hdr *req_hdr = req; 16 | hdr->ic_msg_type = MCTP_CTRL_HDR_MSG_TYPE; 17 | hdr->rq_dgram_inst = req_hdr->rq_dgram_inst & 18 | MCTP_CTRL_HDR_INSTANCE_ID_MASK; 19 | hdr->command_code = req_hdr->command_code; 20 | } 21 | 22 | static uint8_t mctp_ctrl_set_endpoint_id(struct mctp_bus *bus, uint8_t src_eid, 23 | uint8_t msg_tag, const void *data, 24 | size_t len) 25 | { 26 | if (len != sizeof(struct mctp_ctrl_cmd_set_endpoint_id_req)) { 27 | return MCTP_CTRL_CC_ERROR_INVALID_LENGTH; 28 | } 29 | const struct mctp_ctrl_cmd_set_endpoint_id_req *req = data; 30 | 31 | uint8_t op = req->operation & MCTP_CTRL_SET_EID_OP_MASK; 32 | if (!(op == MCTP_CTRL_SET_EID_OP_SET || 33 | op == MCTP_CTRL_SET_EID_OP_FORCE)) { 34 | return MCTP_CTRL_CC_ERROR_INVALID_DATA; 35 | } 36 | 37 | if (mctp_bus_set_eid(bus->binding, req->eid)) { 38 | return MCTP_CTRL_CC_ERROR_INVALID_DATA; 39 | } 40 | 41 | struct mctp_ctrl_cmd_set_endpoint_id_resp *resp = 42 | __mctp_msg_alloc(sizeof(*resp), bus->mctp); 43 | if (!resp) { 44 | mctp_prdebug("no response buffer"); 45 | return MCTP_CTRL_CC_ERROR; 46 | } 47 | memset(resp, 0x00, sizeof(*resp)); 48 | fill_resp(data, &resp->hdr); 49 | resp->completion_code = MCTP_CTRL_CC_SUCCESS; 50 | resp->status = MCTP_CTRL_SET_EID_STATUS_ACCEPTED; 51 | resp->eid = req->eid; 52 | resp->pool_size = 0; 53 | 54 | int rc = mctp_message_tx_alloced(bus->mctp, src_eid, false, msg_tag, 55 | resp, sizeof(*resp)); 56 | if (!rc) { 57 | mctp_prdebug("set_endpoint_id response send failed: %d", rc); 58 | } 59 | return MCTP_CTRL_CC_SUCCESS; 60 | } 61 | 62 | static uint8_t mctp_ctrl_get_endpoint_id(struct mctp_bus *bus, uint8_t src_eid, 63 | uint8_t msg_tag, const void *data, 64 | size_t len) 65 | { 66 | if (len != sizeof(struct mctp_ctrl_msg_hdr)) { 67 | /* Expect empty request */ 68 | return MCTP_CTRL_CC_ERROR_INVALID_LENGTH; 69 | } 70 | (void)data; 71 | 72 | struct mctp_ctrl_cmd_get_endpoint_id_resp *resp = 73 | __mctp_msg_alloc(sizeof(*resp), bus->mctp); 74 | if (!resp) { 75 | mctp_prdebug("no response buffer"); 76 | return MCTP_CTRL_CC_ERROR; 77 | } 78 | memset(resp, 0x00, sizeof(*resp)); 79 | fill_resp(data, &resp->hdr); 80 | resp->completion_code = MCTP_CTRL_CC_SUCCESS; 81 | resp->endpoint_id = bus->eid; 82 | resp->endpoint_type = MCTP_CTRL_ENDPOINT_TYPE_SIMPLE | 83 | MCTP_CTRL_ENDPOINT_ID_TYPE_STATIC; 84 | resp->medium_specific = 0x00; 85 | 86 | int rc = mctp_message_tx_alloced(bus->mctp, src_eid, false, msg_tag, 87 | resp, sizeof(*resp)); 88 | if (!rc) { 89 | mctp_prdebug("get_endpoint_id response send failed: %d", rc); 90 | } 91 | return MCTP_CTRL_CC_SUCCESS; 92 | } 93 | 94 | #define MCTP_PROTOCOL_COUNT 4 95 | /* Big endian */ 96 | const uint8_t MCTP_PROTOCOL_VERSIONS[MCTP_PROTOCOL_COUNT * 4] = { 97 | // 1.0 98 | 0xf1, 99 | 0xf0, 100 | 0xff, 101 | 0x00, 102 | // 1.1 103 | 0xf1, 104 | 0xf1, 105 | 0xff, 106 | 0x00, 107 | // 1.2 108 | 0xf1, 109 | 0xf2, 110 | 0xff, 111 | 0x00, 112 | // 1.3.3 113 | 0xf1, 114 | 0xf3, 115 | 0xf3, 116 | 0x00, 117 | }; 118 | 119 | static uint8_t mctp_ctrl_get_version(struct mctp_bus *bus, uint8_t src_eid, 120 | uint8_t msg_tag, const void *data, 121 | size_t len) 122 | { 123 | if (len != sizeof(struct mctp_ctrl_cmd_get_version_req)) { 124 | return MCTP_CTRL_CC_ERROR_INVALID_LENGTH; 125 | } 126 | const struct mctp_ctrl_cmd_get_version_req *req = data; 127 | 128 | switch (req->msg_type) { 129 | case 0x00: 130 | case 0xff: 131 | /* Only have versions for MCTP base or control */ 132 | break; 133 | default: 134 | return MCTP_CTRL_VERSIONS_NOT_SUPPORTED; 135 | } 136 | 137 | /* Return only the versions for MCTP */ 138 | size_t total_sz = sizeof(struct mctp_ctrl_cmd_get_version_resp) + 139 | sizeof(MCTP_PROTOCOL_VERSIONS); 140 | 141 | struct mctp_ctrl_cmd_get_version_resp *resp = 142 | __mctp_msg_alloc(total_sz, bus->mctp); 143 | if (!resp) { 144 | mctp_prdebug("no response buffer"); 145 | return MCTP_CTRL_CC_ERROR; 146 | } 147 | memset(resp, 0x00, total_sz); 148 | fill_resp(data, &resp->hdr); 149 | resp->completion_code = MCTP_CTRL_CC_SUCCESS; 150 | resp->version_count = MCTP_PROTOCOL_COUNT; 151 | memcpy(resp->versions, MCTP_PROTOCOL_VERSIONS, 152 | sizeof(MCTP_PROTOCOL_VERSIONS)); 153 | 154 | int rc = mctp_message_tx_alloced(bus->mctp, src_eid, false, msg_tag, 155 | resp, total_sz); 156 | if (!rc) { 157 | mctp_prdebug("mctp get_version response send failed: %d", rc); 158 | } 159 | return MCTP_CTRL_CC_SUCCESS; 160 | } 161 | 162 | static uint8_t mctp_ctrl_get_types(struct mctp_bus *bus, uint8_t src_eid, 163 | uint8_t msg_tag, const void *data, 164 | size_t len) 165 | { 166 | if (len != sizeof(struct mctp_ctrl_msg_hdr)) { 167 | return MCTP_CTRL_CC_ERROR_INVALID_LENGTH; 168 | } 169 | (void)data; 170 | 171 | size_t total_sz = sizeof(struct mctp_ctrl_cmd_get_types_resp) + 172 | bus->mctp->control.num_msg_types; 173 | 174 | struct mctp_ctrl_cmd_get_types_resp *resp = 175 | __mctp_msg_alloc(total_sz, bus->mctp); 176 | if (!resp) { 177 | mctp_prdebug("no response buffer"); 178 | return MCTP_CTRL_CC_ERROR; 179 | } 180 | memset(resp, 0x00, total_sz); 181 | fill_resp(data, &resp->hdr); 182 | resp->completion_code = MCTP_CTRL_CC_SUCCESS; 183 | resp->type_count = bus->mctp->control.num_msg_types; 184 | memcpy(resp->types, bus->mctp->control.msg_types, 185 | bus->mctp->control.num_msg_types); 186 | 187 | int rc = mctp_message_tx_alloced(bus->mctp, src_eid, false, msg_tag, 188 | resp, total_sz); 189 | if (!rc) { 190 | mctp_prdebug("mctp get_types response send failed: %d", rc); 191 | } 192 | return MCTP_CTRL_CC_SUCCESS; 193 | } 194 | 195 | static void reply_error(struct mctp *mctp, uint8_t src_eid, uint8_t msg_tag, 196 | const struct mctp_ctrl_msg_hdr *ctrl_hdr, uint8_t ccode) 197 | { 198 | struct mctp_ctrl_cmd_empty_resp *resp = 199 | __mctp_msg_alloc(sizeof(*resp), mctp); 200 | if (!resp) { 201 | mctp_prdebug("no response buffer"); 202 | return; 203 | } 204 | memset(resp, 0x00, sizeof(*resp)); 205 | fill_resp(ctrl_hdr, &resp->hdr); 206 | resp->completion_code = ccode; 207 | 208 | int rc = mctp_message_tx_alloced(mctp, src_eid, false, msg_tag, resp, 209 | sizeof(*resp)); 210 | if (!rc) { 211 | mctp_prdebug("error response send failed: %d", rc); 212 | } 213 | } 214 | 215 | /* Control message request handler. This will respond to the mandatory MCTP control 216 | * commands */ 217 | bool mctp_control_handler(struct mctp_bus *bus, mctp_eid_t src_eid, 218 | bool tag_owner, uint8_t msg_tag, const void *data, 219 | size_t len) 220 | { 221 | if (!tag_owner) { 222 | // Not a request 223 | return false; 224 | } 225 | 226 | if (len < 1) { 227 | // No type byte 228 | return false; 229 | } 230 | 231 | const struct mctp_ctrl_msg_hdr *ctrl_hdr = data; 232 | if (ctrl_hdr->ic_msg_type != MCTP_CTRL_HDR_MSG_TYPE) { 233 | // Not Control type 234 | return false; 235 | } 236 | 237 | if (len < sizeof(struct mctp_ctrl_msg_hdr)) { 238 | // Drop short messages, but treat as handled 239 | return true; 240 | } 241 | 242 | if ((ctrl_hdr->rq_dgram_inst & 243 | (MCTP_CTRL_HDR_FLAG_REQUEST | MCTP_CTRL_HDR_FLAG_DGRAM)) != 244 | MCTP_CTRL_HDR_FLAG_REQUEST) { 245 | // Drop message, isn't a request. 246 | // Treat as handled since TO bit was set. 247 | return true; 248 | } 249 | 250 | // A valid MCTP Control request has been received, process it 251 | 252 | uint8_t cc = MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD; 253 | switch (ctrl_hdr->command_code) { 254 | case MCTP_CTRL_CMD_SET_ENDPOINT_ID: 255 | cc = mctp_ctrl_set_endpoint_id(bus, src_eid, msg_tag, data, 256 | len); 257 | break; 258 | case MCTP_CTRL_CMD_GET_ENDPOINT_ID: 259 | cc = mctp_ctrl_get_endpoint_id(bus, src_eid, msg_tag, data, 260 | len); 261 | break; 262 | case MCTP_CTRL_CMD_GET_VERSION_SUPPORT: 263 | cc = mctp_ctrl_get_version(bus, src_eid, msg_tag, data, len); 264 | break; 265 | case MCTP_CTRL_CMD_GET_MESSAGE_TYPE_SUPPORT: 266 | cc = mctp_ctrl_get_types(bus, src_eid, msg_tag, data, len); 267 | break; 268 | default: 269 | cc = MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD; 270 | break; 271 | } 272 | 273 | if (cc) { 274 | reply_error(bus->mctp, src_eid, msg_tag, ctrl_hdr, cc); 275 | } 276 | 277 | // No further handling required. 278 | return true; 279 | } 280 | 281 | int mctp_control_add_type(struct mctp *mctp, uint8_t msg_type) 282 | { 283 | /* Check for existing */ 284 | for (size_t i = 0; i < mctp->control.num_msg_types; i++) { 285 | if (mctp->control.msg_types[i] == msg_type) { 286 | return 0; 287 | } 288 | } 289 | 290 | if (mctp->control.num_msg_types == MCTP_CONTROL_MAX_TYPES) { 291 | return -ENOSPC; 292 | } 293 | 294 | mctp->control.msg_types[mctp->control.num_msg_types] = msg_type; 295 | mctp->control.num_msg_types++; 296 | return 0; 297 | } 298 | 299 | void mctp_control_remove_type(struct mctp *mctp, uint8_t msg_type) 300 | { 301 | for (size_t i = 0; i < mctp->control.num_msg_types; i++) { 302 | if (mctp->control.msg_types[i] == msg_type) { 303 | memmove(&mctp->control.msg_types[i], 304 | &mctp->control.msg_types[i + 1], 305 | mctp->control.num_msg_types - (i + 1)); 306 | mctp->control.num_msg_types--; 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /libmctp.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #ifndef _LIBMCTP_H 4 | #define _LIBMCTP_H 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | typedef uint8_t mctp_eid_t; 17 | 18 | /* Special Endpoint ID values */ 19 | #define MCTP_EID_NULL 0 20 | #define MCTP_EID_BROADCAST 0xff 21 | 22 | /* MCTP packet definitions */ 23 | struct mctp_hdr { 24 | uint8_t ver; 25 | uint8_t dest; 26 | uint8_t src; 27 | uint8_t flags_seq_tag; 28 | }; 29 | 30 | /* Definitions for flags_seq_tag field */ 31 | #define MCTP_HDR_FLAG_SOM (1 << 7) 32 | #define MCTP_HDR_FLAG_EOM (1 << 6) 33 | #define MCTP_HDR_FLAG_TO (1 << 3) 34 | #define MCTP_HDR_TO_SHIFT (3) 35 | #define MCTP_HDR_TO_MASK (1) 36 | #define MCTP_HDR_SEQ_SHIFT (4) 37 | #define MCTP_HDR_SEQ_MASK (0x3) 38 | #define MCTP_HDR_TAG_SHIFT (0) 39 | #define MCTP_HDR_TAG_MASK (0x7) 40 | 41 | #define MCTP_MESSAGE_TO_SRC true 42 | #define MCTP_MESSAGE_TO_DST false 43 | #define MCTP_MESSAGE_CAPTURE_OUTGOING true 44 | #define MCTP_MESSAGE_CAPTURE_INCOMING false 45 | 46 | /* Baseline Transmission Unit and packet size */ 47 | #define MCTP_BTU 64 48 | #define MCTP_PACKET_SIZE(unit) ((unit) + sizeof(struct mctp_hdr)) 49 | #define MCTP_BODY_SIZE(unit) ((unit) - sizeof(struct mctp_hdr)) 50 | 51 | /* packet buffers */ 52 | 53 | struct mctp_pktbuf { 54 | size_t start, end, size; 55 | size_t mctp_hdr_off; 56 | bool alloc; 57 | unsigned char data[]; 58 | }; 59 | 60 | #define MCTP_PKTBUF_SIZE(payload) \ 61 | (MCTP_PACKET_SIZE(payload) + sizeof(struct mctp_pktbuf)) 62 | #define PKTBUF_STORAGE_ALIGN __attribute((aligned(alignof(struct mctp_pktbuf)))) 63 | 64 | struct mctp; 65 | struct mctp_bus; 66 | struct mctp_binding; 67 | 68 | /* Initialise a mctp_pktbuf in static storage. Should not be freed. 69 | * Storage must be sized to fit the binding, 70 | * MCTP_PKTBUF_SIZE(binding->pkt_size + binding->pkt_header + binding->pkt_trailer). 71 | * storage must be aligned to alignof(struct mctp_pktbuf), 72 | * use PKTBUF_STORAGE_ALIGN macro */ 73 | struct mctp_pktbuf *mctp_pktbuf_init(struct mctp_binding *binding, 74 | void *storage); 75 | /* Allocate and initialise a mctp_pktbuf. Should be freed with 76 | * mctp_pktbuf_free */ 77 | struct mctp_pktbuf *mctp_pktbuf_alloc(struct mctp_binding *binding, size_t len); 78 | void mctp_pktbuf_free(struct mctp_pktbuf *pkt); 79 | struct mctp_hdr *mctp_pktbuf_hdr(struct mctp_pktbuf *pkt); 80 | void *mctp_pktbuf_data(struct mctp_pktbuf *pkt); 81 | size_t mctp_pktbuf_size(const struct mctp_pktbuf *pkt); 82 | void *mctp_pktbuf_alloc_start(struct mctp_pktbuf *pkt, size_t size); 83 | void *mctp_pktbuf_alloc_end(struct mctp_pktbuf *pkt, size_t size); 84 | int mctp_pktbuf_push(struct mctp_pktbuf *pkt, const void *data, size_t len); 85 | void *mctp_pktbuf_pop(struct mctp_pktbuf *pkt, size_t len); 86 | 87 | /* MCTP core */ 88 | 89 | /* Allocate and setup a MCTP instance */ 90 | struct mctp *mctp_init(void); 91 | /* Cleanup and deallocate a MCTP instance from mctp_init() */ 92 | void mctp_destroy(struct mctp *mctp); 93 | 94 | /* Setup a MCTP instance */ 95 | int mctp_setup(struct mctp *mctp, size_t struct_mctp_size); 96 | /* Release resource of a MCTP instance */ 97 | void mctp_cleanup(struct mctp *mctp); 98 | 99 | void mctp_set_max_message_size(struct mctp *mctp, size_t message_size); 100 | typedef void (*mctp_capture_fn)(struct mctp_pktbuf *pkt, bool outgoing, 101 | void *user); 102 | void mctp_set_capture_handler(struct mctp *mctp, mctp_capture_fn fn, 103 | void *user); 104 | 105 | /* Register a binding to the MCTP core, and creates a bus (populating 106 | * binding->bus). 107 | * 108 | * If this function is called, the MCTP stack is initialised as an 'endpoint', 109 | * and will deliver local packets to a RX callback - see `mctp_set_rx_all()` 110 | * below. 111 | */ 112 | int mctp_register_bus(struct mctp *mctp, struct mctp_binding *binding, 113 | mctp_eid_t eid); 114 | 115 | void mctp_unregister_bus(struct mctp *mctp, struct mctp_binding *binding); 116 | 117 | int mctp_bus_set_eid(struct mctp_binding *binding, mctp_eid_t eid); 118 | 119 | /* Create a simple bidirectional bridge between busses. 120 | * 121 | * In this mode, the MCTP stack is initialised as a bridge. There is no EID 122 | * defined, so no packets are considered local. Instead, all messages from one 123 | * binding are forwarded to the other. 124 | */ 125 | int mctp_bridge_busses(struct mctp *mctp, struct mctp_binding *b1, 126 | struct mctp_binding *b2); 127 | 128 | typedef void (*mctp_rx_fn)(uint8_t src_eid, bool tag_owner, uint8_t msg_tag, 129 | void *data, void *msg, size_t len); 130 | 131 | int mctp_set_rx_all(struct mctp *mctp, mctp_rx_fn fn, void *data); 132 | 133 | /* Transmit a message. 134 | * @msg: The message buffer to send. Must be suitable for 135 | * free(), or the custom mctp_set_alloc_ops() m_msg_free. 136 | * The mctp stack will take ownership of the buffer 137 | * and release it when message transmission is complete or fails. 138 | * 139 | * If an asynchronous binding is being used, it will return -EBUSY if 140 | * a message is already pending for transmission (msg will be freed as usual). 141 | * Asynchronous users can test mctp_is_tx_ready() prior to sending. 142 | */ 143 | int mctp_message_tx_alloced(struct mctp *mctp, mctp_eid_t eid, bool tag_owner, 144 | uint8_t msg_tag, void *msg, size_t msg_len); 145 | 146 | /* Transmit a message. 147 | * @msg: The message buffer to send. Ownership of this buffer 148 | * remains with the caller (a copy is made internally with __mctp_msg_alloc). 149 | * 150 | * If an asynchronous binding is being used, it will return -EBUSY if 151 | * a message is already pending for transmission. 152 | * Asynchronous users can test mctp_is_tx_ready() prior to sending. 153 | * 154 | * This is equivalent to duplicating `msg` then calling mctp_message_tx_alloc(). 155 | */ 156 | int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid, bool tag_owner, 157 | uint8_t msg_tag, const void *msg, size_t msg_len); 158 | 159 | /* Transmit a request message. 160 | * @msg: The message buffer to send. Must be suitable for 161 | * free(), or the custom mctp_set_alloc_ops() m_msg_free. 162 | * 163 | * A tag with Tag Owner bit set will allocated for the sent message, 164 | * and returned to the caller (TO bit is unset in the returned @alloc_msg_tag). 165 | * alloc_msg_tag may be NULL to ignore the returned tag. 166 | * If no tags are spare -EBUSY will be returned. 167 | * 168 | * If an asynchronous binding is being used, it will return -EBUSY if 169 | * a message is already pending for transmission (msg will be freed). 170 | * Asynchronous users can test mctp_is_tx_ready() prior to sending. 171 | */ 172 | int mctp_message_tx_request(struct mctp *mctp, mctp_eid_t eid, void *msg, 173 | size_t msg_len, uint8_t *alloc_msg_tag); 174 | 175 | bool mctp_is_tx_ready(struct mctp *mctp, mctp_eid_t eid); 176 | 177 | /* hardware bindings */ 178 | 179 | /** 180 | * @tx: Binding function to transmit one packet on the interface 181 | * @tx_storage: A buffer for transmitting packets. Must be sized 182 | * as MCTP_PKTBUF_SIZE(mtu) and 8 byte aligned. 183 | * Return: 184 | * * 0 - Success, pktbuf can be released 185 | * * -EMSGSIZE - Packet exceeds binding MTU, pktbuf must be dropped 186 | * * -EBUSY - Packet unable to be transmitted, pktbuf must be retained 187 | */ 188 | struct mctp_binding { 189 | const char *name; 190 | uint8_t version; 191 | struct mctp_bus *bus; 192 | struct mctp *mctp; 193 | size_t pkt_size; 194 | size_t pkt_header; 195 | size_t pkt_trailer; 196 | void *tx_storage; 197 | int (*start)(struct mctp_binding *binding); 198 | int (*tx)(struct mctp_binding *binding, struct mctp_pktbuf *pkt); 199 | mctp_rx_fn control_rx; 200 | void *control_rx_data; 201 | }; 202 | 203 | void mctp_binding_set_tx_enabled(struct mctp_binding *binding, bool enable); 204 | 205 | /* 206 | * Receive a packet from binding to core. Takes ownership of pkt, free()-ing it 207 | * after use. 208 | */ 209 | void mctp_bus_rx(struct mctp_binding *binding, struct mctp_pktbuf *pkt); 210 | 211 | /* environment-specific allocation */ 212 | void mctp_set_alloc_ops(void *(*m_alloc)(size_t), void (*m_free)(void *), 213 | void *(*m_msg_alloc)(size_t, void *), 214 | void (*m_msg_free)(void *, void *)); 215 | /* Gets/sets context that will be passed to custom m_msg_ ops */ 216 | void *mctp_get_alloc_ctx(struct mctp *mctp); 217 | void mctp_set_alloc_ctx(struct mctp *mctp, void *ctx); 218 | 219 | /* environment-specific logging */ 220 | 221 | void mctp_set_log_stdio(int level); 222 | void mctp_set_log_syslog(void); 223 | void mctp_set_log_custom(void (*fn)(int, const char *, va_list)); 224 | 225 | /* these should match the syslog-standard LOG_* definitions, for 226 | * easier use with syslog */ 227 | #define MCTP_LOG_ERR 3 228 | #define MCTP_LOG_WARNING 4 229 | #define MCTP_LOG_NOTICE 5 230 | #define MCTP_LOG_INFO 6 231 | #define MCTP_LOG_DEBUG 7 232 | 233 | /* Environment-specific time functionality */ 234 | /* The `now` callback returns a timestamp in milliseconds. 235 | * Timestamps should be monotonically increasing, and can have an arbitrary 236 | * origin. (As long as returned timestamps aren't too close to UINT64_MAX, not 237 | * a problem forany reasonable implementation). */ 238 | void mctp_set_now_op(struct mctp *mctp, uint64_t (*now)(void *), void *ctx); 239 | /* Returns a timestamp in milliseconds */ 240 | uint64_t mctp_now(struct mctp *mctp); 241 | 242 | int mctp_control_handler_enable(struct mctp *mctp); 243 | void mctp_control_handler_disable(struct mctp *mctp); 244 | 245 | /* Add/remove message types to be reported by Get MCTP Version Support. 246 | * Control type is added automatically for the control handler */ 247 | int mctp_control_add_type(struct mctp *mctp, uint8_t msg_type); 248 | void mctp_control_remove_type(struct mctp *mctp, uint8_t msg_type); 249 | 250 | #ifdef __cplusplus 251 | } 252 | #endif 253 | 254 | #endif /* _LIBMCTP_H */ 255 | -------------------------------------------------------------------------------- /serial.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "crc-16-ccitt.h" 10 | 11 | #ifdef HAVE_CONFIG_H 12 | #include "config.h" 13 | #endif 14 | 15 | #ifdef MCTP_HAVE_FILEIO 16 | #include 17 | #include 18 | #include 19 | #else 20 | static const size_t write(int fd, const void *buf, size_t len) 21 | { 22 | return -1; 23 | } 24 | #endif 25 | 26 | #define pr_fmt(x) "serial: " x 27 | 28 | #define SERIAL_BTU MCTP_BTU 29 | 30 | #include "libmctp.h" 31 | #include "libmctp-alloc.h" 32 | #include "libmctp-log.h" 33 | #include "libmctp-serial.h" 34 | #include "container_of.h" 35 | 36 | struct mctp_binding_serial { 37 | struct mctp_binding binding; 38 | int fd; 39 | unsigned long bus_id; 40 | 41 | mctp_serial_tx_fn tx_fn; 42 | void *tx_fn_data; 43 | 44 | /* receive buffer and state */ 45 | uint8_t rxbuf[1024]; 46 | struct mctp_pktbuf *rx_pkt; 47 | uint8_t rx_storage[MCTP_PKTBUF_SIZE(SERIAL_BTU)] PKTBUF_STORAGE_ALIGN; 48 | uint8_t rx_exp_len; 49 | uint16_t rx_fcs; 50 | uint16_t rx_fcs_calc; 51 | enum { 52 | STATE_WAIT_SYNC_START, 53 | STATE_WAIT_REVISION, 54 | STATE_WAIT_LEN, 55 | STATE_DATA, 56 | STATE_DATA_ESCAPED, 57 | STATE_WAIT_FCS1, 58 | STATE_WAIT_FCS2, 59 | STATE_WAIT_SYNC_END, 60 | } rx_state; 61 | 62 | /* temporary transmit buffer */ 63 | uint8_t txbuf[256]; 64 | /* used by the MCTP stack */ 65 | uint8_t tx_storage[MCTP_PKTBUF_SIZE(SERIAL_BTU)] PKTBUF_STORAGE_ALIGN; 66 | }; 67 | 68 | #define binding_to_serial(b) \ 69 | container_of(b, struct mctp_binding_serial, binding) 70 | 71 | #define MCTP_SERIAL_REVISION 0x01 72 | #define MCTP_SERIAL_FRAMING_FLAG 0x7e 73 | #define MCTP_SERIAL_ESCAPE 0x7d 74 | 75 | struct mctp_serial_header { 76 | uint8_t flag; 77 | uint8_t revision; 78 | uint8_t len; 79 | }; 80 | 81 | struct mctp_serial_trailer { 82 | uint8_t fcs_msb; 83 | uint8_t fcs_lsb; 84 | uint8_t flag; 85 | }; 86 | 87 | /* 88 | * @fn: A function that will copy data from the buffer at src into the dst object 89 | * @dst: An opaque object to pass as state to fn 90 | * @src: A pointer to the buffer of data to copy to dst 91 | * @len: The length of the data pointed to by src 92 | * @return: 0 on succes, negative error code on failure 93 | * 94 | * Pre-condition: fn returns a write count or a negative error code 95 | * Post-condition: All bytes written or an error has occurred 96 | */ 97 | static ssize_t mctp_write_all(mctp_serial_tx_fn fn, void *dst, uint8_t *src, 98 | size_t len) 99 | { 100 | uint8_t *__src = src; 101 | ssize_t wrote; 102 | while (len) { 103 | wrote = fn(dst, __src, len); 104 | if (wrote < 0) { 105 | break; 106 | } 107 | __src += wrote; 108 | len -= wrote; 109 | } 110 | return len ? wrote : 0; 111 | } 112 | 113 | static int mctp_serial_write(void *fildesp, void *buf, size_t nbyte) 114 | { 115 | ssize_t wrote; 116 | int fildes = *((int *)fildesp); 117 | 118 | return ((wrote = write(fildes, buf, nbyte)) < 0) ? -errno : wrote; 119 | } 120 | 121 | static size_t mctp_serial_pkt_escape(struct mctp_pktbuf *pkt, uint8_t *buf) 122 | { 123 | uint8_t total_len; 124 | uint8_t *p; 125 | int i, j; 126 | 127 | total_len = pkt->end - pkt->mctp_hdr_off; 128 | 129 | p = (void *)mctp_pktbuf_hdr(pkt); 130 | 131 | for (i = 0, j = 0; i < total_len; i++, j++) { 132 | uint8_t c = p[i]; 133 | if (c == 0x7e || c == 0x7d) { 134 | if (buf) 135 | buf[j] = 0x7d; 136 | j++; 137 | c ^= 0x20; 138 | } 139 | if (buf) 140 | buf[j] = c; 141 | } 142 | 143 | return j; 144 | } 145 | 146 | static int mctp_binding_serial_tx(struct mctp_binding *b, 147 | struct mctp_pktbuf *pkt) 148 | { 149 | struct mctp_binding_serial *serial = binding_to_serial(b); 150 | struct mctp_serial_header *hdr; 151 | struct mctp_serial_trailer *tlr; 152 | uint8_t *buf; 153 | size_t len; 154 | uint16_t fcs; 155 | 156 | /* the length field in the header excludes serial framing 157 | * and escape sequences */ 158 | len = mctp_pktbuf_size(pkt); 159 | 160 | hdr = (void *)serial->txbuf; 161 | hdr->flag = MCTP_SERIAL_FRAMING_FLAG; 162 | hdr->revision = MCTP_SERIAL_REVISION; 163 | hdr->len = len; 164 | 165 | // Calculate fcs 166 | fcs = crc_16_ccitt(FCS_INIT_16, (const uint8_t *)hdr + 1, 2); 167 | fcs = crc_16_ccitt(fcs, (const uint8_t *)mctp_pktbuf_hdr(pkt), len); 168 | 169 | buf = (void *)(hdr + 1); 170 | 171 | len = mctp_serial_pkt_escape(pkt, NULL); 172 | if (len + sizeof(*hdr) + sizeof(*tlr) > sizeof(serial->txbuf)) 173 | return -EMSGSIZE; 174 | 175 | mctp_serial_pkt_escape(pkt, buf); 176 | 177 | buf += len; 178 | 179 | tlr = (void *)buf; 180 | tlr->flag = MCTP_SERIAL_FRAMING_FLAG; 181 | tlr->fcs_msb = fcs >> 8; 182 | tlr->fcs_lsb = fcs & 0xff; 183 | 184 | len += sizeof(*hdr) + sizeof(*tlr); 185 | 186 | if (!serial->tx_fn) 187 | return mctp_write_all(mctp_serial_write, &serial->fd, 188 | &serial->txbuf[0], len); 189 | 190 | return mctp_write_all(serial->tx_fn, serial->tx_fn_data, 191 | &serial->txbuf[0], len); 192 | } 193 | 194 | static void mctp_serial_finish_packet(struct mctp_binding_serial *serial, 195 | bool valid) 196 | { 197 | struct mctp_pktbuf *pkt = serial->rx_pkt; 198 | assert(pkt); 199 | 200 | if (valid) 201 | mctp_bus_rx(&serial->binding, pkt); 202 | 203 | serial->rx_pkt = NULL; 204 | } 205 | 206 | static void mctp_serial_start_packet(struct mctp_binding_serial *serial) 207 | { 208 | serial->rx_pkt = mctp_pktbuf_init(&serial->binding, serial->rx_storage); 209 | } 210 | 211 | static void mctp_rx_consume_one(struct mctp_binding_serial *serial, uint8_t c) 212 | { 213 | struct mctp_pktbuf *pkt = serial->rx_pkt; 214 | bool valid = false; 215 | 216 | mctp_prdebug("state: %d, char 0x%02x", serial->rx_state, c); 217 | 218 | assert(!pkt == (serial->rx_state == STATE_WAIT_SYNC_START || 219 | serial->rx_state == STATE_WAIT_REVISION || 220 | serial->rx_state == STATE_WAIT_LEN)); 221 | 222 | switch (serial->rx_state) { 223 | case STATE_WAIT_SYNC_START: 224 | if (c != MCTP_SERIAL_FRAMING_FLAG) { 225 | mctp_prdebug("lost sync, dropping packet"); 226 | if (pkt) 227 | mctp_serial_finish_packet(serial, false); 228 | } else { 229 | serial->rx_state = STATE_WAIT_REVISION; 230 | } 231 | break; 232 | 233 | case STATE_WAIT_REVISION: 234 | if (c == MCTP_SERIAL_REVISION) { 235 | serial->rx_state = STATE_WAIT_LEN; 236 | serial->rx_fcs_calc = crc_16_ccitt_byte(FCS_INIT_16, c); 237 | } else if (c == MCTP_SERIAL_FRAMING_FLAG) { 238 | /* Handle the case where there are bytes dropped in request, 239 | * and the state machine is out of sync. The failed request's 240 | * trailing footer i.e. 0x7e would be interpreted as next 241 | * request's framing footer. So if we are in STATE_WAIT_REVISION 242 | * and receive 0x7e byte, then contine to stay in 243 | * STATE_WAIT_REVISION 244 | */ 245 | mctp_prdebug( 246 | "Received serial framing flag 0x%02x while waiting" 247 | " for serial revision 0x%02x.", 248 | c, MCTP_SERIAL_REVISION); 249 | } else { 250 | mctp_prdebug("invalid revision 0x%02x", c); 251 | serial->rx_state = STATE_WAIT_SYNC_START; 252 | } 253 | break; 254 | case STATE_WAIT_LEN: 255 | if (c > serial->binding.pkt_size || 256 | c < sizeof(struct mctp_hdr)) { 257 | mctp_prdebug("invalid size %d", c); 258 | serial->rx_state = STATE_WAIT_SYNC_START; 259 | } else { 260 | mctp_serial_start_packet(serial); 261 | pkt = serial->rx_pkt; 262 | serial->rx_exp_len = c; 263 | serial->rx_state = STATE_DATA; 264 | serial->rx_fcs_calc = 265 | crc_16_ccitt_byte(serial->rx_fcs_calc, c); 266 | } 267 | break; 268 | 269 | case STATE_DATA: 270 | if (c == MCTP_SERIAL_ESCAPE) { 271 | serial->rx_state = STATE_DATA_ESCAPED; 272 | } else { 273 | mctp_pktbuf_push(pkt, &c, 1); 274 | serial->rx_fcs_calc = 275 | crc_16_ccitt_byte(serial->rx_fcs_calc, c); 276 | if (pkt->end - pkt->mctp_hdr_off == serial->rx_exp_len) 277 | serial->rx_state = STATE_WAIT_FCS1; 278 | } 279 | break; 280 | 281 | case STATE_DATA_ESCAPED: 282 | c ^= 0x20; 283 | mctp_pktbuf_push(pkt, &c, 1); 284 | serial->rx_fcs_calc = crc_16_ccitt_byte(serial->rx_fcs_calc, c); 285 | if (pkt->end - pkt->mctp_hdr_off == serial->rx_exp_len) 286 | serial->rx_state = STATE_WAIT_FCS1; 287 | else 288 | serial->rx_state = STATE_DATA; 289 | break; 290 | 291 | case STATE_WAIT_FCS1: 292 | serial->rx_fcs = c << 8; 293 | serial->rx_state = STATE_WAIT_FCS2; 294 | break; 295 | case STATE_WAIT_FCS2: 296 | serial->rx_fcs |= c; 297 | serial->rx_state = STATE_WAIT_SYNC_END; 298 | break; 299 | 300 | case STATE_WAIT_SYNC_END: 301 | if (serial->rx_fcs == serial->rx_fcs_calc) { 302 | if (c == MCTP_SERIAL_FRAMING_FLAG) { 303 | valid = true; 304 | } else { 305 | valid = false; 306 | mctp_prdebug("missing end frame marker"); 307 | } 308 | } else { 309 | valid = false; 310 | mctp_prdebug("invalid fcs : 0x%04x, expect 0x%04x", 311 | serial->rx_fcs, serial->rx_fcs_calc); 312 | } 313 | 314 | mctp_serial_finish_packet(serial, valid); 315 | serial->rx_state = STATE_WAIT_SYNC_START; 316 | break; 317 | } 318 | 319 | mctp_prdebug(" -> state: %d", serial->rx_state); 320 | } 321 | static void mctp_rx_consume(struct mctp_binding_serial *serial, const void *buf, 322 | size_t len) 323 | { 324 | size_t i; 325 | 326 | for (i = 0; i < len; i++) 327 | mctp_rx_consume_one(serial, ((const uint8_t *)buf)[i]); 328 | } 329 | 330 | #ifdef MCTP_HAVE_FILEIO 331 | int mctp_serial_read(struct mctp_binding_serial *serial) 332 | { 333 | ssize_t len; 334 | 335 | len = read(serial->fd, serial->rxbuf, sizeof(serial->rxbuf)); 336 | if (len == 0) 337 | return -1; 338 | 339 | if (len < 0) { 340 | mctp_prerr("can't read from serial device: %s", 341 | strerror(errno)); 342 | return -1; 343 | } 344 | 345 | mctp_rx_consume(serial, serial->rxbuf, len); 346 | 347 | return 0; 348 | } 349 | 350 | int mctp_serial_init_pollfd(struct mctp_binding_serial *serial, 351 | struct pollfd *pollfd) 352 | { 353 | pollfd->fd = serial->fd; 354 | pollfd->events = POLLIN; 355 | 356 | return 0; 357 | } 358 | 359 | int mctp_serial_open_path(struct mctp_binding_serial *serial, 360 | const char *device) 361 | { 362 | serial->fd = open(device, O_RDWR); 363 | if (serial->fd < 0) 364 | mctp_prerr("can't open device %s: %s", device, strerror(errno)); 365 | 366 | return 0; 367 | } 368 | 369 | void mctp_serial_open_fd(struct mctp_binding_serial *serial, int fd) 370 | { 371 | serial->fd = fd; 372 | } 373 | #endif 374 | 375 | void mctp_serial_set_tx_fn(struct mctp_binding_serial *serial, 376 | mctp_serial_tx_fn fn, void *data) 377 | { 378 | serial->tx_fn = fn; 379 | serial->tx_fn_data = data; 380 | } 381 | 382 | int mctp_serial_rx(struct mctp_binding_serial *serial, const void *buf, 383 | size_t len) 384 | { 385 | mctp_rx_consume(serial, buf, len); 386 | return 0; 387 | } 388 | 389 | static int mctp_serial_core_start(struct mctp_binding *binding) 390 | { 391 | mctp_binding_set_tx_enabled(binding, true); 392 | return 0; 393 | } 394 | 395 | struct mctp_binding *mctp_binding_serial_core(struct mctp_binding_serial *b) 396 | { 397 | return &b->binding; 398 | } 399 | 400 | struct mctp_binding_serial *mctp_serial_init(void) 401 | { 402 | struct mctp_binding_serial *serial; 403 | 404 | serial = __mctp_alloc(sizeof(*serial)); 405 | memset(serial, 0, sizeof(*serial)); 406 | serial->fd = -1; 407 | serial->rx_state = STATE_WAIT_SYNC_START; 408 | serial->rx_pkt = NULL; 409 | serial->binding.name = "serial"; 410 | serial->binding.version = 1; 411 | serial->binding.pkt_size = MCTP_PACKET_SIZE(SERIAL_BTU); 412 | serial->binding.pkt_header = 0; 413 | serial->binding.pkt_trailer = 0; 414 | serial->binding.tx_storage = serial->tx_storage; 415 | 416 | serial->binding.start = mctp_serial_core_start; 417 | serial->binding.tx = mctp_binding_serial_tx; 418 | 419 | return serial; 420 | } 421 | 422 | void mctp_serial_destroy(struct mctp_binding_serial *serial) 423 | { 424 | __mctp_free(serial); 425 | } 426 | -------------------------------------------------------------------------------- /utils/mctp-demux-daemon.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | #define _GNU_SOURCE 4 | 5 | #ifdef HAVE_CONFIG_H 6 | #include "config.h" 7 | #endif 8 | 9 | #define SD_LISTEN_FDS_START 3 10 | 11 | #include "compiler.h" 12 | #include "libmctp.h" 13 | #include "libmctp-serial.h" 14 | #include "libmctp-astlpc.h" 15 | #include "utils/mctp-capture.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 35 | 36 | #if HAVE_SYSTEMD_SD_DAEMON_H 37 | #include 38 | #else 39 | static inline int sd_listen_fds(int i __unused) 40 | { 41 | return -1; 42 | } 43 | #endif 44 | 45 | static const mctp_eid_t local_eid_default = 8; 46 | static char sockname[] = "\0mctp-mux"; 47 | 48 | struct binding { 49 | const char *name; 50 | int (*init)(struct mctp *mctp, struct binding *binding, mctp_eid_t eid, 51 | int n_params, char *const *params); 52 | void (*destroy)(struct mctp *mctp, struct binding *binding); 53 | int (*init_pollfd)(struct binding *binding, struct pollfd *pollfd); 54 | int (*process)(struct binding *binding); 55 | void *data; 56 | }; 57 | 58 | struct client { 59 | bool active; 60 | int sock; 61 | uint8_t type; 62 | }; 63 | 64 | struct ctx { 65 | struct mctp *mctp; 66 | struct binding *binding; 67 | bool verbose; 68 | int local_eid; 69 | uint8_t *buf; 70 | size_t buf_size; 71 | 72 | int sock; 73 | struct pollfd *pollfds; 74 | 75 | struct client *clients; 76 | int n_clients; 77 | 78 | struct { 79 | struct capture binding; 80 | struct capture socket; 81 | } pcap; 82 | }; 83 | 84 | static void tx_message(struct ctx *ctx, mctp_eid_t eid, void *msg, size_t len) 85 | { 86 | int rc; 87 | 88 | rc = mctp_message_tx(ctx->mctp, eid, MCTP_MESSAGE_TO_SRC, 0, msg, len); 89 | if (rc) 90 | warnx("Failed to send message: %d", rc); 91 | } 92 | 93 | static void client_remove_inactive(struct ctx *ctx) 94 | { 95 | int i; 96 | 97 | for (i = 0; i < ctx->n_clients; i++) { 98 | struct client *client = &ctx->clients[i]; 99 | if (client->active) 100 | continue; 101 | close(client->sock); 102 | 103 | ctx->n_clients--; 104 | memmove(&ctx->clients[i], &ctx->clients[i + 1], 105 | (ctx->n_clients - i) * sizeof(*ctx->clients)); 106 | ctx->clients = realloc(ctx->clients, 107 | ctx->n_clients * sizeof(*ctx->clients)); 108 | } 109 | } 110 | 111 | static void rx_message(uint8_t eid, bool tag_owner __unused, 112 | uint8_t msg_tag __unused, void *data, void *msg, 113 | size_t len) 114 | { 115 | struct ctx *ctx = data; 116 | struct iovec iov[2]; 117 | struct msghdr msghdr; 118 | bool removed; 119 | uint8_t type; 120 | int i, rc; 121 | 122 | if (len < 2) 123 | return; 124 | 125 | type = *(uint8_t *)msg; 126 | 127 | if (ctx->verbose) 128 | fprintf(stderr, "MCTP message received: len %zd, type %d\n", 129 | len, type); 130 | 131 | memset(&msghdr, 0, sizeof(msghdr)); 132 | msghdr.msg_iov = iov; 133 | msghdr.msg_iovlen = 2; 134 | iov[0].iov_base = &eid; 135 | iov[0].iov_len = 1; 136 | iov[1].iov_base = msg; 137 | iov[1].iov_len = len; 138 | 139 | for (i = 0; i < ctx->n_clients; i++) { 140 | struct client *client = &ctx->clients[i]; 141 | 142 | if (client->type != type) 143 | continue; 144 | 145 | if (ctx->verbose) 146 | fprintf(stderr, " forwarding to client %d\n", i); 147 | 148 | rc = sendmsg(client->sock, &msghdr, 0); 149 | if (rc != (ssize_t)(len + 1)) { 150 | client->active = false; 151 | removed = true; 152 | } 153 | } 154 | 155 | if (removed) 156 | client_remove_inactive(ctx); 157 | } 158 | 159 | static int binding_null_init(struct mctp *mctp __unused, 160 | struct binding *binding __unused, 161 | mctp_eid_t eid __unused, int n_params, 162 | char *const *params __unused) 163 | { 164 | if (n_params != 0) { 165 | warnx("null binding doesn't accept parameters"); 166 | return -1; 167 | } 168 | return 0; 169 | } 170 | 171 | static int binding_serial_init(struct mctp *mctp, struct binding *binding, 172 | mctp_eid_t eid, int n_params, 173 | char *const *params) 174 | { 175 | struct mctp_binding_serial *serial; 176 | const char *path; 177 | int rc; 178 | 179 | if (n_params != 1) { 180 | warnx("serial binding requires device param"); 181 | return -1; 182 | } 183 | 184 | path = params[0]; 185 | 186 | serial = mctp_serial_init(); 187 | assert(serial); 188 | 189 | rc = mctp_serial_open_path(serial, path); 190 | if (rc) 191 | return -1; 192 | 193 | mctp_register_bus(mctp, mctp_binding_serial_core(serial), eid); 194 | 195 | binding->data = serial; 196 | 197 | return 0; 198 | } 199 | 200 | static int binding_serial_init_pollfd(struct binding *binding, 201 | struct pollfd *pollfd) 202 | { 203 | return mctp_serial_init_pollfd(binding->data, pollfd); 204 | } 205 | 206 | static int binding_serial_process(struct binding *binding) 207 | { 208 | return mctp_serial_read(binding->data); 209 | } 210 | 211 | static int binding_astlpc_init(struct mctp *mctp, struct binding *binding, 212 | mctp_eid_t eid, int n_params, 213 | char *const *params __attribute__((unused))) 214 | { 215 | struct mctp_binding_astlpc *astlpc; 216 | const char *path; 217 | 218 | if (n_params != 1) { 219 | warnx("astlpc binding requires kcs device param"); 220 | return -1; 221 | } 222 | 223 | path = params[0]; 224 | 225 | astlpc = mctp_astlpc_init_fileio(path); 226 | if (!astlpc) { 227 | warnx("could not initialise astlpc binding"); 228 | return -1; 229 | } 230 | 231 | mctp_register_bus(mctp, mctp_binding_astlpc_core(astlpc), eid); 232 | 233 | binding->data = astlpc; 234 | return 0; 235 | } 236 | 237 | static void binding_astlpc_destroy(struct mctp *mctp, struct binding *binding) 238 | { 239 | struct mctp_binding_astlpc *astlpc = binding->data; 240 | 241 | mctp_unregister_bus(mctp, mctp_binding_astlpc_core(astlpc)); 242 | 243 | mctp_astlpc_destroy(astlpc); 244 | } 245 | 246 | static int binding_astlpc_init_pollfd(struct binding *binding, 247 | struct pollfd *pollfd) 248 | { 249 | return mctp_astlpc_init_pollfd(binding->data, pollfd); 250 | } 251 | 252 | static int binding_astlpc_process(struct binding *binding) 253 | { 254 | return mctp_astlpc_poll(binding->data); 255 | } 256 | 257 | struct binding bindings[] = { { 258 | .name = "null", 259 | .init = binding_null_init, 260 | }, 261 | { 262 | .name = "serial", 263 | .init = binding_serial_init, 264 | .destroy = NULL, 265 | .init_pollfd = binding_serial_init_pollfd, 266 | .process = binding_serial_process, 267 | }, 268 | { 269 | .name = "astlpc", 270 | .init = binding_astlpc_init, 271 | .destroy = binding_astlpc_destroy, 272 | .init_pollfd = binding_astlpc_init_pollfd, 273 | .process = binding_astlpc_process, 274 | } }; 275 | 276 | struct binding *binding_lookup(const char *name) 277 | { 278 | struct binding *binding; 279 | unsigned int i; 280 | 281 | for (i = 0; i < ARRAY_SIZE(bindings); i++) { 282 | binding = &bindings[i]; 283 | 284 | if (!strcmp(binding->name, name)) 285 | return binding; 286 | } 287 | 288 | return NULL; 289 | } 290 | 291 | static int socket_init(struct ctx *ctx) 292 | { 293 | struct sockaddr_un addr; 294 | int namelen, rc; 295 | 296 | namelen = sizeof(sockname) - 1; 297 | addr.sun_family = AF_UNIX; 298 | memcpy(addr.sun_path, sockname, namelen); 299 | 300 | ctx->sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); 301 | if (ctx->sock < 0) { 302 | warn("can't create socket"); 303 | return -1; 304 | } 305 | 306 | rc = bind(ctx->sock, (struct sockaddr *)&addr, 307 | sizeof(addr.sun_family) + namelen); 308 | if (rc) { 309 | warn("can't bind socket"); 310 | goto err_close; 311 | } 312 | 313 | rc = listen(ctx->sock, 1); 314 | if (rc) { 315 | warn("can't listen on socket"); 316 | goto err_close; 317 | } 318 | 319 | return 0; 320 | 321 | err_close: 322 | close(ctx->sock); 323 | return -1; 324 | } 325 | 326 | static int socket_process(struct ctx *ctx) 327 | { 328 | struct client *client; 329 | int fd; 330 | 331 | fd = accept4(ctx->sock, NULL, 0, SOCK_NONBLOCK); 332 | if (fd < 0) 333 | return -1; 334 | 335 | ctx->n_clients++; 336 | ctx->clients = 337 | realloc(ctx->clients, ctx->n_clients * sizeof(struct client)); 338 | 339 | client = &ctx->clients[ctx->n_clients - 1]; 340 | memset(client, 0, sizeof(*client)); 341 | client->active = true; 342 | client->sock = fd; 343 | 344 | return 0; 345 | } 346 | 347 | static int client_process_recv(struct ctx *ctx, int idx) 348 | { 349 | struct client *client = &ctx->clients[idx]; 350 | uint8_t eid; 351 | ssize_t len; 352 | int rc; 353 | 354 | /* are we waiting for a type message? */ 355 | if (!client->type) { 356 | uint8_t type; 357 | rc = read(client->sock, &type, 1); 358 | if (rc <= 0) 359 | goto out_close; 360 | 361 | if (type == 0) { 362 | rc = -1; 363 | goto out_close; 364 | } 365 | if (ctx->verbose) 366 | fprintf(stderr, "client[%d] registered for type %u\n", 367 | idx, type); 368 | client->type = type; 369 | return 0; 370 | } 371 | 372 | len = recv(client->sock, NULL, 0, MSG_PEEK | MSG_TRUNC); 373 | if (len < 0) { 374 | if (errno != ECONNRESET) 375 | warn("can't receive (peek) from client"); 376 | 377 | rc = -1; 378 | goto out_close; 379 | } 380 | 381 | if ((size_t)len > ctx->buf_size) { 382 | void *tmp; 383 | 384 | tmp = realloc(ctx->buf, len); 385 | if (!tmp) { 386 | warn("can't allocate for incoming message"); 387 | rc = -1; 388 | goto out_close; 389 | } 390 | ctx->buf = tmp; 391 | ctx->buf_size = len; 392 | } 393 | 394 | rc = recv(client->sock, ctx->buf, ctx->buf_size, 0); 395 | if (rc < 0) { 396 | if (errno != ECONNRESET) 397 | warn("can't receive from client"); 398 | rc = -1; 399 | goto out_close; 400 | } 401 | 402 | if (rc <= 0) { 403 | rc = -1; 404 | goto out_close; 405 | } 406 | 407 | eid = *(uint8_t *)ctx->buf; 408 | 409 | if (ctx->pcap.socket.path) 410 | capture_socket(ctx->pcap.socket.dumper, ctx->buf, rc, 411 | MCTP_MESSAGE_CAPTURE_OUTGOING, eid); 412 | 413 | if (ctx->verbose) 414 | fprintf(stderr, "client[%d] sent message: dest 0x%02x len %d\n", 415 | idx, eid, rc - 1); 416 | 417 | if (eid == ctx->local_eid) 418 | rx_message(eid, MCTP_MESSAGE_TO_DST, 0, ctx, ctx->buf + 1, 419 | rc - 1); 420 | else 421 | tx_message(ctx, eid, ctx->buf + 1, rc - 1); 422 | 423 | return 0; 424 | 425 | out_close: 426 | client->active = false; 427 | return rc; 428 | } 429 | 430 | static int binding_init(struct ctx *ctx, const char *name, int argc, 431 | char *const *argv) 432 | { 433 | int rc; 434 | 435 | ctx->binding = binding_lookup(name); 436 | if (!ctx->binding) { 437 | warnx("no such binding '%s'", name); 438 | return -1; 439 | } 440 | 441 | rc = ctx->binding->init(ctx->mctp, ctx->binding, ctx->local_eid, argc, 442 | argv); 443 | return rc; 444 | } 445 | 446 | static void binding_destroy(struct ctx *ctx) 447 | { 448 | if (ctx->binding->destroy) 449 | ctx->binding->destroy(ctx->mctp, ctx->binding); 450 | } 451 | 452 | enum { 453 | FD_BINDING = 0, 454 | FD_SOCKET, 455 | FD_SIGNAL, 456 | FD_NR, 457 | }; 458 | 459 | static int run_daemon(struct ctx *ctx) 460 | { 461 | bool clients_changed = false; 462 | sigset_t mask; 463 | int rc, i; 464 | int n_clients; 465 | 466 | ctx->pollfds = malloc(FD_NR * sizeof(struct pollfd)); 467 | 468 | if (!ctx->binding->init_pollfd) { 469 | ctx->pollfds[FD_BINDING].fd = -1; 470 | ctx->pollfds[FD_BINDING].events = 0; 471 | } 472 | 473 | sigemptyset(&mask); 474 | sigaddset(&mask, SIGINT); 475 | sigaddset(&mask, SIGTERM); 476 | sigaddset(&mask, SIGQUIT); 477 | 478 | if ((rc = sigprocmask(SIG_BLOCK, &mask, NULL)) == -1) { 479 | warn("sigprocmask"); 480 | return rc; 481 | } 482 | 483 | ctx->pollfds[FD_SIGNAL].fd = signalfd(-1, &mask, 0); 484 | ctx->pollfds[FD_SIGNAL].events = POLLIN; 485 | 486 | ctx->pollfds[FD_SOCKET].fd = ctx->sock; 487 | ctx->pollfds[FD_SOCKET].events = POLLIN; 488 | 489 | mctp_set_rx_all(ctx->mctp, rx_message, ctx); 490 | 491 | for (;;) { 492 | if (clients_changed) { 493 | int i; 494 | 495 | ctx->pollfds = realloc(ctx->pollfds, 496 | (ctx->n_clients + FD_NR) * 497 | sizeof(struct pollfd)); 498 | 499 | for (i = 0; i < ctx->n_clients; i++) { 500 | ctx->pollfds[FD_NR + i].fd = 501 | ctx->clients[i].sock; 502 | ctx->pollfds[FD_NR + i].events = POLLIN; 503 | } 504 | clients_changed = false; 505 | } 506 | 507 | if (ctx->binding->init_pollfd) 508 | ctx->binding->init_pollfd(ctx->binding, 509 | &ctx->pollfds[FD_BINDING]); 510 | rc = poll(ctx->pollfds, ctx->n_clients + FD_NR, -1); 511 | if (rc < 0) { 512 | warn("poll failed"); 513 | break; 514 | } 515 | 516 | if (!rc) 517 | continue; 518 | 519 | if (ctx->pollfds[FD_SIGNAL].revents) { 520 | struct signalfd_siginfo si; 521 | ssize_t got; 522 | 523 | got = read(ctx->pollfds[FD_SIGNAL].fd, &si, sizeof(si)); 524 | if (got == sizeof(si)) { 525 | warnx("Received %s, quitting", 526 | strsignal(si.ssi_signo)); 527 | rc = 0; 528 | break; 529 | } else { 530 | warnx("Unexpected read result for signalfd: %d", 531 | rc); 532 | warnx("Quitting on the basis that signalfd became ready"); 533 | rc = -1; 534 | break; 535 | } 536 | } 537 | 538 | n_clients = ctx->n_clients; 539 | if (ctx->pollfds[FD_BINDING].revents) { 540 | rc = 0; 541 | if (ctx->binding->process) 542 | rc = ctx->binding->process(ctx->binding); 543 | if (rc) 544 | break; 545 | } 546 | if (n_clients != ctx->n_clients) { 547 | /* 548 | * Clients (i.e. sockets) were removed in the binding->process() function 549 | * call above. More specifically in function rx_message(), invoked through 550 | * the binding->process() call. 551 | * We must go back to the top of the loop to realign the pollfds sockets array 552 | */ 553 | clients_changed = true; 554 | continue; 555 | } 556 | 557 | for (i = 0; i < ctx->n_clients; i++) { 558 | if (!ctx->pollfds[FD_NR + i].revents) 559 | continue; 560 | 561 | rc = client_process_recv(ctx, i); 562 | if (rc) 563 | clients_changed = true; 564 | } 565 | 566 | if (ctx->pollfds[FD_SOCKET].revents) { 567 | rc = socket_process(ctx); 568 | if (rc) 569 | break; 570 | clients_changed = true; 571 | } 572 | 573 | if (clients_changed) 574 | client_remove_inactive(ctx); 575 | } 576 | 577 | free(ctx->pollfds); 578 | 579 | return rc; 580 | } 581 | 582 | static const struct option options[] = { 583 | { "capture-binding", required_argument, 0, 'b' }, 584 | { "capture-socket", required_argument, 0, 's' }, 585 | { "binding-linktype", required_argument, 0, 'B' }, 586 | { "socket-linktype", required_argument, 0, 'S' }, 587 | { "verbose", no_argument, 0, 'v' }, 588 | { "eid", required_argument, 0, 'e' }, 589 | { 0 }, 590 | }; 591 | 592 | static void usage(const char *progname) 593 | { 594 | unsigned int i; 595 | 596 | fprintf(stderr, "usage: %s [params]\n", progname); 597 | fprintf(stderr, "Available bindings:\n"); 598 | for (i = 0; i < ARRAY_SIZE(bindings); i++) 599 | fprintf(stderr, " %s\n", bindings[i].name); 600 | } 601 | 602 | int main(int argc, char *const *argv) 603 | { 604 | struct ctx *ctx, _ctx; 605 | int rc; 606 | 607 | ctx = &_ctx; 608 | ctx->clients = NULL; 609 | ctx->n_clients = 0; 610 | ctx->local_eid = local_eid_default; 611 | ctx->verbose = false; 612 | ctx->pcap.binding.path = NULL; 613 | ctx->pcap.socket.path = NULL; 614 | 615 | for (;;) { 616 | rc = getopt_long(argc, argv, "b:e:s::v", options, NULL); 617 | if (rc == -1) 618 | break; 619 | switch (rc) { 620 | case 'b': 621 | ctx->pcap.binding.path = optarg; 622 | break; 623 | case 's': 624 | ctx->pcap.socket.path = optarg; 625 | break; 626 | case 'B': 627 | fprintf(stderr, 628 | "binding-linktype argument is deprecated\n"); 629 | break; 630 | case 'S': 631 | fprintf(stderr, 632 | "socket-linktype argument is deprecated\n"); 633 | break; 634 | case 'v': 635 | ctx->verbose = true; 636 | break; 637 | case 'e': 638 | ctx->local_eid = atoi(optarg); 639 | break; 640 | default: 641 | fprintf(stderr, "Invalid argument\n"); 642 | return EXIT_FAILURE; 643 | } 644 | } 645 | 646 | if (optind >= argc) { 647 | fprintf(stderr, "missing binding argument\n"); 648 | usage(argv[0]); 649 | return EXIT_FAILURE; 650 | } 651 | 652 | /* setup initial buffer */ 653 | ctx->buf_size = 4096; 654 | ctx->buf = malloc(ctx->buf_size); 655 | 656 | mctp_set_log_stdio(ctx->verbose ? MCTP_LOG_DEBUG : MCTP_LOG_NOTICE); 657 | 658 | ctx->mctp = mctp_init(); 659 | assert(ctx->mctp); 660 | 661 | if (ctx->pcap.binding.path || ctx->pcap.socket.path) { 662 | if (capture_init()) { 663 | rc = EXIT_FAILURE; 664 | goto cleanup_mctp; 665 | } 666 | } 667 | 668 | if (ctx->pcap.binding.path) { 669 | rc = capture_prepare(&ctx->pcap.binding); 670 | if (rc == -1) { 671 | fprintf(stderr, "Failed to initialise capture: %d\n", 672 | rc); 673 | rc = EXIT_FAILURE; 674 | goto cleanup_mctp; 675 | } 676 | 677 | mctp_set_capture_handler(ctx->mctp, capture_binding, 678 | ctx->pcap.binding.dumper); 679 | } 680 | 681 | if (ctx->pcap.socket.path) { 682 | rc = capture_prepare(&ctx->pcap.socket); 683 | if (rc == -1) { 684 | fprintf(stderr, "Failed to initialise capture: %d\n", 685 | rc); 686 | rc = EXIT_FAILURE; 687 | goto cleanup_pcap_binding; 688 | } 689 | } 690 | 691 | rc = binding_init(ctx, argv[optind], argc - optind - 1, 692 | argv + optind + 1); 693 | if (rc) { 694 | fprintf(stderr, "Failed to initialise binding: %d\n", rc); 695 | rc = EXIT_FAILURE; 696 | goto cleanup_pcap_socket; 697 | } 698 | 699 | rc = sd_listen_fds(true); 700 | if (rc <= 0) { 701 | rc = socket_init(ctx); 702 | if (rc) { 703 | fprintf(stderr, "Failed to initialse socket: %d\n", rc); 704 | goto cleanup_binding; 705 | } 706 | } else { 707 | ctx->sock = SD_LISTEN_FDS_START; 708 | } 709 | 710 | rc = run_daemon(ctx); 711 | 712 | cleanup_binding: 713 | binding_destroy(ctx); 714 | 715 | cleanup_pcap_socket: 716 | if (ctx->pcap.socket.path) 717 | capture_close(&ctx->pcap.socket); 718 | 719 | cleanup_pcap_binding: 720 | if (ctx->pcap.binding.path) 721 | capture_close(&ctx->pcap.binding); 722 | 723 | rc = rc ? EXIT_FAILURE : EXIT_SUCCESS; 724 | cleanup_mctp: 725 | 726 | return rc; 727 | } 728 | -------------------------------------------------------------------------------- /tests/test_core.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | #define _GNU_SOURCE 3 | 4 | #ifdef NDEBUG 5 | #undef NDEBUG 6 | #endif 7 | 8 | #if HAVE_CONFIG_H 9 | #include "config.h" 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "compiler.h" 23 | #include "libmctp-alloc.h" 24 | #include "libmctp-log.h" 25 | #include "range.h" 26 | #include "test-utils.h" 27 | 28 | #define TEST_DEST_EID 9 29 | #define TEST_DEST_NULL_EID 0 30 | #define TEST_DEST_BROADCAST_EID 255 31 | #define TEST_SRC_EID 10 32 | 33 | #ifndef ARRAY_SIZE 34 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 35 | #endif 36 | 37 | #define MAX_PAYLOAD_SIZE 50000 38 | 39 | struct pktbuf { 40 | struct mctp_hdr hdr; 41 | uint8_t *payload; 42 | }; 43 | 44 | struct test_params { 45 | bool seen; 46 | size_t message_size; 47 | uint8_t msg_tag; 48 | bool tag_owner; 49 | }; 50 | 51 | static void rx_message(uint8_t eid __unused, bool tag_owner, uint8_t msg_tag, 52 | void *data, void *msg __unused, size_t len) 53 | { 54 | struct test_params *param = (struct test_params *)data; 55 | 56 | mctp_prdebug("MCTP message received: len %zd, tag %u", len, msg_tag); 57 | 58 | param->seen = true; 59 | param->message_size = len; 60 | param->msg_tag = msg_tag; 61 | param->tag_owner = tag_owner; 62 | } 63 | 64 | static uint8_t get_sequence() 65 | { 66 | static uint8_t pkt_seq = 0; 67 | 68 | return (pkt_seq++ % 4); 69 | } 70 | 71 | static uint8_t get_tag() 72 | { 73 | static uint8_t tag = 0; 74 | 75 | return (tag++ % 8); 76 | } 77 | 78 | /* 79 | * receive_pktbuf bypasses all bindings and directly invokes mctp_bus_rx. 80 | * This is necessary in order invoke test cases on the core functionality. 81 | * The memory allocated for the mctp packet is capped at MCTP_BTU 82 | * size, however, the mimiced rx pkt still retains the len parameter. 83 | * This allows to mimic packets larger than a sane memory allocator can 84 | * provide. 85 | */ 86 | static void receive_ptkbuf(struct mctp_binding_test *binding, 87 | const struct pktbuf *pktbuf, size_t len) 88 | { 89 | size_t alloc_size = MIN((size_t)MCTP_BTU, len); 90 | struct mctp_pktbuf *rx_pkt; 91 | 92 | rx_pkt = __mctp_alloc(sizeof(*rx_pkt) + MCTP_PACKET_SIZE(alloc_size)); 93 | assert(rx_pkt); 94 | 95 | /* Preserve passed len parameter */ 96 | rx_pkt->size = MCTP_PACKET_SIZE(len); 97 | rx_pkt->start = 0; 98 | rx_pkt->end = MCTP_PACKET_SIZE(len); 99 | rx_pkt->mctp_hdr_off = 0; 100 | memcpy(rx_pkt->data, &pktbuf->hdr, sizeof(pktbuf->hdr)); 101 | memcpy(rx_pkt->data + sizeof(pktbuf->hdr), pktbuf->payload, alloc_size); 102 | 103 | mctp_bus_rx((struct mctp_binding *)binding, rx_pkt); 104 | __mctp_free(rx_pkt); 105 | } 106 | 107 | static void receive_one_fragment(struct mctp_binding_test *binding, 108 | uint8_t *payload, size_t fragment_size, 109 | uint8_t flags_seq_tag, struct pktbuf *pktbuf) 110 | { 111 | pktbuf->hdr.flags_seq_tag = flags_seq_tag; 112 | pktbuf->payload = payload; 113 | receive_ptkbuf(binding, pktbuf, fragment_size); 114 | } 115 | 116 | static void receive_two_fragment_message(struct mctp_binding_test *binding, 117 | uint8_t *payload, 118 | size_t fragment1_size, 119 | size_t fragment2_size, 120 | struct pktbuf *pktbuf) 121 | { 122 | uint8_t tag = MCTP_HDR_FLAG_TO | get_tag(); 123 | uint8_t flags_seq_tag; 124 | 125 | flags_seq_tag = MCTP_HDR_FLAG_SOM | 126 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 127 | receive_one_fragment(binding, payload, fragment1_size, flags_seq_tag, 128 | pktbuf); 129 | 130 | flags_seq_tag = MCTP_HDR_FLAG_EOM | 131 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 132 | receive_one_fragment(binding, payload + fragment1_size, fragment2_size, 133 | flags_seq_tag, pktbuf); 134 | } 135 | 136 | static void mctp_core_test_simple_rx() 137 | { 138 | struct mctp *mctp = NULL; 139 | struct mctp_binding_test *binding = NULL; 140 | struct test_params test_param; 141 | uint8_t test_payload[2 * MCTP_BTU]; 142 | struct pktbuf pktbuf; 143 | 144 | memset(test_payload, 0, sizeof(test_payload)); 145 | test_param.seen = false; 146 | test_param.message_size = 0; 147 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 148 | mctp_set_rx_all(mctp, rx_message, &test_param); 149 | memset(&pktbuf, 0, sizeof(pktbuf)); 150 | pktbuf.hdr.dest = TEST_DEST_EID; 151 | pktbuf.hdr.src = TEST_SRC_EID; 152 | 153 | /* Receive 2 fragments of equal size */ 154 | receive_two_fragment_message(binding, test_payload, MCTP_BTU, MCTP_BTU, 155 | &pktbuf); 156 | 157 | assert(test_param.seen); 158 | assert(test_param.message_size == 2 * MCTP_BTU); 159 | 160 | mctp_binding_test_destroy(binding); 161 | mctp_destroy(mctp); 162 | } 163 | 164 | static void mctp_core_test_receive_equal_length_fragments() 165 | { 166 | struct mctp *mctp = NULL; 167 | struct mctp_binding_test *binding = NULL; 168 | struct test_params test_param; 169 | static uint8_t test_payload[MAX_PAYLOAD_SIZE]; 170 | uint8_t tag = MCTP_HDR_FLAG_TO | get_tag(); 171 | struct pktbuf pktbuf; 172 | uint8_t flags_seq_tag; 173 | 174 | memset(test_payload, 0, sizeof(test_payload)); 175 | test_param.seen = false; 176 | test_param.message_size = 0; 177 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 178 | mctp_set_rx_all(mctp, rx_message, &test_param); 179 | memset(&pktbuf, 0, sizeof(pktbuf)); 180 | pktbuf.hdr.dest = TEST_DEST_EID; 181 | pktbuf.hdr.src = TEST_SRC_EID; 182 | 183 | /* Receive 3 fragments, each of size MCTP_BTU */ 184 | flags_seq_tag = MCTP_HDR_FLAG_SOM | 185 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 186 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 187 | &pktbuf); 188 | 189 | flags_seq_tag = (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 190 | receive_one_fragment(binding, test_payload + MCTP_BTU, MCTP_BTU, 191 | flags_seq_tag, &pktbuf); 192 | 193 | flags_seq_tag = MCTP_HDR_FLAG_EOM | 194 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 195 | receive_one_fragment(binding, test_payload + (2 * MCTP_BTU), MCTP_BTU, 196 | flags_seq_tag, &pktbuf); 197 | 198 | assert(test_param.seen); 199 | assert(test_param.message_size == 3 * MCTP_BTU); 200 | 201 | mctp_binding_test_destroy(binding); 202 | mctp_destroy(mctp); 203 | } 204 | 205 | static void mctp_core_test_receive_unexpected_smaller_middle_fragment() 206 | { 207 | struct mctp *mctp = NULL; 208 | struct mctp_binding_test *binding = NULL; 209 | struct test_params test_param; 210 | static uint8_t test_payload[MAX_PAYLOAD_SIZE]; 211 | uint8_t tag = MCTP_HDR_FLAG_TO | get_tag(); 212 | struct pktbuf pktbuf; 213 | uint8_t flags_seq_tag; 214 | 215 | memset(test_payload, 0, sizeof(test_payload)); 216 | test_param.seen = false; 217 | test_param.message_size = 0; 218 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 219 | mctp_set_rx_all(mctp, rx_message, &test_param); 220 | memset(&pktbuf, 0, sizeof(pktbuf)); 221 | pktbuf.hdr.dest = TEST_DEST_EID; 222 | pktbuf.hdr.src = TEST_SRC_EID; 223 | 224 | /* Middle fragment with size MCTP_BTU - 1 */ 225 | flags_seq_tag = MCTP_HDR_FLAG_SOM | 226 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 227 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 228 | &pktbuf); 229 | 230 | flags_seq_tag = (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 231 | receive_one_fragment(binding, test_payload + MCTP_BTU, MCTP_BTU - 1, 232 | flags_seq_tag, &pktbuf); 233 | 234 | flags_seq_tag = MCTP_HDR_FLAG_EOM | 235 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 236 | receive_one_fragment(binding, test_payload + (2 * MCTP_BTU), MCTP_BTU, 237 | flags_seq_tag, &pktbuf); 238 | 239 | assert(!test_param.seen); 240 | 241 | mctp_binding_test_destroy(binding); 242 | mctp_destroy(mctp); 243 | } 244 | 245 | static void mctp_core_test_receive_unexpected_bigger_middle_fragment() 246 | { 247 | struct mctp *mctp = NULL; 248 | struct mctp_binding_test *binding = NULL; 249 | struct test_params test_param; 250 | static uint8_t test_payload[MAX_PAYLOAD_SIZE]; 251 | uint8_t tag = MCTP_HDR_FLAG_TO | get_tag(); 252 | struct pktbuf pktbuf; 253 | uint8_t flags_seq_tag; 254 | 255 | memset(test_payload, 0, sizeof(test_payload)); 256 | test_param.seen = false; 257 | test_param.message_size = 0; 258 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 259 | mctp_set_rx_all(mctp, rx_message, &test_param); 260 | memset(&pktbuf, 0, sizeof(pktbuf)); 261 | pktbuf.hdr.dest = TEST_DEST_EID; 262 | pktbuf.hdr.src = TEST_SRC_EID; 263 | 264 | /* Middle fragment with size MCTP_BTU + 1 */ 265 | flags_seq_tag = MCTP_HDR_FLAG_SOM | 266 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 267 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 268 | &pktbuf); 269 | 270 | flags_seq_tag = (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 271 | receive_one_fragment(binding, test_payload + MCTP_BTU, MCTP_BTU + 1, 272 | flags_seq_tag, &pktbuf); 273 | 274 | flags_seq_tag = MCTP_HDR_FLAG_EOM | 275 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 276 | receive_one_fragment(binding, test_payload + (2 * MCTP_BTU), MCTP_BTU, 277 | flags_seq_tag, &pktbuf); 278 | 279 | assert(!test_param.seen); 280 | 281 | mctp_binding_test_destroy(binding); 282 | mctp_destroy(mctp); 283 | } 284 | 285 | static void mctp_core_test_receive_smaller_end_fragment() 286 | { 287 | struct mctp *mctp = NULL; 288 | struct mctp_binding_test *binding = NULL; 289 | struct test_params test_param; 290 | static uint8_t test_payload[MAX_PAYLOAD_SIZE]; 291 | uint8_t tag = MCTP_HDR_FLAG_TO | get_tag(); 292 | uint8_t end_frag_size = MCTP_BTU - 10; 293 | struct pktbuf pktbuf; 294 | uint8_t flags_seq_tag; 295 | 296 | memset(test_payload, 0, sizeof(test_payload)); 297 | test_param.seen = false; 298 | test_param.message_size = 0; 299 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 300 | mctp_set_rx_all(mctp, rx_message, &test_param); 301 | memset(&pktbuf, 0, sizeof(pktbuf)); 302 | pktbuf.hdr.dest = TEST_DEST_EID; 303 | pktbuf.hdr.src = TEST_SRC_EID; 304 | 305 | flags_seq_tag = MCTP_HDR_FLAG_SOM | 306 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 307 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 308 | &pktbuf); 309 | 310 | flags_seq_tag = (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 311 | receive_one_fragment(binding, test_payload + MCTP_BTU, MCTP_BTU, 312 | flags_seq_tag, &pktbuf); 313 | 314 | flags_seq_tag = MCTP_HDR_FLAG_EOM | 315 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 316 | receive_one_fragment(binding, test_payload + (2 * MCTP_BTU), 317 | end_frag_size, flags_seq_tag, &pktbuf); 318 | 319 | assert(test_param.seen); 320 | assert(test_param.message_size == 321 | (size_t)(2 * MCTP_BTU + end_frag_size)); 322 | 323 | mctp_binding_test_destroy(binding); 324 | mctp_destroy(mctp); 325 | } 326 | 327 | static void mctp_core_test_receive_bigger_end_fragment() 328 | { 329 | struct mctp *mctp = NULL; 330 | struct mctp_binding_test *binding = NULL; 331 | struct test_params test_param; 332 | static uint8_t test_payload[MAX_PAYLOAD_SIZE]; 333 | uint8_t tag = MCTP_HDR_FLAG_TO | get_tag(); 334 | uint8_t end_frag_size = MCTP_BTU + 10; 335 | struct pktbuf pktbuf; 336 | uint8_t flags_seq_tag; 337 | 338 | memset(test_payload, 0, sizeof(test_payload)); 339 | test_param.seen = false; 340 | test_param.message_size = 0; 341 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 342 | mctp_set_rx_all(mctp, rx_message, &test_param); 343 | memset(&pktbuf, 0, sizeof(pktbuf)); 344 | pktbuf.hdr.dest = TEST_DEST_EID; 345 | pktbuf.hdr.src = TEST_SRC_EID; 346 | 347 | flags_seq_tag = MCTP_HDR_FLAG_SOM | 348 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 349 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 350 | &pktbuf); 351 | 352 | flags_seq_tag = (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 353 | receive_one_fragment(binding, test_payload + MCTP_BTU, MCTP_BTU, 354 | flags_seq_tag, &pktbuf); 355 | 356 | flags_seq_tag = MCTP_HDR_FLAG_EOM | 357 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 358 | receive_one_fragment(binding, test_payload + (2 * MCTP_BTU), 359 | end_frag_size, flags_seq_tag, &pktbuf); 360 | 361 | assert(!test_param.seen); 362 | 363 | mctp_binding_test_destroy(binding); 364 | mctp_destroy(mctp); 365 | } 366 | 367 | static void mctp_core_test_drop_large_fragments() 368 | { 369 | struct mctp *mctp = NULL; 370 | struct mctp_binding_test *binding = NULL; 371 | struct test_params test_param; 372 | static uint8_t test_payload[MAX_PAYLOAD_SIZE]; 373 | struct pktbuf pktbuf; 374 | 375 | memset(test_payload, 0, sizeof(test_payload)); 376 | test_param.seen = false; 377 | test_param.message_size = 0; 378 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 379 | mctp_set_rx_all(mctp, rx_message, &test_param); 380 | memset(&pktbuf, 0, sizeof(pktbuf)); 381 | pktbuf.hdr.dest = TEST_DEST_EID; 382 | pktbuf.hdr.src = TEST_SRC_EID; 383 | 384 | /* Receive a large payload - first fragment with MCTP_BTU bytes, 385 | * 2nd fragment of SIZE_MAX */ 386 | 387 | receive_two_fragment_message(binding, test_payload, MCTP_BTU, 388 | SIZE_MAX - sizeof(struct mctp_hdr), 389 | &pktbuf); 390 | 391 | assert(!test_param.seen); 392 | 393 | mctp_binding_test_destroy(binding); 394 | mctp_destroy(mctp); 395 | } 396 | 397 | static void mctp_core_test_exhaust_context_buffers() 398 | { 399 | struct mctp *mctp = NULL; 400 | struct mctp_binding_test *binding = NULL; 401 | struct test_params test_param; 402 | static uint8_t test_payload[MAX_PAYLOAD_SIZE]; 403 | uint8_t tag = MCTP_HDR_FLAG_TO | get_tag(); 404 | uint8_t i = 0; 405 | const uint8_t max_context_buffers = 16; 406 | struct pktbuf pktbuf; 407 | uint8_t flags_seq_tag; 408 | 409 | memset(test_payload, 0, sizeof(test_payload)); 410 | test_param.seen = false; 411 | test_param.message_size = 0; 412 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 413 | mctp_set_rx_all(mctp, rx_message, &test_param); 414 | memset(&pktbuf, 0, sizeof(pktbuf)); 415 | pktbuf.hdr.dest = TEST_DEST_EID; 416 | pktbuf.hdr.src = TEST_SRC_EID; 417 | 418 | /* Exhaust all 16 context buffers*/ 419 | for (i = 0; i < max_context_buffers; i++) { 420 | flags_seq_tag = MCTP_HDR_FLAG_SOM | 421 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 422 | receive_one_fragment(binding, test_payload, MCTP_BTU, 423 | flags_seq_tag, &pktbuf); 424 | 425 | /* Change source EID so that different contexts are created */ 426 | pktbuf.hdr.src++; 427 | } 428 | 429 | /* Send a full message from a different EID */ 430 | pktbuf.hdr.src++; 431 | receive_two_fragment_message(binding, test_payload, MCTP_BTU, MCTP_BTU, 432 | &pktbuf); 433 | 434 | /* Message assembly should fail */ 435 | assert(!test_param.seen); 436 | 437 | /* Complete message assembly for one of the messages */ 438 | pktbuf.hdr.src -= max_context_buffers; 439 | flags_seq_tag = MCTP_HDR_FLAG_EOM | 440 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | tag; 441 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 442 | &pktbuf); 443 | 444 | assert(test_param.seen); 445 | assert(test_param.message_size == (2 * MCTP_BTU)); 446 | 447 | mctp_binding_test_destroy(binding); 448 | mctp_destroy(mctp); 449 | } 450 | 451 | static void mctp_core_test_rx_with_tag() 452 | { 453 | struct mctp *mctp = NULL; 454 | struct mctp_binding_test *binding = NULL; 455 | struct test_params test_param; 456 | static uint8_t test_payload[MCTP_BTU]; 457 | uint8_t tag = get_tag(); 458 | struct pktbuf pktbuf; 459 | uint8_t flags_seq_tag; 460 | 461 | memset(test_payload, 0, sizeof(test_payload)); 462 | test_param.seen = false; 463 | test_param.message_size = 0; 464 | test_param.msg_tag = 0; 465 | test_param.tag_owner = false; 466 | 467 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 468 | mctp_set_rx_all(mctp, rx_message, &test_param); 469 | memset(&pktbuf, 0, sizeof(pktbuf)); 470 | pktbuf.hdr.dest = TEST_DEST_EID; 471 | pktbuf.hdr.src = TEST_SRC_EID; 472 | 473 | /* Set tag and tag owner fields for a recieve packet */ 474 | flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM | 475 | (1 << MCTP_HDR_TO_SHIFT) | tag; 476 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 477 | &pktbuf); 478 | 479 | assert(test_param.seen); 480 | assert(test_param.message_size == (MCTP_BTU)); 481 | assert(test_param.msg_tag == tag); 482 | assert(test_param.tag_owner); 483 | 484 | mctp_binding_test_destroy(binding); 485 | mctp_destroy(mctp); 486 | } 487 | 488 | static void mctp_core_test_rx_with_tag_multifragment() 489 | { 490 | struct mctp *mctp = NULL; 491 | struct mctp_binding_test *binding = NULL; 492 | struct test_params test_param; 493 | static uint8_t test_payload[MCTP_BTU]; 494 | uint8_t tag = get_tag(); 495 | struct pktbuf pktbuf; 496 | uint8_t flags_seq_tag; 497 | 498 | memset(test_payload, 0, sizeof(test_payload)); 499 | test_param.seen = false; 500 | test_param.message_size = 0; 501 | test_param.msg_tag = 0; 502 | test_param.tag_owner = false; 503 | 504 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 505 | mctp_set_rx_all(mctp, rx_message, &test_param); 506 | memset(&pktbuf, 0, sizeof(pktbuf)); 507 | pktbuf.hdr.dest = TEST_DEST_EID; 508 | pktbuf.hdr.src = TEST_SRC_EID; 509 | 510 | /* Set tag and tag owner fields for a 3 fragment packet */ 511 | flags_seq_tag = MCTP_HDR_FLAG_SOM | 512 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | 513 | (1 << MCTP_HDR_TO_SHIFT) | tag; 514 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 515 | &pktbuf); 516 | 517 | flags_seq_tag = (get_sequence() << MCTP_HDR_SEQ_SHIFT) | 518 | (1 << MCTP_HDR_TO_SHIFT) | tag; 519 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 520 | &pktbuf); 521 | 522 | flags_seq_tag = MCTP_HDR_FLAG_EOM | 523 | (get_sequence() << MCTP_HDR_SEQ_SHIFT) | 524 | (1 << MCTP_HDR_TO_SHIFT) | tag; 525 | receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag, 526 | &pktbuf); 527 | 528 | assert(test_param.seen); 529 | assert(test_param.message_size == (3 * MCTP_BTU)); 530 | assert(test_param.msg_tag == tag); 531 | assert(test_param.tag_owner); 532 | 533 | mctp_binding_test_destroy(binding); 534 | mctp_destroy(mctp); 535 | } 536 | 537 | /* 538 | * This test case covers null destination eid. MCTP 539 | * daemon might query endpoint (i.e., Get Endpoint 540 | * ID command) by physical address requests and 541 | * destination eid as 0. Endpoint shall accept and 542 | * handle this request. 543 | */ 544 | static void mctp_core_test_rx_with_null_dst_eid() 545 | { 546 | struct mctp *mctp = NULL; 547 | struct mctp_binding_test *binding = NULL; 548 | struct test_params test_param; 549 | uint8_t test_payload[2 * MCTP_BTU]; 550 | struct pktbuf pktbuf; 551 | 552 | memset(test_payload, 0, sizeof(test_payload)); 553 | test_param.seen = false; 554 | test_param.message_size = 0; 555 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 556 | mctp_set_rx_all(mctp, rx_message, &test_param); 557 | memset(&pktbuf, 0, sizeof(pktbuf)); 558 | pktbuf.hdr.dest = TEST_DEST_NULL_EID; 559 | pktbuf.hdr.src = TEST_SRC_EID; 560 | 561 | /* Receive 2 fragments of equal size */ 562 | receive_two_fragment_message(binding, test_payload, MCTP_BTU, MCTP_BTU, 563 | &pktbuf); 564 | 565 | assert(test_param.seen); 566 | assert(test_param.message_size == 2 * MCTP_BTU); 567 | 568 | mctp_binding_test_destroy(binding); 569 | mctp_destroy(mctp); 570 | } 571 | 572 | /* 573 | * This test case covers Broadcast Request message (i.e., 574 | * `Endpoint Discovery` command). Endpoint shall accept 575 | * and handle this request. 576 | */ 577 | static void mctp_core_test_rx_with_broadcast_dst_eid() 578 | { 579 | struct mctp *mctp = NULL; 580 | struct mctp_binding_test *binding = NULL; 581 | struct test_params test_param; 582 | uint8_t test_payload[2 * MCTP_BTU]; 583 | struct pktbuf pktbuf; 584 | 585 | memset(test_payload, 0, sizeof(test_payload)); 586 | test_param.seen = false; 587 | test_param.message_size = 0; 588 | mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID); 589 | mctp_set_rx_all(mctp, rx_message, &test_param); 590 | memset(&pktbuf, 0, sizeof(pktbuf)); 591 | pktbuf.hdr.dest = TEST_DEST_BROADCAST_EID; 592 | pktbuf.hdr.src = TEST_SRC_EID; 593 | 594 | /* Receive 2 fragments of equal size */ 595 | receive_two_fragment_message(binding, test_payload, MCTP_BTU, MCTP_BTU, 596 | &pktbuf); 597 | 598 | assert(test_param.seen); 599 | assert(test_param.message_size == 2 * MCTP_BTU); 600 | 601 | mctp_binding_test_destroy(binding); 602 | mctp_destroy(mctp); 603 | } 604 | 605 | /* 606 | * This test case tests tag allocation. 8 tags 607 | * are allowed to be pending. 608 | */ 609 | static void mctp_core_test_tx_alloc_tag() 610 | { 611 | struct mctp *mctp = NULL; 612 | struct mctp_binding_test *binding = NULL; 613 | struct test_params test_param; 614 | uint8_t msg_tag; 615 | void *msg; 616 | int rc; 617 | mctp_eid_t dest_eid1 = 30; 618 | size_t msg_len = 10; 619 | 620 | mctp_test_stack_init(&mctp, &binding, dest_eid1); 621 | mctp_set_rx_all(mctp, rx_message, &test_param); 622 | 623 | uint8_t used = 0; 624 | for (int i = 0; i < 8; i++) { 625 | test_param.seen = false; 626 | test_param.msg_tag = 0xff; 627 | test_param.tag_owner = false; 628 | 629 | msg = __mctp_alloc(msg_len); 630 | memset(msg, 0x99, msg_len); 631 | rc = mctp_message_tx_request(mctp, dest_eid1, msg, msg_len, 632 | &msg_tag); 633 | assert(rc == 0); 634 | assert(test_param.seen == true); 635 | assert(test_param.msg_tag == msg_tag); 636 | assert(test_param.tag_owner == true); 637 | used |= (1 << msg_tag); 638 | } 639 | assert(used == 0xff); 640 | 641 | /* Ran out of tags */ 642 | test_param.seen = false; 643 | msg = __mctp_alloc(msg_len); 644 | memset(msg, 0x99, msg_len); 645 | rc = mctp_message_tx_request(mctp, dest_eid1, msg, msg_len, &msg_tag); 646 | assert(rc == -EBUSY); 647 | assert(test_param.seen == false); 648 | 649 | /* Send/Receive a response to one of those tags */ 650 | test_param.seen = false; 651 | msg = __mctp_alloc(msg_len); 652 | memset(msg, 0x99, msg_len); 653 | /* Arbitrary one */ 654 | uint8_t replied_tag = 3; 655 | rc = mctp_message_tx_alloced(mctp, dest_eid1, false, replied_tag, msg, 656 | msg_len); 657 | assert(rc == 0); 658 | assert(test_param.seen == true); 659 | assert(test_param.msg_tag == replied_tag); 660 | assert(test_param.tag_owner == false); 661 | 662 | /* Now sending allocates that tag again, since it is the only spare one */ 663 | test_param.seen = false; 664 | msg = __mctp_alloc(msg_len); 665 | memset(msg, 0x99, msg_len); 666 | rc = mctp_message_tx_request(mctp, dest_eid1, msg, msg_len, &msg_tag); 667 | assert(rc == 0); 668 | assert(test_param.seen == true); 669 | assert(msg_tag == replied_tag); 670 | 671 | mctp_binding_test_destroy(binding); 672 | mctp_destroy(mctp); 673 | } 674 | 675 | /* clang-format off */ 676 | #define TEST_CASE(test) { #test, test } 677 | static const struct { 678 | const char *name; 679 | void (*test)(void); 680 | } mctp_core_tests[] = { 681 | TEST_CASE(mctp_core_test_simple_rx), 682 | TEST_CASE(mctp_core_test_receive_equal_length_fragments), 683 | TEST_CASE(mctp_core_test_receive_unexpected_smaller_middle_fragment), 684 | TEST_CASE(mctp_core_test_receive_unexpected_bigger_middle_fragment), 685 | TEST_CASE(mctp_core_test_receive_smaller_end_fragment), 686 | TEST_CASE(mctp_core_test_receive_bigger_end_fragment), 687 | TEST_CASE(mctp_core_test_drop_large_fragments), 688 | TEST_CASE(mctp_core_test_exhaust_context_buffers), 689 | TEST_CASE(mctp_core_test_rx_with_tag), 690 | TEST_CASE(mctp_core_test_rx_with_tag_multifragment), 691 | TEST_CASE(mctp_core_test_rx_with_null_dst_eid), 692 | TEST_CASE(mctp_core_test_rx_with_broadcast_dst_eid), 693 | TEST_CASE(mctp_core_test_tx_alloc_tag), 694 | }; 695 | /* clang-format on */ 696 | 697 | int main(void) 698 | { 699 | uint8_t i; 700 | 701 | mctp_set_log_stdio(MCTP_LOG_DEBUG); 702 | 703 | static_assert(ARRAY_SIZE(mctp_core_tests) < SIZE_MAX, "size"); 704 | for (i = 0; i < ARRAY_SIZE(mctp_core_tests); i++) { 705 | mctp_prlog(MCTP_LOG_DEBUG, "begin: %s", 706 | mctp_core_tests[i].name); 707 | mctp_core_tests[i].test(); 708 | mctp_prlog(MCTP_LOG_DEBUG, "end: %s\n", 709 | mctp_core_tests[i].name); 710 | } 711 | 712 | return 0; 713 | } 714 | -------------------------------------------------------------------------------- /docs/bindings/vendor-ibm-astlpc.md: -------------------------------------------------------------------------------- 1 | # Management Component Transport Protocol (MCTP) LPC Transport Binding Specification for ASPEED BMC Systems 2 | 3 | ## Scope 4 | 5 | This design provides an efficient method to transfer MCTP packets between the 6 | host and BMC over the LPC bus on ASPEED BMC platforms. 7 | 8 | ## References 9 | 10 | The following referenced documents are indispensable for the application of this 11 | document. 12 | 13 | 1. DMTF DSP0236, Management Component Transport Protocol (MCTP) Base 14 | Specification 1.0, 15 | 16 | 17 | 2. Intel (R) Low Pin Count (LPC) Interface Specification 1.1, 18 | 19 | 20 | 3. IPMI Consortium, Intelligent Platform Management Interface Specification, 21 | v1.5 Revision 1.1 February 20, 2002, 22 | 23 | 24 | ## Definitions 25 | 26 | ### BTU: Baseline Transmission Unit 27 | 28 | Defined by the MCTP base specification as the smallest maximum packet size all 29 | MCTP-compliant endpoints must accept. 30 | 31 | ### IBF: Input Buffer Full 32 | 33 | A hardware-defined flag bit in a KCS device's Status Register (STR). The IBF 34 | flag indicates that a value has been written by the host to the corresponding 35 | Input Data Register (IDR). 36 | 37 | ### IDR: Input Data Register 38 | 39 | One of the three register interfaces exposed by a KCS device. The IDR is a one 40 | byte buffer which is written by the host and read by the BMC. 41 | 42 | ### KCS: Keyboard-Controller-Style 43 | 44 | A set of bit definitions and operation of the registers typically used in 45 | keyboard microcontrollers and embedded controllers. The term "Keyboard 46 | Controller Style" reflects that the register definition was originally used as 47 | the legacy "8742" keyboard controller interface in PC architecture computer 48 | systems. This interface is available built-in to several commercially available 49 | microcontrollers. Data is transferred across the KCS interface using a per-byte 50 | handshake. 51 | 52 | ### LPC Bus: Low Pin Count Bus 53 | 54 | A bus specification that implements ISA bus in a reduced physical form while 55 | extending ISA's capabilities. 56 | 57 | ### LPC FW: LPC Firmware Cycles 58 | 59 | LPC firmware cycles allow separate boot BIOS firmware memory cycles and 60 | application memory cycles with respect to the LPC bus. The ASPEED BMCs allow 61 | remapping of the LPC firmware cycles onto arbitrary regions of the BMC's 62 | physical address space, including RAM. 63 | 64 | ### MTU: Maximum Transmission Unit 65 | 66 | The largest payload the link will accept for a packet. The Maximum Transmission 67 | Unit represents a value that is at least as large as the BTU. Negotiation of MTU 68 | values larger than the BTU may improve throughput for data-intensive transfers. 69 | 70 | ### OBF: Output Buffer Full 71 | 72 | A hardware-defined flag bit in a KCS device's Status Register (STR). The OBF 73 | flag indicates that a value has been written by the BMC to the corresponding 74 | Output Data Register (ODR). 75 | 76 | ### ODR: Output Data Register 77 | 78 | One of the three register interfaces exposed by a KCS device. The ODR is a one 79 | byte buffer which is written by the BMC and read by the host. 80 | 81 | ### STR: Status Register 82 | 83 | One of the three register interfaces exposed by a KCS device. STR is a 84 | BMC-controlled, eight-bit register exposed to both the BMC and the host for 85 | indication of IBF and OBF events on the input (IDR) and output (ODR) buffers. 86 | Bits that are not defined by hardware can be software-controlled in a manner 87 | defined by a platform-specific ABI. 88 | 89 | ## Conventions 90 | 91 | Where unspecified, state, command and sequence descriptions apply to all 92 | versions of the protocol unless marked otherwise. 93 | 94 | ## MCTP over LPC Transport 95 | 96 | ### MCTP over LPC: Concepts 97 | 98 | The basic components used for the transfer are: 99 | 100 | - An interrupt mechanism using the IPMI KCS interface 101 | - A window of the LPC FW address space, where reads and writes are forwarded to 102 | BMC memory, using the LPC2AHB hardware 103 | 104 | In order to transfer a packet, either side of the channel (BMC or host) will: 105 | 106 | 1. Write the packet to the LPC FW window 107 | - The BMC will perform writes by writing to the memory backing the LPC window 108 | - The host will perform writes by writing to the LPC bus, at predefined 109 | addresses 110 | 2. Trigger an interrupt on the remote side, by writing to the KCS data buffer 111 | 112 | On this indication, the remote side will: 113 | 114 | 1. Read from the KCS status register, which shows that the single-byte KCS data 115 | buffer is full 116 | 2. Read the provided command from the KCS data buffer, acknowledging the 117 | interrupt 118 | 3. Read the MCTP packet from the LPC FW window 119 | 120 | ### MCTP over LPC: Scope 121 | 122 | The document limits itself to describing the operation of the binding protocol. 123 | The following issues of protocol ABI are considered out of scope: 124 | 125 | 1. The LPC IO address and Serial IRQ parameters of the KCS device 126 | 2. The concrete location of the control region in the LPC FW address space 127 | 128 | ### KCS Interface 129 | 130 | The KCS hardware on the ASPEED BMCs is used as a method of indicating, to the 131 | remote side, that a packet is ready to be transferred through the LPC FW 132 | mapping. 133 | 134 | The KCS hardware consists of two single-byte buffers: the Output Data Register 135 | (ODR) and the Input Data Register (IDR). The ODR is written by the BMC and read 136 | by the host. The IDR is the obverse. 137 | 138 | The KCS unit also contains a status register (STR), allowing both host and BMC 139 | to determine if there is data in the ODR or IDR. These are single-bit flags, 140 | designated Input/Output Buffer Full (IBF/OBF), and are automatically set by 141 | hardware when data has been written to the corresponding ODR/IDR buffer (and 142 | cleared when data has been read). 143 | 144 | While the IBF and OBF flags are managed in hardware, the remaining 145 | software-defined bits in the status register are used to carry other required 146 | protocol state. A problematic feature of the KCS status register is described in 147 | the IPMI specification, which states that an interrupt may be triggered on 148 | writes to the KCS status register but hardware implementations are not required 149 | to do so. Comparatively, writes to the data registers must set the corresponding 150 | buffer-full flag and invoke an interrupt. 151 | 152 | To ensure interrupts are generated for status updates, we exploit the OBF 153 | interrupt to signal a status update by writing a dummy command to ODR after 154 | updating the status register, as outlined below. 155 | 156 | ### LPC FW Window 157 | 158 | The window of BMC-memory-backed LPC FW address space has a predefined format, 159 | consisting of: 160 | 161 | - A control descriptor, describing static data about the rest of the window 162 | - A receive area for BMC-to-host packets 163 | - A transmit area, for host-to-BMC packets 164 | 165 | The control descriptor contains a version, and offset and size data for the 166 | transmit and receive areas. These offsets are relative to the start of the LPC 167 | FW window. 168 | 169 | Full definition of the control area is defined below, and it will be the base 170 | for all future versions. 171 | 172 | ```c 173 | struct mctp_lpcmap_hdr { 174 | uint32_t magic; 175 | 176 | uint16_t bmc_ver_min; 177 | uint16_t bmc_ver_cur; 178 | uint16_t host_ver_min; 179 | uint16_t host_ver_cur; 180 | uint16_t negotiated_ver; 181 | uint16_t pad0; 182 | 183 | uint32_t rx_offset; 184 | uint32_t rx_size; 185 | uint32_t tx_offset; 186 | uint32_t tx_size; 187 | } __attribute__((packed)); 188 | ``` 189 | 190 | The magic value marking the beginning of the control area is the ASCII encoding 191 | of "MCTP": 192 | 193 | ```c 194 | #define LPC_MAGIC 0x4d435450 195 | ``` 196 | 197 | All medium-specific metadata is in big-endian format. This includes: 198 | 199 | 1. Control area data 200 | 2. Medium-specific packet header fields 201 | 3. Medium-specific packet trailer fields 202 | 203 | MCTP packet data is transferred exactly as is presented, and no data escaping is 204 | performed. 205 | 206 | In all versions of the protocol, the transmit and receive areas contain a 207 | medium-specific header comprising a 32-bit payload length field, followed 208 | immediately by the MCTP packet data to be transferred. The full MCTP packet, 209 | including MCTP header, is considered to be the payload for the purpose of the 210 | header's length field. 211 | 212 | ```c 213 | struct mctp_lpcbuf_hdr { 214 | uint32_t length; 215 | } __attribute__((packed)); 216 | ``` 217 | 218 | A medium-specific packet trailer must immediately follow the payload. The length 219 | of the trailer is not accounted for in the length field of the medium-specific 220 | packet header: The length of the trailer is implied by the negotiated protocol 221 | version. 222 | 223 | For protocol versions 1 and 2, the medium-specific trailer length is zero. 224 | 225 | For protocol version 3, the medium-specific trailer comprises a CRC-32 checksum 226 | of the payload. 227 | 228 | ```c 229 | struct mctp_lpcbuf_tlr { 230 | uint32_t crc32; 231 | } __attribute__((packed)); 232 | ``` 233 | 234 | Where the CRC-32 implementation is defined by the following characteristics (or 235 | equivalent): 236 | 237 | 1. The polynomial 238 | `x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1` 239 | 2. Initialising the remainder state to `2^32 - 1` 240 | 3. Incrementally shifting and `XOR`ing data bytes through the reversed 241 | polynomial representation `0xEDB88320` 242 | 4. `XOR`ing the calculated remainder with `2^32 - 1` 243 | 244 | For all defined versions, only a single MCTP packet is present in the Rx and Tx 245 | areas. This may change for future versions of the protocol. 246 | 247 | #### Negotiation of the Maximum Transmission Unit 248 | 249 | Version 1 of the protocol offers no mechanism for negotiation of the maximum 250 | transmission unit. The Rx and Tx buffers must be sized to accommodate packets up 251 | to the Baseline Transmission Unit, and the implementation assumes that the MTU 252 | is set to the BTU regardless of the values of `rx_size` and `tx_size`. 253 | 254 | Version 2 of the protocol exploits the `rx_size` and `tx_size` fields in the 255 | control region to negotiate the link MTU. Note that at the time that the MTU is 256 | under negotiation the protocol version has not been finalised, so the process is 257 | necessarily backwards-compatible. 258 | 259 | The relevant property that each endpoint must control is the MTU of packets it 260 | will receive, as this governs how the remote endpoint's packetisation impacts 261 | memory pressure at the local endpoint. As such while the BMC MUST populate 262 | `rx_size` for backwards compatibility with version 1, the host MAY write 263 | `rx_size` without regard for its current value if the host supports version 2. 264 | The BMC controls the value of `tx_size`, and MAY choose to adjust it in response 265 | to the host's proposed `rx_size` value. As such, when `Channel Active` is set by 266 | the BMC, the host MUST read both `rx_size` and `tx_size` in response to ensure 267 | both the BMC and the host have a consistent understanding of the MTU in each 268 | direction. It is convention for `rx_size` and `tx_size` to be set to the same 269 | value by the BMC as part of finalising the channel, though it is not invalid to 270 | have asymmetric MTUs. 271 | 272 | For all protocol versions, the following properties must be upheld for the Rx 273 | and Tx buffers to be considered valid: 274 | 275 | - Intersect neither eachother nor the control region 276 | - Not extend beyond the window allocated to MCTP in the LPC FW address space 277 | - Must accommodate at least BTU-sized payloads 278 | 279 | The BMC MAY choose to fail channel initialisation if these properties are 280 | violated in the negotiation process. 281 | 282 | ### KCS Status and Control Sequences 283 | 284 | The KCS status flags and command set govern the state of the protocol, defining 285 | the ability to send and receive packets on the LPC bus. 286 | 287 | #### KCS Status Register Layout 288 | 289 | | Bit | Managed By | Description | 290 | | ------- | ---------- | ---------------------------------- | 291 | | 7 (MSB) | Software | BMC Active | 292 | | 6 | Software | Channel active, version negotiated | 293 | | 5 | Software | Unused | 294 | | 4 | Software | Unused | 295 | | 3 | Hardware | Command / Data | 296 | | 2 | Software | Unused | 297 | | 1 | Hardware | Input Buffer Full | 298 | | 0 (LSB) | Hardware | Output Buffer Full | 299 | 300 | #### KCS Data Register Commands 301 | 302 | | Command | Description | 303 | | ------- | ----------- | 304 | | 0x00 | Initialise | 305 | | 0x01 | Tx Begin | 306 | | 0x02 | Rx Complete | 307 | | 0xff | Dummy Value | 308 | 309 | #### Host Command to BMC Sequence 310 | 311 | The host sends commands to the BMC to signal channel initialisation, begin 312 | transmission of a packet, or to complete reception of a packet. 313 | 314 | | Step | Description | 315 | | ---- | ------------------------------------------------------- | 316 | | 1 | The host writes a command value to IDR | 317 | | 2 | The hardware sets IBF, which triggers a BMC interrupt | 318 | | 3 | The BMC reads the status register for IBF | 319 | | 4 | If IBF is set, the BMC reads the host command from IDR | 320 | | 5 | The interrupt is acknowledged by the data register read | 321 | 322 | #### BMC Command to Host Sequence 323 | 324 | The BMC sends commands to the host to begin transmission of a packet or to 325 | complete reception of a packet. 326 | 327 | | Step | Description | 328 | | ---- | ------------------------------------------------------- | 329 | | 1 | The BMC writes a command value to ODR | 330 | | 2 | The hardware sets OBF, which triggers a host interrupt | 331 | | 3 | The host reads the status register for OBF | 332 | | 4 | If OBF is set, the host reads the BMC command from ODR | 333 | | 5 | The interrupt is acknowledged by the data register read | 334 | 335 | #### BMC Status Update Sequence 336 | 337 | The BMC sends status updates to the host to signal loss of function, loss of 338 | channel state, or the presence of a command in the KCS data register. 339 | 340 | | Step | Description | 341 | | ---- | -------------------------------------------------------------- | 342 | | 1 | The BMC writes the status value to the status register | 343 | | 2 | The BMC writes the dummy command to ODR | 344 | | 3 | The hardware sets OBF, which triggers a host interrupt | 345 | | 4 | If OBF is set, the host reads the BMC command from ODR | 346 | | 5 | The interrupt is acknowledged by the data register read | 347 | | 6 | The host observes the command is the dummy command | 348 | | 7 | The host reads the status register to capture the state change | 349 | 350 | #### LPC Window Ownership and Synchronisation 351 | 352 | Because the LPC FW window is shared between the host and the BMC we need strict 353 | rules on which entity is allowed to access it at specific times. 354 | 355 | Firstly, we have rules for modification: 356 | 357 | - The control data is only written during initialisation. The control area is 358 | never modified once the channel is active. 359 | - Only the BMC may write to the Rx buffer described in the control area 360 | - Only the host may write to the Tx buffer described in the control area 361 | 362 | During packet transmission, the follow sequence occurs: 363 | 364 | 1. The Tx side writes the packet to its Tx buffer 365 | 2. The Tx side sends a `Tx Begin` message, indicating that the buffer ownership 366 | is transferred 367 | 3. The Rx side now owns the buffer, and reads the message from its Rx area 368 | 4. The Rx side sends a `Rx Complete` once done, indicating that the buffer 369 | ownership is transferred back to the Tx side. 370 | 371 | ### LPC Binding Operation 372 | 373 | The binding operation is not symmetric as the BMC is the only side that can 374 | drive the status register. Each side's initialisation sequence is outlined 375 | below. 376 | 377 | The sequences below contain steps where the BMC updates the channel status and 378 | where commands are sent between the BMC and the host. The act of updating status 379 | or sending a command invokes the behaviour outlined in 380 | [KCS Control](#kcs-status-and-control-sequences). 381 | 382 | The packet transmission sequences assume that `BMC Active` and `Channel Active` 383 | are set. 384 | 385 | #### BMC Initialisation Sequence 386 | 387 | | Step | Description | 388 | | ---- | ------------------------------------------------------------------------------------- | 389 | | 1 | The BMC initialises the control area: magic value, BMC versions and buffer parameters | 390 | | 2 | The BMC sets the status to `BMC Active` | 391 | 392 | #### Host Initialisation Sequence 393 | 394 | | Step | v1 | v2 | v3 | Description | 395 | | ---- | --- | --- | --- | ---------------------------------------------------------------------------------------- | 396 | | 1 | ✓ | ✓ | ✓ | The host waits for the `BMC Active` state | 397 | | 2 | ✓ | ✓ | ✓ | The host populates the its version fields | 398 | | 3 | | ✓ | ✓ | The host derives and writes to `rx_size` the packet size associated with its desired MTU | 399 | | 4 | ✓ | ✓ | ✓ | The host sends the `Initialise` command | 400 | | 5 | ✓ | ✓ | ✓ | The BMC observes the `Initialise` command | 401 | | 6 | ✓ | ✓ | ✓ | The BMC calculates and writes `negotiated_ver` | 402 | | 7 | | ✓ | ✓ | The BMC calculates the MTUs and updates neither, one or both of `rx_size` and `tx_size` | 403 | | 8 | ✓ | ✓ | ✓ | The BMC sets the status to `Channel Active` | 404 | | 9 | ✓ | ✓ | ✓ | The host observes that `Channel Active` is set | 405 | | 10 | ✓ | ✓ | ✓ | The host reads the negotiated version | 406 | | 11 | | ✓ | ✓ | The host reads both `rx_size` and `tx_size` to derive the negotiated MTUs | 407 | 408 | #### Host Packet Transmission Sequence 409 | 410 | | Step | v1 | v2 | v3 | Description | 411 | | ---- | --- | --- | --- | ---------------------------------------------------------------------------------------------- | 412 | | 1 | | | ✓ | The host calculates the CRC-32 over the packet data | 413 | | 2 | ✓ | ✓ | ✓ | The host waits on any previous `Rx Complete` message | 414 | | 3 | ✓ | ✓ | ✓ | The host writes the packet data and medium-specific metadata to its Tx area (BMC Rx area) | 415 | | 4 | ✓ | ✓ | ✓ | The host sends the `Tx Begin` command, transferring ownership of its Tx buffer to the BMC | 416 | | 5 | ✓ | ✓ | ✓ | The BMC observes the `Tx Begin` command | 417 | | 6 | ✓ | ✓ | ✓ | The BMC reads the packet data and medium-specific metadata from the its Rx area (host Tx area) | 418 | | 7 | ✓ | ✓ | ✓ | The BMC sends the `Rx Complete` command, transferring ownership of its Rx buffer to the host | 419 | | 8 | ✓ | ✓ | ✓ | The host observes the `Rx Complete` command | 420 | | 9 | | | ✓ | The BMC validates the provided CRC-32 over the packet data | 421 | 422 | #### BMC Packet Transmission Sequence 423 | 424 | | Step | v1 | v2 | v3 | Description | 425 | | ---- | --- | --- | --- | ----------------------------------------------------------------------------------------------- | 426 | | 1 | | | ✓ | The BMC calculates the CRC-32 over the packet data | 427 | | 2 | ✓ | ✓ | ✓ | The BMC waits on any previous `Rx Complete` message | 428 | | 3 | ✓ | ✓ | ✓ | The BMC writes the packet data and medium-specific metadata to its Tx area (host Rx area) | 429 | | 4 | ✓ | ✓ | ✓ | The BMC sends the `Tx Begin` command, transferring ownership of its Tx buffer to the host | 430 | | 5 | ✓ | ✓ | ✓ | The host observes the `Tx Begin` command | 431 | | 6 | ✓ | ✓ | ✓ | The host reads the packet data and medium-specific metadata from the host Rx area (BMC Tx area) | 432 | | 7 | ✓ | ✓ | ✓ | The host sends the `Rx Complete` command, transferring ownership of its Rx buffer to the BMC | 433 | | 8 | ✓ | ✓ | ✓ | The BMC observes the `Rx Complete` command | 434 | | 9 | | | ✓ | The host validates the provided CRC-32 over the packet data | 435 | 436 | ## Implementation Notes 437 | 438 | On the BMC the initial prototype implementation makes use of the following 439 | components: 440 | 441 | - An LPC KCS device exposed by a [binding-specific kernel driver][mctp-driver] 442 | - The reserved memory mapped by the LPC2AHB bridge via the [aspeed-lpc-ctrl 443 | driver][aspeed-lpc-ctrl] 444 | - The astlpc binding found in [libmctp][libmctp] 445 | 446 | [mctp-driver]: 447 | https://github.com/openbmc/linux/commit/9a3b539a175cf4fe1f8fc2997e8a91abec25c37f 448 | [aspeed-lpc-ctrl]: 449 | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/soc/aspeed/aspeed-lpc-ctrl.c?h=v5.7 450 | [libmctp]: https://github.com/openbmc/libmctp 451 | 452 | From the host side, the LPC Firmware and KCS IO cycles are driven by 453 | free-standing firmware. Some firmwares exploit [libmctp][libmctp] by 454 | implementing the driver hooks for direct access to the LPC devices. 455 | 456 | ## Alternatives Considered 457 | 458 | ### The KCS MCTP Binding (DSP0254) 459 | 460 | The KCS hardware (used as the full transfer channel) can be used to transfer 461 | arbitrarily-sized MCTP messages. However, there are much larger overheads in 462 | synchronisation between host and BMC for every byte transferred. 463 | 464 | ### The MCTP Serial Binding (DSP0253) 465 | 466 | We could use the VUART hardware to transfer the MCTP packets according to the 467 | existing MCTP Serial Binding. However, the VUART device is already used for 468 | console data. Multiplexing both MCTP and console would be an alternative, but 469 | the complexity introduced would make low-level debugging both more difficult and 470 | less reliable. 471 | 472 | ### The BT interface 473 | 474 | The BT interface allows for block-at-time transfers. However, the BT buffer size 475 | is only 64 bytes on the AST2500 hardware, which does not allow us to comply with 476 | the MCTP Base Specification (DSP0236) that requires a 64-byte payload size as 477 | the minimum. The 64-byte BT buffer does not allow for MCTP and transport 478 | headers. 479 | 480 | Additionally, we would like to develop the MCTP channel alongside the existing 481 | IPMI interfaces, to allow a gradual transition from IPMI to MCTP. As the BT 482 | channel is already used on OpenPOWER systems for IPMI transfers, we would not be 483 | able to support both in parallel. 484 | 485 | ### Using the AST2500 LPC Mailbox 486 | 487 | This would require enabling the SuperIO interface, which allows the host to 488 | access the entire BMC address space, and so introduces security vulnerabilities. 489 | --------------------------------------------------------------------------------