summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-core/interfaces/libmctp/smbus.c
diff options
context:
space:
mode:
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.c467
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);
+}