From cbfe7813c7791889102de3b4fd7b2ca7c95d2142 Mon Sep 17 00:00:00 2001 From: Tom Burdick Date: Tue, 29 Apr 2025 09:34:20 -0500 Subject: [PATCH] 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 --- dts/bindings/pmci/mctp/i2c_gpio_target.yaml | 28 +++ .../zephyr/pmci/mctp/mctp_i2c_gpio_target.h | 91 +++++++ subsys/pmci/mctp/CMakeLists.txt | 1 + subsys/pmci/mctp/Kconfig | 11 +- subsys/pmci/mctp/mctp_i2c_gpio_target.c | 225 ++++++++++++++++++ 5 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 dts/bindings/pmci/mctp/i2c_gpio_target.yaml create mode 100644 include/zephyr/pmci/mctp/mctp_i2c_gpio_target.h create mode 100644 subsys/pmci/mctp/mctp_i2c_gpio_target.c diff --git a/dts/bindings/pmci/mctp/i2c_gpio_target.yaml b/dts/bindings/pmci/mctp/i2c_gpio_target.yaml new file mode 100644 index 00000000000..05ac662621a --- /dev/null +++ b/dts/bindings/pmci/mctp/i2c_gpio_target.yaml @@ -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. diff --git a/include/zephyr/pmci/mctp/mctp_i2c_gpio_target.h b/include/zephyr/pmci/mctp/mctp_i2c_gpio_target.h new file mode 100644 index 00000000000..283b3628450 --- /dev/null +++ b/include/zephyr/pmci/mctp/mctp_i2c_gpio_target.h @@ -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 +#include +#include +#include +#include +#include +#include + +/** + * @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_ */ diff --git a/subsys/pmci/mctp/CMakeLists.txt b/subsys/pmci/mctp/CMakeLists.txt index 7c34a53d643..aa32c208f00 100644 --- a/subsys/pmci/mctp/CMakeLists.txt +++ b/subsys/pmci/mctp/CMakeLists.txt @@ -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) diff --git a/subsys/pmci/mctp/Kconfig b/subsys/pmci/mctp/Kconfig index 8937124bd2e..1d70ccc7b24 100644 --- a/subsys/pmci/mctp/Kconfig +++ b/subsys/pmci/mctp/Kconfig @@ -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 diff --git a/subsys/pmci/mctp/mctp_i2c_gpio_target.c b/subsys/pmci/mctp/mctp_i2c_gpio_target.c new file mode 100644 index 00000000000..bd3525e223d --- /dev/null +++ b/subsys/pmci/mctp/mctp_i2c_gpio_target.c @@ -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 +#include +#include +#include +#include + +#include +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; +}