Commit cbfe7813 authored by Tom Burdick's avatar Tom Burdick Committed by Alberto Escolar
Browse files

pmci: mctp: I2C+GPIO Target binding



Adds a I2C+GPIO Target device binding for MCTP communication over I2C.

The binding requires an i2c bus and gpio pin, along with a specified I2C
and endpoint address pair. These are then used to create an MCTP binding
which can be used to communicate in a peer to peer manner among other
MCTP endpoints.

Each message transmit signals to the bus controller using a GPIO logical
high and is unset on transmission completion. Pending transmitters are
queued using a semaphore avoiding memcpy being needed to asynchronously
transmit mctp pktbufs.

Signed-off-by: default avatarTom Burdick <thomas.burdick@intel.com>
parent 64643293
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
# Copyright (c) 2025 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

description: |
  A configuration for MCTP bindings to an I2C target with GPIO.

compatible: "zephyr,mctp-i2c-gpio-target"

properties:
  i2c:
    type: phandle
    description: |
      I2C peripheral that is used as an MCTP bus with the target mode API.

  i2c-addr:
    type: int
    description: |
      I2C address of endpoint

  endpoint-id:
    type: int
    description: |
      MCTP endpoint id

  endpoint-gpios:
    type: phandle-array
    description: |
      GPIO connected to the endpoint.
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2025 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 */

#ifndef ZEPHYR_MCTP_I2C_GPIO_TARGET_H_
#define ZEPHYR_MCTP_I2C_GPIO_TARGET_H_

#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/pmci/mctp/mctp_i2c_gpio_common.h>
#include <libmctp.h>

/**
 * @brief An MCTP binding for Zephyr's I2C target interface using GPIO
 */
struct mctp_binding_i2c_gpio_target {
	/** @cond INTERNAL_HIDDEN */
	struct mctp_binding binding;
	const struct device *i2c;
	struct i2c_target_config i2c_target_cfg;
	uint8_t endpoint_id;
	const struct gpio_dt_spec endpoint_gpio;
	uint8_t reg_addr;
	bool rxtx;
	uint8_t rx_idx;
	struct mctp_pktbuf *rx_pkt;
	struct k_sem *tx_lock;
	struct k_sem *tx_complete;
	uint8_t tx_idx;
	struct mctp_pktbuf *tx_pkt;
	/** @endcond INTERNAL_HIDDEN */
};

/** @cond INTERNAL_HIDDEN */
extern const struct i2c_target_callbacks mctp_i2c_gpio_target_callbacks;
int mctp_i2c_gpio_target_start(struct mctp_binding *binding);
int mctp_i2c_gpio_target_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt);
/** @endcond INTERNAL_HIDDEN */

/**
 * @brief Define a MCTP bus binding for I2C target with GPIO
 *
 * Rather than mode switching as the MCTP standard wishes, this is a custom
 * binding. On the target side a gpio pin is set active (could be active low or
 * active high) when writes are pending. It's expected the controller then
 * reads from the I2C target device at some point in the future. Reads are
 * accepted at any time and are expected to contain full mctp packets.
 *
 * In effect each device has a pair of pseudo registers for reading or writing
 * as a packet FIFO.
 *
 * Thus the sequence for a I2C target to send a message would be...
 *
 * 1. Setup a length and message value (pseudo registers).
 * 2. Signal the controller with a GPIO level set
 * 3. The controller then is expected to read the length (write of addr 0 + read 1 byte)
 * 4. The controller then is expected to read the message (write of addr 1 + read N bytes)
 * 5. The target clears the pin set
 * 6. The controller reports a message receive to MCTP
 *
 * @param _name Symbolic name of the bus binding variable
 * @param _node_id DeviceTree Node containing the configuration of this MCTP binding
 */
