643 lines
14 KiB
C
643 lines
14 KiB
C
/*
|
|
* 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 <string.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <modbus_internal.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
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, 4, 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
|