184 lines
4.3 KiB
C
184 lines
4.3 KiB
C
/*
|
|
* 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)
|
|
{
|
|
}
|