/* * 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 #include #include #include 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