Skip to content

File syn_smbus.c

File List > proto > syn_smbus.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#include "syn_smbus.h"

#include <string.h>

#if !defined(SYN_USE_SMBUS) || SYN_USE_SMBUS

uint8_t syn_smbus_calc_pec(uint8_t init_crc, const uint8_t *data, size_t len)
{
    if (data == NULL) {
        return init_crc;
    }
    uint8_t crc = init_crc;
    for (size_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (int b = 0; b < 8; b++) {
            if (crc & 0x80) {
                crc = (uint8_t)((crc << 1) ^ 0x07);
            } else {
                crc = (uint8_t)(crc << 1);
            }
        }
    }
    return crc;
}

SYN_Status syn_smbus_encode_packet(const SYN_SMBUS_Packet *pkt, uint8_t *tx_buf, size_t buf_size,
                                   size_t *out_len)
{
    if (pkt == NULL || tx_buf == NULL || out_len == NULL) {
        return SYN_INVALID_PARAM;
    }

    size_t idx = 0;
    uint8_t addr_rw_write = (uint8_t)((pkt->slave_addr << 1) | 0x00);
    uint8_t addr_rw_read = (uint8_t)((pkt->slave_addr << 1) | 0x01);

    switch (pkt->proto) {
    case SYN_SMBUS_PROTO_QUICK_READ:
    case SYN_SMBUS_PROTO_QUICK_WRITE:
        if (buf_size < 1)
            return SYN_ERROR;
        tx_buf[idx++] = (pkt->proto == SYN_SMBUS_PROTO_QUICK_READ) ? addr_rw_read : addr_rw_write;
        break;

    case SYN_SMBUS_PROTO_SEND_BYTE:
        if (buf_size < 2 + (pkt->pec_enabled ? 1 : 0))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        break;

    case SYN_SMBUS_PROTO_RECEIVE_BYTE:
        if (buf_size < 1 + (pkt->pec_enabled ? 1 : 0))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_read;
        break;

    case SYN_SMBUS_PROTO_WRITE_BYTE:
        if (buf_size < 3 + (pkt->pec_enabled ? 1 : 0))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        tx_buf[idx++] = (pkt->length > 0) ? pkt->data[0] : 0;
        break;

    case SYN_SMBUS_PROTO_READ_BYTE:
        if (buf_size < 2 + (pkt->pec_enabled ? 1 : 0))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        break;

    case SYN_SMBUS_PROTO_WRITE_WORD:
        if (buf_size < 4 + (pkt->pec_enabled ? 1 : 0))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        tx_buf[idx++] = (pkt->length >= 1) ? pkt->data[0] : 0;
        tx_buf[idx++] = (pkt->length >= 2) ? pkt->data[1] : 0;
        break;

    case SYN_SMBUS_PROTO_READ_WORD:
        if (buf_size < 2 + (pkt->pec_enabled ? 1 : 0))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        break;

    case SYN_SMBUS_PROTO_WRITE_BLOCK: {
        uint8_t payload_len =
            (pkt->length > SYN_SMBUS_BLOCK_MAX_LEN) ? SYN_SMBUS_BLOCK_MAX_LEN : pkt->length;
        if (buf_size < (size_t)(3 + payload_len + (pkt->pec_enabled ? 1 : 0)))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        tx_buf[idx++] = payload_len;
        if (payload_len > 0) {
            memcpy(&tx_buf[idx], pkt->data, payload_len);
            idx += payload_len;
        }
        break;
    }

    case SYN_SMBUS_PROTO_READ_BLOCK:
        if (buf_size < 2 + (pkt->pec_enabled ? 1 : 0))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        break;

    case SYN_SMBUS_PROTO_PROCESS_CALL:
        if (buf_size < 4 + (pkt->pec_enabled ? 1 : 0))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        tx_buf[idx++] = (pkt->length >= 1) ? pkt->data[0] : 0;
        tx_buf[idx++] = (pkt->length >= 2) ? pkt->data[1] : 0;
        break;

    case SYN_SMBUS_PROTO_BLOCK_PROCESS_CALL: {
        uint8_t payload_len =
            (pkt->length > SYN_SMBUS_BLOCK_MAX_LEN) ? SYN_SMBUS_BLOCK_MAX_LEN : pkt->length;
        if (buf_size < (size_t)(3 + payload_len + (pkt->pec_enabled ? 1 : 0)))
            return SYN_ERROR;
        tx_buf[idx++] = addr_rw_write;
        tx_buf[idx++] = pkt->command;
        tx_buf[idx++] = payload_len;
        if (payload_len > 0) {
            memcpy(&tx_buf[idx], pkt->data, payload_len);
            idx += payload_len;
        }
        break;
    }

    default:
        return SYN_INVALID_PARAM;
    }

    if (pkt->pec_enabled) {
        tx_buf[idx] = syn_smbus_calc_pec(0, tx_buf, idx);
        idx++;
    }

    *out_len = idx;
    return SYN_OK;
}