#define MCTP_I2C_GPIO_TARGET_DT_DEFINE(_name, _node_id)                                            \
	K_SEM_DEFINE(_name ##_tx_lock, 1, 1);                                                      \
	K_SEM_DEFINE(_name ##_tx_complete, 0, 1);                                                  \
	struct mctp_binding_i2c_gpio_target _name = {                                              \
		.binding = {                                                                       \
			.name = STRINGIFY(_name), .version = 1,                                    \
			.start = mctp_i2c_gpio_target_start,                                       \
			.tx = mctp_i2c_gpio_target_tx,                                             \
			.pkt_size = MCTP_I2C_GPIO_MAX_PKT_SIZE,                                    \
		},                                                                                 \
		.i2c = DEVICE_DT_GET(DT_PHANDLE(_node_id, i2c)),                                   \
		.i2c_target_cfg = {                                                                \
			.address = DT_PROP(_node_id, i2c_addr),                                    \
			.callbacks = &mctp_i2c_gpio_target_callbacks,                              \
		},                                                                                 \
		.endpoint_id = DT_PROP(_node_id, endpoint_id),                                     \
		.endpoint_gpio = GPIO_DT_SPEC_GET(_node_id, endpoint_gpios),                       \
		.tx_lock = &_name##_tx_lock,                                                       \
		.tx_complete = &_name##_tx_complete,                                               \
	};

#endif /* ZEPHYR_MCTP_I2C_GPIO_TARGET_H_ */
+1 −0
Original line number Diff line number Diff line
@@ -2,3 +2,4 @@ zephyr_library()
zephyr_library_sources(mctp_memory.c)
zephyr_library_sources_ifdef(CONFIG_MCTP_UART mctp_uart.c)
zephyr_library_sources_ifdef(CONFIG_MCTP_I2C_GPIO_CONTROLLER mctp_i2c_gpio_controller.c)
zephyr_library_sources_ifdef(CONFIG_MCTP_I2C_GPIO_TARGET mctp_i2c_gpio_target.c)
+10 −1
Original line number Diff line number Diff line
@@ -32,7 +32,16 @@ config MCTP_I2C_GPIO_CONTROLLER
	depends on GPIO
	help
	  Build the MCTP I2C+GPIO controller binding to use MCTP over Zephyr's I2C RTIO
	  interface and GPIO interrupts.
	  interface and GPIO interrupts from targets to signal their request to write.

config MCTP_I2C_GPIO_TARGET
	bool "MCTP I2C+GPIO Target Binding"
	depends on I2C
	depends on GPIO
	help
	  Build the MCTP I2C+GPIO target binding to use MCTP over Zephyr's I2C target
	  interface and GPIO to signal writes to the bus controller.


module = MCTP
module-str = MCTP
+225 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2025 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 */

#include "zephyr/pmci/mctp/mctp_i2c_gpio_common.h"
#include <zephyr/sys/__assert.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/pmci/mctp/mctp_i2c_gpio_target.h>
#include <crc-16-ccitt.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(mctp_i2c_gpio_target, CONFIG_MCTP_LOG_LEVEL);

int mctp_i2c_gpio_target_write_requested(struct i2c_target_config *config)
{

	struct mctp_binding_i2c_gpio_target *b =
		CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);

	if (b->rxtx || b->reg_addr == MCTP_I2C_GPIO_INVALID_ADDR) {
		/* Reset our state */
		b->reg_addr = MCTP_I2C_GPIO_INVALID_ADDR;
		b->rxtx = false;
		b->rx_idx = 0;
	}

	return 0;
}

int mctp_i2c_gpio_target_write_received(struct i2c_target_config *config, uint8_t val)
{
	struct mctp_binding_i2c_gpio_target *b =
		CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);
	int ret = 0;

	switch (b->reg_addr) {
	case MCTP_I2C_GPIO_INVALID_ADDR:
		b->rxtx = false;
		b->reg_addr = val;
		break;
	case MCTP_I2C_GPIO_RX_MSG_LEN_ADDR:
		b->rxtx = true;
		b->rx_pkt = mctp_pktbuf_alloc(&b->binding, (size_t)val);
		break;
	case MCTP_I2C_GPIO_RX_MSG_ADDR:
		b->rxtx = true;
		b->rx_pkt->data[b->rx_idx] = val;
		b->rx_idx += 1;

		/* buffer full */
		if (b->rx_idx >= b->rx_pkt->size) {
			ret = -ENOMEM;
		}
		break;
	default:
		LOG_ERR("Write when reg_addr is %d", b->reg_addr);
		ret = -EIO;
		break;
	}

	return ret;
}

int mctp_i2c_gpio_target_read_requested(struct i2c_target_config *config, uint8_t *val)
{
	struct mctp_binding_i2c_gpio_target *b =
		CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);
	uint8_t pkt_len;
	int ret = 0;

	switch (b->reg_addr) {
	case MCTP_I2C_GPIO_TX_MSG_LEN_ADDR:
		b->rxtx = true;
		if (b->tx_pkt == NULL) {
			LOG_WRN("empty packet?");
			pkt_len = 0;
		} else {
			pkt_len = (uint8_t)(b->tx_pkt->end - b->tx_pkt->start);
		}
		*val = pkt_len;
		break;
	case MCTP_I2C_GPIO_TX_MSG_ADDR:
		b->rxtx = true;
		*val = b->tx_pkt->data[b->tx_pkt->start];
		b->tx_idx = b->tx_pkt->start;
		break;
	default:
		LOG_WRN("invalid rre reg %d", b->reg_addr);
		ret = -EINVAL;
	}

	return ret;
}

