|  | /* | 
|  | * Copyright (c) 2021 Nordic Semiconductor ASA | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <logging/log.h> | 
|  | LOG_MODULE_REGISTER(modbus_raw, CONFIG_MODBUS_LOG_LEVEL); | 
|  |  | 
|  | #include <kernel.h> | 
|  | #include <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->raw_tx_cb(iface, &ctx->tx_adu); | 
|  |  | 
|  | 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) { | 
|  | memcpy(adu, &ctx->rx_adu, sizeof(struct modbus_adu)); | 
|  | } 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->raw_tx_cb = param.raw_tx_cb; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void modbus_raw_disable(struct modbus_context *ctx) | 
|  | { | 
|  | } |