blob: b5c9df25398dc4618f67b3a6de99d04b5be289f8 [file] [log] [blame]
/*
* Copyright (c) 2020 PHYTEC Messtechnik GmbH
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modbus, CONFIG_MODBUS_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <string.h>
#include <zephyr/sys/byteorder.h>
#include <modbus_internal.h>
#define DT_DRV_COMPAT zephyr_modbus_serial
#define MB_RTU_DEFINE_GPIO_CFG(inst, prop) \
static struct gpio_dt_spec prop##_cfg_##inst = { \
.port = DEVICE_DT_GET(DT_INST_PHANDLE(inst, prop)), \
.pin = DT_INST_GPIO_PIN(inst, prop), \
.dt_flags = DT_INST_GPIO_FLAGS(inst, prop), \
};
#define MB_RTU_DEFINE_GPIO_CFGS(inst) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, de_gpios), \
(MB_RTU_DEFINE_GPIO_CFG(inst, de_gpios)), ()) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, re_gpios), \
(MB_RTU_DEFINE_GPIO_CFG(inst, re_gpios)), ())
DT_INST_FOREACH_STATUS_OKAY(MB_RTU_DEFINE_GPIO_CFGS)
#define MB_RTU_ASSIGN_GPIO_CFG(inst, prop) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, prop), \
(&prop##_cfg_##inst), (NULL))
#define MODBUS_DT_GET_SERIAL_DEV(inst) { \
.dev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
.de = MB_RTU_ASSIGN_GPIO_CFG(inst, de_gpios), \
.re = MB_RTU_ASSIGN_GPIO_CFG(inst, re_gpios), \
},
#ifdef CONFIG_MODBUS_SERIAL
static struct modbus_serial_config modbus_serial_cfg[] = {
DT_INST_FOREACH_STATUS_OKAY(MODBUS_DT_GET_SERIAL_DEV)
};
#endif
#define MODBUS_DT_GET_DEV(inst) { \
.iface_name = DEVICE_DT_NAME(DT_DRV_INST(inst)),\
.cfg = &modbus_serial_cfg[inst], \
},
#define DEFINE_MODBUS_RAW_ADU(x, _) { \
.iface_name = "RAW_"#x, \
.rawcb.raw_tx_cb = NULL, \
.mode = MODBUS_MODE_RAW, \
}
static struct modbus_context mb_ctx_tbl[] = {
DT_INST_FOREACH_STATUS_OKAY(MODBUS_DT_GET_DEV)
#ifdef CONFIG_MODBUS_RAW_ADU
LISTIFY(CONFIG_MODBUS_NUMOF_RAW_ADU, DEFINE_MODBUS_RAW_ADU, (,), _)
#endif
};
static void modbus_rx_handler(struct k_work *item)
{
struct modbus_context *ctx;
ctx = CONTAINER_OF(item, struct modbus_context, server_work);
switch (ctx->mode) {
case MODBUS_MODE_RTU:
case MODBUS_MODE_ASCII:
if (IS_ENABLED(CONFIG_MODBUS_SERIAL)) {
modbus_serial_rx_disable(ctx);
ctx->rx_adu_err = modbus_serial_rx_adu(ctx);
}
break;
case MODBUS_MODE_RAW:
if (IS_ENABLED(CONFIG_MODBUS_RAW_ADU)) {
ctx->rx_adu_err = modbus_raw_rx_adu(ctx);
}
break;
default:
LOG_ERR("Unknown MODBUS mode");
return;
}
if (ctx->client == true) {
k_sem_give(&ctx->client_wait_sem);
} else if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
bool respond = modbus_server_handler(ctx);
if (respond) {
modbus_tx_adu(ctx);
} else {
LOG_DBG("Server has dropped frame");
}
switch (ctx->mode) {
case MODBUS_MODE_RTU:
case MODBUS_MODE_ASCII:
if (IS_ENABLED(CONFIG_MODBUS_SERIAL) &&
respond == false) {
modbus_serial_rx_enable(ctx);
}
break;
default:
break;
}
}
}
void modbus_tx_adu(struct modbus_context *ctx)
{
switch (ctx->mode) {
case MODBUS_MODE_RTU:
case MODBUS_MODE_ASCII:
if (IS_ENABLED(CONFIG_MODBUS_SERIAL) &&
modbus_serial_tx_adu(ctx)) {
LOG_ERR("Unsupported MODBUS serial mode");
}
break;
case MODBUS_MODE_RAW:
if (IS_ENABLED(CONFIG_MODBUS_RAW_ADU) &&
modbus_raw_tx_adu(ctx)) {
LOG_ERR("Unsupported MODBUS raw mode");
}
break;
default:
LOG_ERR("Unknown MODBUS mode");
}
}
int modbus_tx_wait_rx_adu(struct modbus_context *ctx)
{
modbus_tx_adu(ctx);
if (k_sem_take(&ctx->client_wait_sem, K_USEC(ctx->rxwait_to)) != 0) {
LOG_WRN("Client wait-for-RX timeout");
return -ETIMEDOUT;
}
return ctx->rx_adu_err;
}
struct modbus_context *modbus_get_context(const uint8_t iface)
{
struct modbus_context *ctx;
if (iface >= ARRAY_SIZE(mb_ctx_tbl)) {
LOG_ERR("Interface %u not available", iface);
return NULL;
}
ctx = &mb_ctx_tbl[iface];
if (!atomic_test_bit(&ctx->state, MODBUS_STATE_CONFIGURED)) {
LOG_ERR("Interface not configured");
return NULL;
}
return ctx;
}
int modbus_iface_get_by_ctx(const struct modbus_context *ctx)
{
for (int i = 0; i < ARRAY_SIZE(mb_ctx_tbl); i++) {
if (&mb_ctx_tbl[i] == ctx) {
return i;
}
}
return -ENODEV;
}
int modbus_iface_get_by_name(const char *iface_name)
{
for (int i = 0; i < ARRAY_SIZE(mb_ctx_tbl); i++) {
if (strcmp(iface_name, mb_ctx_tbl[i].iface_name) == 0) {
return i;
}
}
return -ENODEV;
}
static struct modbus_context *modbus_init_iface(const uint8_t iface)
{
struct modbus_context *ctx;
if (iface >= ARRAY_SIZE(mb_ctx_tbl)) {
LOG_ERR("Interface %u not available", iface);
return NULL;
}
ctx = &mb_ctx_tbl[iface];
if (atomic_test_and_set_bit(&ctx->state, MODBUS_STATE_CONFIGURED)) {
LOG_ERR("Interface already used");
return NULL;
}
k_mutex_init(&ctx->iface_lock);
k_sem_init(&ctx->client_wait_sem, 0, 1);
k_work_init(&ctx->server_work, modbus_rx_handler);
return ctx;
}
static int modbus_user_fc_init(struct modbus_context *ctx, struct modbus_iface_param param)
{
sys_slist_init(&ctx->user_defined_cbs);
LOG_DBG("Initializing user-defined function code support.");
return 0;
}
int modbus_init_server(const int iface, struct modbus_iface_param param)
{
struct modbus_context *ctx = NULL;
int rc = 0;
if (!IS_ENABLED(CONFIG_MODBUS_SERVER)) {
LOG_ERR("Modbus server support is not enabled");
rc = -ENOTSUP;
goto init_server_error;
}
if (param.server.user_cb == NULL) {
LOG_ERR("User callbacks should be available");
rc = -EINVAL;
goto init_server_error;
}
ctx = modbus_init_iface(iface);
if (ctx == NULL) {
rc = -EINVAL;
goto init_server_error;
}
ctx->client = false;
if (modbus_user_fc_init(ctx, param) != 0) {
LOG_ERR("Failed to init MODBUS user defined function codes");
rc = -EINVAL;
goto init_server_error;
}
switch (param.mode) {
case MODBUS_MODE_RTU:
case MODBUS_MODE_ASCII:
if (IS_ENABLED(CONFIG_MODBUS_SERIAL) &&
modbus_serial_init(ctx, param) != 0) {
LOG_ERR("Failed to init MODBUS over serial line");
rc = -EINVAL;
goto init_server_error;
}
break;
case MODBUS_MODE_RAW:
if (IS_ENABLED(CONFIG_MODBUS_RAW_ADU) &&
modbus_raw_init(ctx, param) != 0) {
LOG_ERR("Failed to init MODBUS raw ADU support");
rc = -EINVAL;
goto init_server_error;
}
break;
default:
LOG_ERR("Unknown MODBUS mode");
rc = -ENOTSUP;
goto init_server_error;
}
ctx->unit_id = param.server.unit_id;
ctx->mbs_user_cb = param.server.user_cb;
if (IS_ENABLED(CONFIG_MODBUS_FC08_DIAGNOSTIC)) {
modbus_reset_stats(ctx);
}
LOG_DBG("Modbus interface %s initialized", ctx->iface_name);
return 0;
init_server_error:
if (ctx != NULL) {
atomic_clear_bit(&ctx->state, MODBUS_STATE_CONFIGURED);
}
return rc;
}
int modbus_register_user_fc(const int iface, struct modbus_custom_fc *custom_fc)
{
struct modbus_context *ctx = modbus_get_context(iface);
if (!custom_fc) {
LOG_ERR("Provided function code handler was NULL");
return -EINVAL;
}
if (custom_fc->fc & BIT(7)) {
LOG_ERR("Function codes must have MSB of 0");
return -EINVAL;
}
custom_fc->excep_code = MODBUS_EXC_NONE;
LOG_DBG("Registered new custom function code %d", custom_fc->fc);
sys_slist_append(&ctx->user_defined_cbs, &custom_fc->node);
return 0;
}
int modbus_init_client(const int iface, struct modbus_iface_param param)
{
struct modbus_context *ctx = NULL;
int rc = 0;
if (!IS_ENABLED(CONFIG_MODBUS_CLIENT)) {
LOG_ERR("Modbus client support is not enabled");
rc = -ENOTSUP;
goto init_client_error;
}
ctx = modbus_init_iface(iface);
if (ctx == NULL) {
rc = -EINVAL;
goto init_client_error;
}
ctx->client = true;
switch (param.mode) {
case MODBUS_MODE_RTU:
case MODBUS_MODE_ASCII:
if (IS_ENABLED(CONFIG_MODBUS_SERIAL) &&
modbus_serial_init(ctx, param) != 0) {
LOG_ERR("Failed to init MODBUS over serial line");
rc = -EINVAL;
goto init_client_error;
}
break;
case MODBUS_MODE_RAW:
if (IS_ENABLED(CONFIG_MODBUS_RAW_ADU) &&
modbus_raw_init(ctx, param) != 0) {
LOG_ERR("Failed to init MODBUS raw ADU support");
rc = -EINVAL;
goto init_client_error;
}
break;
default:
LOG_ERR("Unknown MODBUS mode");
rc = -ENOTSUP;
goto init_client_error;
}
ctx->unit_id = 0;
ctx->mbs_user_cb = NULL;
ctx->rxwait_to = param.rx_timeout;
return 0;
init_client_error:
if (ctx != NULL) {
atomic_clear_bit(&ctx->state, MODBUS_STATE_CONFIGURED);
}
return rc;
}
int modbus_disable(const uint8_t iface)
{
struct modbus_context *ctx;
struct k_work_sync work_sync;
ctx = modbus_get_context(iface);
if (ctx == NULL) {
LOG_ERR("Interface %u not initialized", iface);
return -EINVAL;
}
switch (ctx->mode) {
case MODBUS_MODE_RTU:
case MODBUS_MODE_ASCII:
if (IS_ENABLED(CONFIG_MODBUS_SERIAL)) {
modbus_serial_disable(ctx);
}
break;
case MODBUS_MODE_RAW:
break;
default:
LOG_ERR("Unknown MODBUS mode");
}
k_work_cancel_sync(&ctx->server_work, &work_sync);
ctx->rxwait_to = 0;
ctx->unit_id = 0;
ctx->mbs_user_cb = NULL;
atomic_clear_bit(&ctx->state, MODBUS_STATE_CONFIGURED);
LOG_INF("Modbus interface %u disabled", iface);
return 0;
}