| /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "libmctp.h" |
| #include "libmctp-alloc.h" |
| #include "libmctp-log.h" |
| #include "container_of.h" |
| #include "libmctp-i2c.h" |
| #include "i2c-internal.h" |
| |
| static const uint8_t MCTP_I2C_COMMAND = 0x0f; |
| |
| #define binding_to_i2c(b) container_of(b, struct mctp_binding_i2c, binding) |
| |
| static bool mctp_i2c_valid_addr(uint8_t addr) |
| { |
| return addr <= 0x7f; |
| } |
| |
| static bool mctp_i2c_valid_eid(uint8_t eid) |
| { |
| /* Disallow reserved range */ |
| return eid >= 8 && eid < 0xff; |
| } |
| |
| static int mctp_i2c_core_start(struct mctp_binding *binding) |
| { |
| mctp_binding_set_tx_enabled(binding, true); |
| return 0; |
| } |
| |
| /* Returns 0 if an entry is found, or -ENOENT otherwise. |
| * The last seen timestamp will be updated for found entries */ |
| static int mctp_i2c_neigh_get(struct mctp_binding_i2c *i2c, uint8_t eid, |
| uint8_t *ret_neigh_addr) |
| { |
| for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) { |
| struct mctp_i2c_neigh *n = &i2c->neigh[i]; |
| if (n->used && n->eid == eid) { |
| n->last_seen_timestamp = mctp_now(i2c->binding.mctp); |
| *ret_neigh_addr = n->addr; |
| return 0; |
| } |
| } |
| return -ENOENT; |
| } |
| |
| /* Adds a new neighbour entry. If the table is full, the oldest |
| * entry will be evicted. If eid already exists, that entry will |
| * be replaced. */ |
| static void mctp_i2c_neigh_add(struct mctp_binding_i2c *i2c, uint8_t eid, |
| uint8_t addr) |
| { |
| assert(addr <= 0x7f); |
| struct mctp_i2c_neigh *entry = NULL; |
| for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) { |
| struct mctp_i2c_neigh *n = &i2c->neigh[i]; |
| if (!n->used) { |
| /* Spare entry, use it */ |
| entry = n; |
| break; |
| } |
| |
| if (n->eid == eid) { |
| /* Replacing existing entry */ |
| entry = n; |
| break; |
| } |
| |
| if (!entry || |
| n->last_seen_timestamp < entry->last_seen_timestamp) { |
| /* Use this as the provisional oldest, keep iterating */ |
| entry = n; |
| } |
| } |
| assert(entry); |
| |
| entry->addr = addr; |
| entry->eid = eid; |
| entry->used = true; |
| entry->last_seen_timestamp = mctp_now(i2c->binding.mctp); |
| } |
| |
| static int mctp_binding_i2c_tx(struct mctp_binding *b, struct mctp_pktbuf *pkt) |
| { |
| struct mctp_binding_i2c *i2c = binding_to_i2c(b); |
| struct mctp_hdr *hdr = mctp_pktbuf_hdr(pkt); |
| int rc; |
| uint8_t neigh_addr; |
| |
| rc = mctp_i2c_neigh_get(i2c, hdr->dest, &neigh_addr); |
| if (rc) { |
| return rc; |
| } |
| |
| struct mctp_i2c_hdr *i2c_hdr = |
| mctp_pktbuf_alloc_start(pkt, sizeof(struct mctp_i2c_hdr)); |
| i2c_hdr->dest = neigh_addr << 1; |
| i2c_hdr->cmd = MCTP_I2C_COMMAND; |
| size_t bytecount = mctp_pktbuf_size(pkt) - |
| (offsetof(struct mctp_i2c_hdr, bytecount) + 1); |
| if (bytecount > 0xff) { |
| return -EINVAL; |
| } |
| i2c_hdr->bytecount = bytecount; |
| i2c_hdr->source = i2c->own_addr << 1 | 1; |
| |
| rc = i2c->tx_fn(pkt->data + pkt->start, mctp_pktbuf_size(pkt), |
| i2c->tx_ctx); |
| switch (rc) { |
| case -EMSGSIZE: |
| case 0: |
| break; |
| case -EBUSY: |
| default: |
| mctp_binding_set_tx_enabled(&i2c->binding, false); |
| } |
| return rc; |
| } |
| |
| int mctp_i2c_set_neighbour(struct mctp_binding_i2c *i2c, uint8_t eid, |
| uint8_t addr) |
| { |
| if (!mctp_i2c_valid_eid(eid)) { |
| return -EINVAL; |
| } |
| if (!mctp_i2c_valid_addr(addr)) { |
| return -EINVAL; |
| } |
| |
| mctp_i2c_neigh_add(i2c, eid, addr); |
| return 0; |
| } |
| |
| int mctp_i2c_setup(struct mctp_binding_i2c *i2c, uint8_t own_addr, |
| mctp_i2c_tx_fn tx_fn, void *tx_ctx) |
| { |
| int rc; |
| |
| memset(i2c, 0x0, sizeof(*i2c)); |
| |
| rc = mctp_i2c_set_address(i2c, own_addr); |
| if (rc) { |
| return rc; |
| } |
| |
| i2c->binding.name = "i2c"; |
| i2c->binding.version = 1; |
| i2c->binding.pkt_size = MCTP_PACKET_SIZE(I2C_BTU); |
| i2c->binding.pkt_header = sizeof(struct mctp_i2c_hdr); |
| i2c->binding.tx_storage = i2c->tx_storage; |
| |
| i2c->binding.start = mctp_i2c_core_start; |
| i2c->binding.tx = mctp_binding_i2c_tx; |
| |
| i2c->tx_fn = tx_fn; |
| i2c->tx_ctx = tx_ctx; |
| |
| return 0; |
| } |
| |
| int mctp_i2c_set_address(struct mctp_binding_i2c *i2c, uint8_t own_addr) |
| { |
| if (!mctp_i2c_valid_addr(own_addr)) { |
| return -EINVAL; |
| } |
| |
| i2c->own_addr = own_addr; |
| return 0; |
| } |
| |
| struct mctp_binding *mctp_binding_i2c_core(struct mctp_binding_i2c *i2c) |
| { |
| return &i2c->binding; |
| } |
| |
| static int mctp_i2c_hdr_validate(const struct mctp_i2c_hdr *hdr) |
| { |
| if (hdr->cmd != MCTP_I2C_COMMAND) { |
| return -EINVAL; |
| } |
| if ((hdr->dest & 1) != 0) { |
| return -EINVAL; |
| } |
| if ((hdr->source & 1) != 1) { |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| void mctp_i2c_rx(struct mctp_binding_i2c *i2c, const void *data, size_t len) |
| { |
| int rc; |
| |
| if (len < sizeof(struct mctp_i2c_hdr)) { |
| return; |
| } |
| const struct mctp_i2c_hdr *hdr = data; |
| rc = mctp_i2c_hdr_validate(hdr); |
| if (rc) { |
| return; |
| } |
| |
| if (hdr->bytecount != len - 3) { |
| return; |
| } |
| |
| if ((hdr->dest >> 1) != i2c->own_addr) { |
| return; |
| } |
| |
| uint8_t src = hdr->source >> 1; |
| if (src == i2c->own_addr) { |
| return; |
| } |
| |
| struct mctp_pktbuf *pkt = |
| mctp_pktbuf_init(&i2c->binding, i2c->rx_storage); |
| rc = mctp_pktbuf_push( |
| pkt, (const uint8_t *)data + sizeof(struct mctp_i2c_hdr), |
| len - sizeof(struct mctp_i2c_hdr)); |
| if (rc) { |
| // Packet too large for I2C_BTU |
| return; |
| } |
| |
| if (mctp_pktbuf_size(pkt) < sizeof(struct mctp_hdr)) { |
| return; |
| } |
| |
| struct mctp_hdr *mctp_hdr = mctp_pktbuf_hdr(pkt); |
| if (mctp_hdr->flags_seq_tag & MCTP_HDR_FLAG_TO) { |
| /* Update neighbour entry */ |
| mctp_i2c_neigh_add(i2c, mctp_hdr->src, src); |
| } |
| |
| mctp_bus_rx(&i2c->binding, pkt); |
| } |
| |
| int mctp_i2c_parse_hdr(const void *data, size_t len, uint8_t *src_addr, |
| uint8_t *dest_addr, uint8_t *bytecount) |
| { |
| int rc; |
| |
| if (len < sizeof(struct mctp_i2c_hdr)) { |
| return -EINVAL; |
| } |
| const struct mctp_i2c_hdr *hdr = data; |
| rc = mctp_i2c_hdr_validate(hdr); |
| if (rc) { |
| return rc; |
| } |
| |
| if (src_addr) { |
| *src_addr = hdr->source >> 1; |
| } |
| if (dest_addr) { |
| *dest_addr = hdr->dest >> 1; |
| } |
| if (bytecount) { |
| *bytecount = hdr->bytecount; |
| } |
| return 0; |
| } |
| |
| void mctp_i2c_tx_poll(struct mctp_binding_i2c *i2c) |
| { |
| mctp_binding_set_tx_enabled(&i2c->binding, true); |
| } |