You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
4.3 KiB
183 lines
4.3 KiB
/* |
|
* Copyright (c) 2021 Nordic Semiconductor ASA |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(modbus_raw, CONFIG_MODBUS_LOG_LEVEL); |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/sys/byteorder.h> |
|
#include <modbus_internal.h> |
|
|
|
#define MODBUS_ADU_LENGTH_DEVIATION 2 |
|
#define MODBUS_RAW_MIN_MSG_SIZE (MODBUS_RTU_MIN_MSG_SIZE - 2) |
|
#define MODBUS_RAW_BUFFER_SIZE (CONFIG_MODBUS_BUFFER_SIZE - 2) |
|
|
|
int modbus_raw_rx_adu(struct modbus_context *ctx) |
|
{ |
|
if (ctx->rx_adu.length < MODBUS_RAW_MIN_MSG_SIZE || |
|
ctx->rx_adu.length > MODBUS_RAW_BUFFER_SIZE) { |
|
LOG_WRN("Frame length error"); |
|
return -EMSGSIZE; |
|
} |
|
|
|
if (ctx->rx_adu.proto_id != MODBUS_ADU_PROTO_ID) { |
|
LOG_ERR("MODBUS protocol not supported"); |
|
return -ENOTSUP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int modbus_raw_tx_adu(struct modbus_context *ctx) |
|
{ |
|
int iface = modbus_iface_get_by_ctx(ctx); |
|
|
|
if (ctx->mode != MODBUS_MODE_RAW) { |
|
return -ENOTSUP; |
|
} |
|
|
|
if (iface < 0) { |
|
return -ENODEV; |
|
} |
|
|
|
ctx->rawcb.raw_tx_cb(iface, &ctx->tx_adu, ctx->rawcb.user_data); |
|
|
|
return 0; |
|
} |
|
|
|
int modbus_raw_submit_rx(const int iface, const struct modbus_adu *adu) |
|
{ |
|
struct modbus_context *ctx; |
|
|
|
ctx = modbus_get_context(iface); |
|
|
|
if (ctx == NULL) { |
|
LOG_ERR("Interface not available"); |
|
return -ENODEV; |
|
} |
|
|
|
if (ctx->mode != MODBUS_MODE_RAW) { |
|
LOG_ERR("Interface not in RAW mode"); |
|
return -ENOTSUP; |
|
} |
|
|
|
ctx->rx_adu.trans_id = adu->trans_id; |
|
ctx->rx_adu.proto_id = adu->proto_id; |
|
ctx->rx_adu.length = adu->length; |
|
ctx->rx_adu.unit_id = adu->unit_id; |
|
ctx->rx_adu.fc = adu->fc; |
|
memcpy(ctx->rx_adu.data, adu->data, |
|
MIN(adu->length, sizeof(ctx->rx_adu.data))); |
|
k_work_submit(&ctx->server_work); |
|
|
|
return 0; |
|
} |
|
|
|
void modbus_raw_put_header(const struct modbus_adu *adu, uint8_t *header) |
|
{ |
|
uint16_t length = MIN(adu->length, CONFIG_MODBUS_BUFFER_SIZE); |
|
|
|
sys_put_be16(adu->trans_id, &header[0]); |
|
sys_put_be16(adu->proto_id, &header[2]); |
|
sys_put_be16(length + MODBUS_ADU_LENGTH_DEVIATION, &header[4]); |
|
header[6] = adu->unit_id; |
|
header[7] = adu->fc; |
|
} |
|
|
|
void modbus_raw_get_header(struct modbus_adu *adu, const uint8_t *header) |
|
{ |
|
adu->trans_id = sys_get_be16(&header[0]); |
|
adu->proto_id = sys_get_be16(&header[2]); |
|
adu->length = MIN(sys_get_be16(&header[4]), CONFIG_MODBUS_BUFFER_SIZE); |
|
adu->unit_id = header[6]; |
|
adu->fc = header[7]; |
|
|
|
if (adu->length >= MODBUS_ADU_LENGTH_DEVIATION) { |
|
adu->length -= MODBUS_ADU_LENGTH_DEVIATION; |
|
} |
|
} |
|
|
|
static void modbus_set_exception(struct modbus_adu *adu, |
|
const uint8_t excep_code) |
|
{ |
|
const uint8_t excep_bit = BIT(7); |
|
|
|
adu->fc |= excep_bit; |
|
adu->data[0] = excep_code; |
|
adu->length = 1; |
|
} |
|
|
|
void modbus_raw_set_server_failure(struct modbus_adu *adu) |
|
{ |
|
const uint8_t excep_bit = BIT(7); |
|
|
|
adu->fc |= excep_bit; |
|
adu->data[0] = MODBUS_EXC_SERVER_DEVICE_FAILURE; |
|
adu->length = 1; |
|
} |
|
|
|
int modbus_raw_backend_txn(const int iface, struct modbus_adu *adu) |
|
{ |
|
struct modbus_context *ctx; |
|
int err; |
|
|
|
ctx = modbus_get_context(iface); |
|
if (ctx == NULL) { |
|
LOG_ERR("Interface %d not available", iface); |
|
modbus_set_exception(adu, MODBUS_EXC_GW_PATH_UNAVAILABLE); |
|
return -ENODEV; |
|
} |
|
|
|
/* |
|
* This is currently only possible over serial line |
|
* since no other medium is directly supported. |
|
*/ |
|
if (ctx->client == false || |
|
(ctx->mode != MODBUS_MODE_RTU && ctx->mode != MODBUS_MODE_ASCII)) { |
|
LOG_ERR("Interface %d has wrong configuration", iface); |
|
modbus_set_exception(adu, MODBUS_EXC_GW_PATH_UNAVAILABLE); |
|
return -ENOTSUP; |
|
} |
|
|
|
LOG_DBG("Use backend interface %d", iface); |
|
memcpy(&ctx->tx_adu, adu, sizeof(struct modbus_adu)); |
|
err = modbus_tx_wait_rx_adu(ctx); |
|
|
|
if (err == 0) { |
|
/* |
|
* Serial line does not use transaction and protocol IDs. |
|
* Temporarily store transaction and protocol IDs, and write it |
|
* back if the transfer was successful. |
|
*/ |
|
uint16_t trans_id = adu->trans_id; |
|
uint16_t proto_id = adu->proto_id; |
|
|
|
memcpy(adu, &ctx->rx_adu, sizeof(struct modbus_adu)); |
|
adu->trans_id = trans_id; |
|
adu->proto_id = proto_id; |
|
} else { |
|
modbus_set_exception(adu, MODBUS_EXC_GW_TARGET_FAILED_TO_RESP); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
int modbus_raw_init(struct modbus_context *ctx, |
|
struct modbus_iface_param param) |
|
{ |
|
if (ctx->mode != MODBUS_MODE_RAW) { |
|
return -ENOTSUP; |
|
} |
|
|
|
ctx->rawcb.raw_tx_cb = param.rawcb.raw_tx_cb; |
|
ctx->rawcb.user_data = param.rawcb.user_data; |
|
|
|
return 0; |
|
} |
|
|
|
void modbus_raw_disable(struct modbus_context *ctx) |
|
{ |
|
}
|
|
|