diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-core/interfaces/libmctp/smbus.c')
-rw-r--r-- | meta-openbmc-mods/meta-common/recipes-core/interfaces/libmctp/smbus.c | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-core/interfaces/libmctp/smbus.c b/meta-openbmc-mods/meta-common/recipes-core/interfaces/libmctp/smbus.c new file mode 100644 index 000000000..2f099a7e5 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-core/interfaces/libmctp/smbus.c @@ -0,0 +1,467 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef MCTP_HAVE_FILEIO +#include <fcntl.h> +#endif + +#define pr_fmt(x) "smbus: " x + +#include <i2c/smbus.h> +#include <linux/i2c-dev.h> +#include <linux/i2c.h> +#include <sys/ioctl.h> + +#include "libmctp-alloc.h" +#include "libmctp-log.h" +#include "libmctp-smbus.h" +#include "libmctp.h" + +struct mctp_binding_smbus { + struct mctp_binding binding; + int out_fd; + int in_fd; + + unsigned long bus_id; + + /* receive buffer */ + uint8_t rxbuf[1024]; + struct mctp_pktbuf *rx_pkt; + + /* temporary transmit buffer */ + uint8_t txbuf[256]; +}; + +#ifndef container_of +#define container_of(ptr, type, member) \ + (type *)((char *)(ptr) - (char *)&((type *)0)->member) +#endif + +#define binding_to_smbus(b) container_of(b, struct mctp_binding_smbus, binding) + +#define MCTP_COMMAND_CODE 0x0F +#define MCTP_SLAVE_ADDRESS 0x1d +#define MCTP_SOURCE_SLAVE_ADDRESS 0x21 + +#define SMBUS_PEC_BYTE_SIZE 1 +#define SMBUS_COMMAND_CODE_SIZE 1 +#define SMBUS_LENGTH_FIELD_SIZE 1 +#define SMBUS_ADDR_OFFSET_SLAVE 0x1000 + +struct mctp_smbus_header_tx { + uint8_t source_slave_address; +}; + +struct mctp_smbus_header_rx { + uint8_t destination_slave_address; + uint8_t command_code; + uint8_t byte_count; + uint8_t source_slave_address; +}; + +#define POLYCHECK (0x1070U << 3) +static uint8_t crc8_calculate(uint16_t d) +{ + int i; + + for (i = 0; i < 8; i++) { + if (d & 0x8000) { + d = d ^ POLYCHECK; + } + d = d << 1; + } + + return (uint8_t)(d >> 8); +} + +/* Incremental CRC8 over count bytes in the array pointed to by p */ +static uint8_t pec_calculate(uint8_t crc, uint8_t *p, size_t count) +{ + int i; + + for (i = 0; i < count; i++) { + crc = crc8_calculate((crc ^ p[i]) << 8); + } + + return crc; +} + +static uint8_t calculate_pec_byte(uint8_t *buf, size_t len, uint8_t address, + uint16_t flags) +{ + uint8_t addr = (address << 1) | (flags & I2C_M_RD ? 1 : 0); + uint8_t pec = pec_calculate(0, &addr, 1); + pec = pec_calculate(pec, buf, len); + + return pec; +} + +static int mctp_smbus_tx(struct mctp_binding_smbus *smbus, uint8_t len) +{ + +#ifdef I2C_M_HOLD + /* Hold message */ + static uint16_t holdtimeout = 1000; // timeout in ms. + struct i2c_msg msg[2] = +#else // !I2C_M_HOLD + struct i2c_msg msg[1] = +#endif // I2C_M_HOLD + {{.addr = MCTP_SLAVE_ADDRESS, + .flags = 0, + .len = len, + .buf = (__uint8_t *)smbus->txbuf} +#ifdef I2C_M_HOLD + , + {.addr = 0, + .flags = I2C_M_HOLD, + .len = sizeof(holdtimeout), + .buf = (__uint8_t *)&holdtimeout} +#endif // I2C_M_HOLD + }; + +#ifdef I2C_M_HOLD + struct i2c_rdwr_ioctl_data msgrdwr = {&msg, 2}; +#else // !I2C_M_HOLD + struct i2c_rdwr_ioctl_data msgrdwr = {&msg, 1}; +#endif // I2C_M_HOLD + + return ioctl(smbus->out_fd, I2C_RDWR, &msgrdwr); +} + +#ifdef I2C_M_HOLD +static int mctp_smbus_unhold_bus(struct mctp_binding_smbus *smbus) +{ + /* Unhold message */ + static uint16_t holdtimeout = 0; // unhold + struct i2c_msg holdmsg = {0, I2C_M_HOLD, sizeof(holdtimeout), + (__uint8_t *)&holdtimeout}; + + struct i2c_rdwr_ioctl_data msgrdwr = {&holdmsg, 1}; + + return ioctl(smbus->out_fd, I2C_RDWR, &msgrdwr); +} +#endif // I2C_M_HOLD + +static int mctp_binding_smbus_tx(struct mctp_binding *b, + struct mctp_pktbuf *pkt) +{ + struct mctp_binding_smbus *smbus = binding_to_smbus(b); + struct mctp_smbus_header_tx *hdr; + size_t pkt_length; + + uint8_t i2c_message_buf[256]; + uint8_t *buf_ptr; + uint8_t i2c_message_len; + + uint16_t timeout = 1000; + + /* the length field in the header excludes smbus framing + * and escape sequences */ + pkt_length = mctp_pktbuf_size(pkt); + + buf_ptr = (void *)smbus->txbuf; + *buf_ptr = MCTP_COMMAND_CODE; + buf_ptr++; + *buf_ptr = pkt_length + sizeof(*hdr); + buf_ptr++; + + hdr = (void *)buf_ptr; + hdr->source_slave_address = MCTP_SOURCE_SLAVE_ADDRESS; + buf_ptr = (buf_ptr + sizeof(*hdr)); + memcpy(buf_ptr, &pkt->data[pkt->start], pkt_length); + buf_ptr = buf_ptr + pkt_length; + + uint8_t pec_byte = calculate_pec_byte( + smbus->txbuf, + SMBUS_COMMAND_CODE_SIZE + SMBUS_LENGTH_FIELD_SIZE + sizeof(*hdr) + + pkt_length, + MCTP_SLAVE_ADDRESS, 0); + + *buf_ptr = pec_byte; + + i2c_message_len = SMBUS_COMMAND_CODE_SIZE + SMBUS_LENGTH_FIELD_SIZE + + sizeof(*hdr) + pkt_length + + SMBUS_PEC_BYTE_SIZE; // command code, length, + // header, data, pec byte + + if (mctp_smbus_tx(smbus, i2c_message_len)) { + mctp_prerr("Can't hold mux"); + return -1; + } + + return 0; +} + +#ifdef MCTP_HAVE_FILEIO +int mctp_smbus_read(struct mctp_binding_smbus *smbus) +{ + ssize_t len = 0; + struct mctp_smbus_header_rx *hdr; + int ret = 0; + + do { + ret = lseek(smbus->in_fd, 0, SEEK_SET); + if (ret < 0) { + mctp_prerr("Failed to seek"); + ret = -1; + } + + len = read(smbus->in_fd, smbus->rxbuf, sizeof(smbus->rxbuf)); + if (len < sizeof(*hdr)) { + // This condition hits from from time to time, even with + // a properly written poll loop, although it's not clear + // why. Return an error so that the upper layer can + // retry. + ret = 0; + break; + } + + hdr = (void *)smbus->rxbuf; + if (hdr->destination_slave_address + != (MCTP_SOURCE_SLAVE_ADDRESS & ~1)) { + mctp_prerr("Got bad slave address %d", + hdr->destination_slave_address); + ret = 0; + break; + } + if (hdr->command_code != MCTP_COMMAND_CODE) { + mctp_prerr("Got bad command code %d", + hdr->command_code); + // Not a payload intended for us + ret = 0; + break; + } + + if (hdr->byte_count != (len - sizeof(*hdr))) { + // Got an incorrectly sized payload + mctp_prerr("Got smbus payload sized %d, expecting %d", + hdr->byte_count, len - sizeof(*hdr)); + ret = 0; + break; + } + + if (len < 0) { + mctp_prerr("can't read from smbus device: %m"); + ret = -1; + break; + } + + smbus->rx_pkt = mctp_pktbuf_alloc(&(smbus->binding), 0); + assert(smbus->rx_pkt); + + if (mctp_pktbuf_push(smbus->rx_pkt, &smbus->rxbuf[sizeof(*hdr)], + len - sizeof(*hdr) - SMBUS_PEC_BYTE_SIZE) + != 0) { + mctp_prerr("Can't push tok pktbuf: %m"); + ret = -1; + break; + } + + mctp_bus_rx(&(smbus->binding), smbus->rx_pkt); + + smbus->rx_pkt = NULL; + + } while (0); + +#ifdef I2C_M_HOLD + if (mctp_smbus_unhold_bus(smbus)) { + mctp_prerr("Can't hold mux"); + ret = -1; + } +#endif // I2C_M_HOLD + + return ret; +} + +int mctp_smbus_get_in_fd(struct mctp_binding_smbus *smbus) +{ + return smbus->in_fd; +} + + +int mctp_smbus_set_in_fd(struct mctp_binding_smbus *smbus, int fd) +{ + smbus->in_fd = fd; +} + +int mctp_smbus_set_out_fd(struct mctp_binding_smbus *smbus, int fd) +{ + smbus->out_fd = fd; +} + +int mctp_smbus_get_out_fd(struct mctp_binding_smbus *smbus) +{ + return smbus->out_fd; +} + +int mctp_smbus_open_in_bus(struct mctp_binding_smbus *smbus, int in_bus) +{ + char filename[60]; + size_t filename_size = 0; + char slave_mqueue[20]; + size_t mqueue_size = 0; + int fd = 0; + size_t size = sizeof(filename); + int address_7_bit = MCTP_SOURCE_SLAVE_ADDRESS >> 1; + int ret = -1; + + snprintf(filename, size, + "/sys/bus/i2c/devices/i2c-%d/%d-%04x/slave-mqueue", in_bus, + in_bus, SMBUS_ADDR_OFFSET_SLAVE | address_7_bit); + + ret = open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (ret >= 0) { + return ret; + } + + // Device doesn't exist. Create it. + filename_size = sizeof(filename); + snprintf(filename, filename_size, + "/sys/bus/i2c/devices/i2c-%d/new_device", in_bus); + filename[filename_size - 1] = '\0'; + + fd = open(filename, O_WRONLY); + if (fd < 0) { + mctp_prerr("can't open root device %s: %m", filename); + return -1; + } + + mqueue_size = sizeof(slave_mqueue); + snprintf(slave_mqueue, mqueue_size, "slave-mqueue %#04x", + SMBUS_ADDR_OFFSET_SLAVE | address_7_bit); + + size = write(fd, slave_mqueue, mqueue_size); + close(fd); + if (size != mqueue_size) { + mctp_prerr("can't create mqueue device on %s: %m", filename); + return -1; + } + + size = sizeof(filename); + snprintf(filename, size, + "/sys/bus/i2c/devices/i2c-%d/%d-%04x/slave-mqueue", in_bus, + in_bus, SMBUS_ADDR_OFFSET_SLAVE | address_7_bit); + + return open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +} + +int mctp_smbus_open_out_bus(struct mctp_binding_smbus *smbus, int out_bus) +{ + char filename[60]; + size_t size = sizeof(filename); + snprintf(filename, size, "/dev/i2c-%d", out_bus); + filename[size - 1] = '\0'; + + return open(filename, O_RDWR | O_NONBLOCK); +} + +/* +int mctp_smbus_open_bus(struct mctp_binding_smbus *smbus, int out_bus_num, + int root_bus_num) +{ + char filename[60]; + size_t filename_size = 0; + char slave_mqueue[20]; + size_t mqueue_size = 0; + int fd = 0; + size_t size = sizeof(filename); + int address_7_bit = MCTP_SOURCE_SLAVE_ADDRESS >> 1; + + snprintf(filename, size, + "/sys/bus/i2c/devices/i2c-%d/%d-%04x/slave-mqueue", + root_bus_num, root_bus_num, + SMBUS_ADDR_OFFSET_SLAVE | address_7_bit); + + smbus->in_fd = open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (smbus->in_fd < 0) { + // Device doesn't exist. Create it. + filename_size = sizeof(filename); + snprintf(filename, filename_size, + "/sys/bus/i2c/devices/i2c-%d/new_device", + root_bus_num); + filename[filename_size - 1] = '\0'; + + fd = open(filename, O_WRONLY); + if (fd < 0) { + mctp_prerr("can't open root device %s: %m", filename); + return -1; + } + + mqueue_size = sizeof(slave_mqueue); + snprintf(slave_mqueue, mqueue_size, "slave-mqueue %#04x", + SMBUS_ADDR_OFFSET_SLAVE | address_7_bit); + + size = write(fd, slave_mqueue, mqueue_size); + close(fd); + if (size != mqueue_size) { + mctp_prerr("can't create mqueue device on %s: %m", + filename); + return -1; + } + + size = sizeof(filename); + snprintf(filename, size, + "/sys/bus/i2c/devices/i2c-%d/%d-%04x/slave-mqueue", + root_bus_num, root_bus_num, + SMBUS_ADDR_OFFSET_SLAVE | address_7_bit); + + smbus->in_fd = + open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (smbus->in_fd < 0) { + mctp_prerr("can't open mqueue device on %s: %m", + filename); + return -2; + } + } + + size = sizeof(filename); + snprintf(filename, size, "/dev/i2c-%d", out_bus_num); + filename[size - 1] = '\0'; + + smbus->out_fd = open(filename, O_RDWR | O_NONBLOCK); + if (smbus->out_fd < 0) { + close(smbus->in_fd); + mctp_prerr("can't open device %s: %m", filename); + } + + return 0; +} +*/ +#endif + +void mctp_smbus_register_bus(struct mctp_binding_smbus *smbus, + struct mctp *mctp, mctp_eid_t eid) +{ + smbus->bus_id = mctp_register_bus(mctp, &smbus->binding, eid); + mctp_binding_set_tx_enabled(&smbus->binding, true); +} + +struct mctp_binding_smbus *mctp_smbus_init(void) +{ + struct mctp_binding_smbus *smbus; + + smbus = __mctp_alloc(sizeof(*smbus)); + memset(&(smbus->binding), 0, sizeof(smbus->binding)); + + smbus->in_fd = -1; + smbus->out_fd = -1; + + smbus->rx_pkt = NULL; + smbus->binding.name = "smbus"; + smbus->binding.version = 1; + smbus->binding.pkt_size = sizeof(smbus->rxbuf); + + smbus->binding.tx = mctp_binding_smbus_tx; + return smbus; +} + +void mctp_smbus_free(struct mctp_binding_smbus *smbus) +{ + __mctp_free(smbus); +} |