SYN_Status syn_smbus_decode_packet(SYN_SMBUS_Packet *pkt, const uint8_t *rx_buf, size_t rx_len,
                                   SYN_SMBUS_Protocol proto, bool has_pec)
{
    if (pkt == NULL || rx_buf == NULL) {
        return SYN_INVALID_PARAM;
    }

    size_t min_len = has_pec ? 1 : 0;
    if (rx_len < min_len) {
        return SYN_INVALID_PARAM;
    }

    pkt->proto = proto;
    pkt->pec_enabled = has_pec;

    size_t data_len = has_pec ? (rx_len - 1) : rx_len;

    if (has_pec) {
        uint8_t calc_pec = syn_smbus_calc_pec(0, rx_buf, data_len);
        pkt->pec = rx_buf[rx_len - 1];
        pkt->pec_valid = (pkt->pec == calc_pec);
        if (!pkt->pec_valid) {
            return SYN_ERROR;
        }
    } else {
        pkt->pec = 0;
        pkt->pec_valid = true;
    }

    switch (proto) {
    case SYN_SMBUS_PROTO_WRITE_BYTE:
        if (data_len >= 3) {
            pkt->length = 1;
            pkt->data[0] = rx_buf[2];
        } else if (data_len >= 1) {
            pkt->length = 1;
            pkt->data[0] = rx_buf[0];
        } else {
            return SYN_INVALID_PARAM;
        }
        break;

    case SYN_SMBUS_PROTO_WRITE_WORD:
        if (data_len >= 4) {
            pkt->length = 2;
            pkt->data[0] = rx_buf[2];
            pkt->data[1] = rx_buf[3];
        } else if (data_len >= 2) {
            pkt->length = 2;
            pkt->data[0] = rx_buf[0];
            pkt->data[1] = rx_buf[1];
        } else {
            return SYN_INVALID_PARAM;
        }
        break;

    case SYN_SMBUS_PROTO_RECEIVE_BYTE:
    case SYN_SMBUS_PROTO_READ_BYTE:
        if (data_len >= 3) {
            pkt->length = 1;
            pkt->data[0] = rx_buf[2];
        } else if (data_len >= 1) {
            pkt->length = 1;
            pkt->data[0] = rx_buf[0];
        } else {
            return SYN_INVALID_PARAM;
        }
        break;

    case SYN_SMBUS_PROTO_READ_WORD:
    case SYN_SMBUS_PROTO_PROCESS_CALL:
        if (data_len >= 4) {
            pkt->length = 2;
            pkt->data[0] = rx_buf[2];
            pkt->data[1] = rx_buf[3];
        } else if (data_len >= 2) {
            pkt->length = 2;
            pkt->data[0] = rx_buf[0];
            pkt->data[1] = rx_buf[1];
        } else {
            return SYN_INVALID_PARAM;
        }
        break;

    case SYN_SMBUS_PROTO_WRITE_BLOCK:
    case SYN_SMBUS_PROTO_READ_BLOCK:
    case SYN_SMBUS_PROTO_BLOCK_PROCESS_CALL: {
        size_t header_len = 0;
        if (data_len >= 3 && (size_t)(3 + rx_buf[2]) <= data_len) {
            header_len = 2;
        } else if (data_len >= 1 && (size_t)(1 + rx_buf[0]) <= data_len) {
            header_len = 0;
        } else {
            return SYN_INVALID_PARAM;
        }
        uint8_t count = rx_buf[header_len];
        pkt->length = count;
        if (count > 0) {
            size_t copy_len = (size_t)count;
            memcpy(pkt->data, &rx_buf[header_len + 1], copy_len);
        }
        break;
    }

    default:
        pkt->length =
            (uint8_t)(data_len > SYN_SMBUS_BUF_MAX_LEN ? SYN_SMBUS_BUF_MAX_LEN : data_len);
        if (pkt->length > 0) {
            memcpy(pkt->data, rx_buf, pkt->length);
        }
        break;
    }

    return SYN_OK;
}

#endif /* SYN_USE_SMBUS */