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: Tom Burdick <thomas.burdick@intel.com>
This commit is contained in:
Tom Burdick 2025-04-29 09:34:20 -05:00 committed by Alberto Escolar
parent 6464329346
commit cbfe7813c7
5 changed files with 355 additions and 1 deletions

View File

@ -0,0 +1,28 @@
# 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.

View File

@ -0,0 +1,91 @@
/*
* 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_ */

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,225 @@
/*
* 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;
}