int mctp_i2c_gpio_target_read_processed(struct i2c_target_config *config, uint8_t *val)
{
	struct mctp_binding_i2c_gpio_target *b =
		CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);

	b->tx_idx += 1;

	if (b->reg_addr != MCTP_I2C_GPIO_TX_MSG_ADDR) {
		goto out;
	}

	if (b->tx_idx > b->tx_pkt->end) {
		LOG_WRN("rrp past end reg %d", b->reg_addr);
		return -EIO;
	}

	*val = b->tx_pkt->data[b->tx_idx];

out:
	return 0;
}

int mctp_i2c_gpio_target_stop(struct i2c_target_config *config)
{
	struct mctp_binding_i2c_gpio_target *b =
		CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);
	uint8_t pkt_len;

	if (b->rxtx) {
		switch (b->reg_addr) {
		case MCTP_I2C_GPIO_TX_MSG_ADDR:
			pkt_len = (b->tx_pkt->end - b->tx_pkt->start);
			if (b->tx_idx < pkt_len - 1) {
				LOG_WRN("Only %d of %d bytes of the transmit packet were read",
					b->tx_idx, pkt_len);
			}
			b->tx_pkt = NULL;
			k_sem_give(b->tx_complete);
			break;

		case MCTP_I2C_GPIO_RX_MSG_ADDR:
			LOG_DBG("stop rx msg, give pkt");
			/* Give message to mctp to process */
			mctp_bus_rx(&b->binding, b->rx_pkt);
			b->rx_pkt = NULL;
			break;
		case MCTP_I2C_GPIO_RX_MSG_LEN_ADDR:
		case MCTP_I2C_GPIO_TX_MSG_LEN_ADDR:
			break;
		default:
			LOG_WRN("unexpected stop for reg %d", b->reg_addr);
			break;
		}
	}

	return 0;
}

const struct i2c_target_callbacks mctp_i2c_gpio_target_callbacks = {
	.write_requested = mctp_i2c_gpio_target_write_requested,
	.read_requested = mctp_i2c_gpio_target_read_requested,
	.write_received = mctp_i2c_gpio_target_write_received,
	.read_processed = mctp_i2c_gpio_target_read_processed,
	.stop = mctp_i2c_gpio_target_stop,
};

/*
 * libmctp wants us to return once the packet is sent not before
 * so the entire process of flagging the tx with gpio, waiting on the read,
 * needs to complete before we can move on.
 *
 * this is called for each packet in the packet queue libmctp provides
 */
int mctp_i2c_gpio_target_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt)
{
	struct mctp_binding_i2c_gpio_target *b =
		CONTAINER_OF(binding, struct mctp_binding_i2c_gpio_target, binding);
	int rc;

	k_sem_take(b->tx_lock, K_FOREVER);

	b->tx_pkt = pkt;

	rc = gpio_pin_set_dt(&b->endpoint_gpio, 1);
	if (rc != 0) {
		LOG_ERR("failed to set gpio pin");
		b->tx_pkt = NULL;
		goto out;
	}

	k_sem_take(b->tx_complete, K_FOREVER);

	rc = gpio_pin_set_dt(&b->endpoint_gpio, 0);
	if (rc != 0) {
		LOG_ERR("failed to clear gpio pin");
	}

out:
	k_sem_give(b->tx_lock);
	return rc;
}

int mctp_i2c_gpio_target_start(struct mctp_binding *binding)
{
	struct mctp_binding_i2c_gpio_target *b =
		CONTAINER_OF(binding, struct mctp_binding_i2c_gpio_target, binding);
	int rc;

	/* Register i2c target */
	rc = i2c_target_register(b->i2c, &b->i2c_target_cfg);
	if (rc != 0) {
		LOG_ERR("failed to register i2c target");
		goto out;
	}

	/* Configure pin to use as data ready signaling */
	rc = gpio_pin_configure_dt(&b->endpoint_gpio, GPIO_OUTPUT_INACTIVE);
	if (rc != 0) {
		LOG_ERR("failed to configure gpio");
		goto out;
	}

	mctp_binding_set_tx_enabled(binding, true);

out:
	return 0;
}