| /* |
| * Copyright (c) 2020 PHYTEC Messtechnik GmbH |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /* |
| * This file is based on mbm_core.c from uC/Modbus Stack. |
| * |
| * uC/Modbus |
| * The Embedded Modbus Stack |
| * |
| * Copyright 2003-2020 Silicon Laboratories Inc. www.silabs.com |
| * |
| * SPDX-License-Identifier: APACHE-2.0 |
| * |
| * This software is subject to an open source license and is distributed by |
| * Silicon Laboratories Inc. pursuant to the terms of the Apache License, |
| * Version 2.0 available at www.apache.org/licenses/LICENSE-2.0. |
| */ |
| |
| #include <string.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <modbus_internal.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(modbus_c, CONFIG_MODBUS_LOG_LEVEL); |
| |
| static int mbc_validate_response_fc(struct modbus_context *ctx, |
| const uint8_t unit_id, |
| uint8_t fc) |
| { |
| uint8_t resp_fc = ctx->rx_adu.fc; |
| uint8_t excep_code = ctx->rx_adu.data[0]; |
| const uint8_t excep_bit = BIT(7); |
| const uint8_t excep_mask = BIT_MASK(7); |
| |
| if (unit_id != ctx->rx_adu.unit_id) { |
| return -EIO; |
| } |
| |
| if (fc != (resp_fc & excep_mask)) { |
| return -EIO; |
| } |
| |
| if (resp_fc & excep_bit) { |
| if (excep_code > MODBUS_EXC_NONE) { |
| return excep_code; |
| } |
| |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int mbc_validate_fc03fp_response(struct modbus_context *ctx, float *ptbl) |
| { |
| size_t resp_byte_cnt; |
| size_t req_byte_cnt; |
| uint16_t req_qty; |
| uint8_t *resp_data; |
| |
| resp_byte_cnt = ctx->rx_adu.data[0]; |
| resp_data = &ctx->rx_adu.data[1]; |
| req_qty = sys_get_be16(&ctx->tx_adu.data[2]); |
| req_byte_cnt = req_qty * sizeof(float); |
| |
| if (req_byte_cnt != resp_byte_cnt) { |
| LOG_ERR("Mismatch in the number of registers"); |
| return -EINVAL; |
| } |
| |
| for (uint16_t i = 0; i < req_qty; i++) { |
| uint32_t reg_val = sys_get_be32(resp_data); |
| |
| memcpy(&ptbl[i], ®_val, sizeof(float)); |
| resp_data += sizeof(uint32_t); |
| } |
| |
| return 0; |
| } |
| |
| static int mbc_validate_rd_response(struct modbus_context *ctx, |
| const uint8_t unit_id, |
| uint8_t fc, |
| uint8_t *data) |
| { |
| int err; |
| size_t resp_byte_cnt; |
| size_t req_byte_cnt; |
| uint16_t req_qty; |
| uint16_t req_addr; |
| uint8_t *resp_data; |
| uint16_t *data_p16 = (uint16_t *)data; |
| |
| if (data == NULL) { |
| return -EINVAL; |
| } |
| |
| resp_byte_cnt = ctx->rx_adu.data[0]; |
| resp_data = &ctx->rx_adu.data[1]; |
| req_qty = sys_get_be16(&ctx->tx_adu.data[2]); |
| req_addr = sys_get_be16(&ctx->tx_adu.data[0]); |
| |
| if ((resp_byte_cnt + 1) > sizeof(ctx->rx_adu.data)) { |
| LOG_ERR("Byte count exceeds buffer length"); |
| return -EINVAL; |
| } |
| |
| switch (fc) { |
| case MODBUS_FC01_COIL_RD: |
| case MODBUS_FC02_DI_RD: |
| req_byte_cnt = ((req_qty - 1) / 8) + 1; |
| if (req_byte_cnt != resp_byte_cnt) { |
| LOG_ERR("Mismatch in the number of coils or inputs"); |
| err = -EINVAL; |
| } else { |
| for (uint8_t i = 0; i < resp_byte_cnt; i++) { |
| data[i] = resp_data[i]; |
| } |
| |
| err = 0; |
| } |
| break; |
| |
| case MODBUS_FC03_HOLDING_REG_RD: |
| if (IS_ENABLED(CONFIG_MODBUS_FP_EXTENSIONS) && |
| (req_addr >= MODBUS_FP_EXTENSIONS_ADDR)) { |
| err = mbc_validate_fc03fp_response(ctx, (float *)data); |
| break; |
| } |
| __fallthrough; |
| case MODBUS_FC04_IN_REG_RD: |
| req_byte_cnt = req_qty * sizeof(uint16_t); |
| if (req_byte_cnt != resp_byte_cnt) { |
| LOG_ERR("Mismatch in the number of registers"); |
| err = -EINVAL; |
| } else { |
| for (uint16_t i = 0; i < req_qty; i++) { |
| data_p16[i] = sys_get_be16(resp_data); |
| resp_data += sizeof(uint16_t); |
| } |
| |
| err = 0; |
| } |
| break; |
| |
| default: |
| LOG_ERR("Validation not implemented for FC 0x%02x", fc); |
| err = -ENOTSUP; |
| } |
| |
| return err; |
| } |
| |
| static int mbc_validate_fc08_response(struct modbus_context *ctx, |
| const uint8_t unit_id, |
| uint16_t *data) |
| { |
| int err; |
| uint16_t resp_sfunc; |
| uint16_t resp_data; |
| uint16_t req_sfunc; |
| uint16_t req_data; |
| |
| if (data == NULL) { |
| return -EINVAL; |
| } |
| |
| req_sfunc = sys_get_be16(&ctx->tx_adu.data[0]); |
| req_data = sys_get_be16(&ctx->tx_adu.data[2]); |
| resp_sfunc = sys_get_be16(&ctx->rx_adu.data[0]); |
| resp_data = sys_get_be16(&ctx->rx_adu.data[2]); |
| |
| if (req_sfunc != resp_sfunc) { |
| LOG_ERR("Mismatch in the sub-function code"); |
| return -EINVAL; |
| } |
| |
| switch (resp_sfunc) { |
| case MODBUS_FC08_SUBF_QUERY: |
| case MODBUS_FC08_SUBF_CLR_CTR: |
| if (req_data != resp_data) { |
| LOG_ERR("Request and response data are different"); |
| err = -EINVAL; |
| } else { |
| *data = resp_data; |
| err = 0; |
| } |
| break; |
| |
| case MODBUS_FC08_SUBF_BUS_MSG_CTR: |
| case MODBUS_FC08_SUBF_BUS_CRC_CTR: |
| case MODBUS_FC08_SUBF_BUS_EXCEPT_CTR: |
| case MODBUS_FC08_SUBF_SERVER_MSG_CTR: |
| case MODBUS_FC08_SUBF_SERVER_NO_RESP_CTR: |
| *data = resp_data; |
| err = 0; |
| break; |
| |
| default: |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int mbc_validate_wr_response(struct modbus_context *ctx, |
| const uint8_t unit_id, |
| uint8_t fc) |
| { |
| int err; |
| uint16_t req_addr; |
| uint16_t req_value; |
| uint16_t resp_addr; |
| uint16_t resp_value; |
| |
| req_addr = sys_get_be16(&ctx->tx_adu.data[0]); |
| req_value = sys_get_be16(&ctx->tx_adu.data[2]); |
| resp_addr = sys_get_be16(&ctx->rx_adu.data[0]); |
| resp_value = sys_get_be16(&ctx->rx_adu.data[2]); |
| |
| switch (fc) { |
| case MODBUS_FC05_COIL_WR: |
| case MODBUS_FC06_HOLDING_REG_WR: |
| case MODBUS_FC15_COILS_WR: |
| case MODBUS_FC16_HOLDING_REGS_WR: |
| if (req_addr != resp_addr || req_value != resp_value) { |
| err = ENXIO; |
| } else { |
| err = 0; |
| } |
| break; |
| |
| default: |
| LOG_ERR("Validation not implemented for FC 0x%02x", fc); |
| err = -ENOTSUP; |
| } |
| |
| return err; |
| } |
| |
| static int mbc_send_cmd(struct modbus_context *ctx, const uint8_t unit_id, |
| uint8_t fc, void *data) |
| { |
| int err; |
| |
| ctx->tx_adu.unit_id = unit_id; |
| ctx->tx_adu.fc = fc; |
| |
| err = modbus_tx_wait_rx_adu(ctx); |
| if (err != 0) { |
| return err; |
| } |
| |
| err = mbc_validate_response_fc(ctx, unit_id, fc); |
| if (err < 0) { |
| LOG_ERR("Failed to validate unit ID or function code"); |
| return err; |
| } else if (err > 0) { |
| LOG_INF("Modbus FC %u, error code %u", fc, err); |
| return err; |
| } |
| |
| switch (fc) { |
| case MODBUS_FC01_COIL_RD: |
| case MODBUS_FC02_DI_RD: |
| case MODBUS_FC03_HOLDING_REG_RD: |
| case MODBUS_FC04_IN_REG_RD: |
| err = mbc_validate_rd_response(ctx, unit_id, fc, data); |
| break; |
| |
| case MODBUS_FC08_DIAGNOSTICS: |
| err = mbc_validate_fc08_response(ctx, unit_id, data); |
| break; |
| |
| case MODBUS_FC05_COIL_WR: |
| case MODBUS_FC06_HOLDING_REG_WR: |
| case MODBUS_FC15_COILS_WR: |
| case MODBUS_FC16_HOLDING_REGS_WR: |
| err = mbc_validate_wr_response(ctx, unit_id, fc); |
| break; |
| |
| default: |
| LOG_ERR("FC 0x%02x not implemented", fc); |
| err = -ENOTSUP; |
| } |
| |
| return err; |
| } |
| |
| int modbus_read_coils(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| uint8_t *const coil_tbl, |
| const uint16_t num_coils) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| ctx->tx_adu.length = 4; |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| sys_put_be16(num_coils, &ctx->tx_adu.data[2]); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC01_COIL_RD, coil_tbl); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| int modbus_read_dinputs(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| uint8_t *const di_tbl, |
| const uint16_t num_di) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| ctx->tx_adu.length = 4; |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| sys_put_be16(num_di, &ctx->tx_adu.data[2]); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC02_DI_RD, di_tbl); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| int modbus_read_holding_regs(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| uint16_t *const reg_buf, |
| const uint16_t num_regs) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| ctx->tx_adu.length = 4; |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| sys_put_be16(num_regs, &ctx->tx_adu.data[2]); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC03_HOLDING_REG_RD, reg_buf); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| |
| #ifdef CONFIG_MODBUS_FP_EXTENSIONS |
| int modbus_read_holding_regs_fp(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| float *const reg_buf, |
| const uint16_t num_regs) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| ctx->tx_adu.length = 4; |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| sys_put_be16(num_regs, &ctx->tx_adu.data[2]); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC03_HOLDING_REG_RD, reg_buf); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| #endif |
| |
| int modbus_read_input_regs(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| uint16_t *const reg_buf, |
| const uint16_t num_regs) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| ctx->tx_adu.length = 4; |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| sys_put_be16(num_regs, &ctx->tx_adu.data[2]); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC04_IN_REG_RD, reg_buf); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| int modbus_write_coil(const int iface, |
| const uint8_t unit_id, |
| const uint16_t coil_addr, |
| const bool coil_state) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| int err; |
| uint16_t coil_val; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| if (coil_state == false) { |
| coil_val = MODBUS_COIL_OFF_CODE; |
| } else { |
| coil_val = MODBUS_COIL_ON_CODE; |
| } |
| |
| ctx->tx_adu.length = 4; |
| sys_put_be16(coil_addr, &ctx->tx_adu.data[0]); |
| sys_put_be16(coil_val, &ctx->tx_adu.data[2]); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC05_COIL_WR, NULL); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| int modbus_write_holding_reg(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| const uint16_t reg_val) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| ctx->tx_adu.length = 4; |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| sys_put_be16(reg_val, &ctx->tx_adu.data[2]); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC06_HOLDING_REG_WR, NULL); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| int modbus_request_diagnostic(const int iface, |
| const uint8_t unit_id, |
| const uint16_t sfunc, |
| const uint16_t data, |
| uint16_t *const data_out) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| ctx->tx_adu.length = 4; |
| sys_put_be16(sfunc, &ctx->tx_adu.data[0]); |
| sys_put_be16(data, &ctx->tx_adu.data[2]); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC08_DIAGNOSTICS, data_out); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| int modbus_write_coils(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| uint8_t *const coil_tbl, |
| const uint16_t num_coils) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| size_t length = 0; |
| uint8_t *data_ptr; |
| size_t num_bytes; |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| length += sizeof(start_addr); |
| sys_put_be16(num_coils, &ctx->tx_adu.data[2]); |
| length += sizeof(num_coils); |
| |
| num_bytes = (uint8_t)(((num_coils - 1) / 8) + 1); |
| ctx->tx_adu.data[4] = num_bytes; |
| length += num_bytes + 1; |
| |
| if (length > sizeof(ctx->tx_adu.data)) { |
| LOG_ERR("Length of data buffer is not sufficient"); |
| k_mutex_unlock(&ctx->iface_lock); |
| return -ENOBUFS; |
| } |
| |
| ctx->tx_adu.length = length; |
| data_ptr = &ctx->tx_adu.data[5]; |
| |
| memcpy(data_ptr, coil_tbl, num_bytes); |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC15_COILS_WR, NULL); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| int modbus_write_holding_regs(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| uint16_t *const reg_buf, |
| const uint16_t num_regs) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| size_t length = 0; |
| uint8_t *data_ptr; |
| size_t num_bytes; |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| length += sizeof(start_addr); |
| sys_put_be16(num_regs, &ctx->tx_adu.data[2]); |
| length += sizeof(num_regs); |
| |
| num_bytes = num_regs * sizeof(uint16_t); |
| ctx->tx_adu.data[4] = num_bytes; |
| length += num_bytes + 1; |
| |
| if (length > sizeof(ctx->tx_adu.data)) { |
| LOG_ERR("Length of data buffer is not sufficient"); |
| k_mutex_unlock(&ctx->iface_lock); |
| return -ENOBUFS; |
| } |
| |
| ctx->tx_adu.length = length; |
| data_ptr = &ctx->tx_adu.data[5]; |
| |
| for (uint16_t i = 0; i < num_regs; i++) { |
| sys_put_be16(reg_buf[i], data_ptr); |
| data_ptr += sizeof(uint16_t); |
| } |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC16_HOLDING_REGS_WR, NULL); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| |
| #ifdef CONFIG_MODBUS_FP_EXTENSIONS |
| int modbus_write_holding_regs_fp(const int iface, |
| const uint8_t unit_id, |
| const uint16_t start_addr, |
| float *const reg_buf, |
| const uint16_t num_regs) |
| { |
| struct modbus_context *ctx = modbus_get_context(iface); |
| size_t length = 0; |
| uint8_t *data_ptr; |
| size_t num_bytes; |
| int err; |
| |
| if (ctx == NULL) { |
| return -ENODEV; |
| } |
| |
| k_mutex_lock(&ctx->iface_lock, K_FOREVER); |
| |
| sys_put_be16(start_addr, &ctx->tx_adu.data[0]); |
| length += sizeof(start_addr); |
| sys_put_be16(num_regs, &ctx->tx_adu.data[2]); |
| length += sizeof(num_regs); |
| |
| num_bytes = num_regs * sizeof(float); |
| ctx->tx_adu.data[4] = num_bytes; |
| length += num_bytes + 1; |
| |
| if (length > sizeof(ctx->tx_adu.data)) { |
| LOG_ERR("Length of data buffer is not sufficient"); |
| k_mutex_unlock(&ctx->iface_lock); |
| return -ENOBUFS; |
| } |
| |
| ctx->tx_adu.length = length; |
| data_ptr = &ctx->tx_adu.data[5]; |
| |
| for (uint16_t i = 0; i < num_regs; i++) { |
| uint32_t reg_val; |
| |
| memcpy(®_val, ®_buf[i], sizeof(reg_val)); |
| sys_put_be32(reg_val, data_ptr); |
| data_ptr += sizeof(uint32_t); |
| } |
| |
| err = mbc_send_cmd(ctx, unit_id, MODBUS_FC16_HOLDING_REGS_WR, NULL); |
| k_mutex_unlock(&ctx->iface_lock); |
| |
| return err; |
| } |
| #endif |