6#include "dawn/proto/modbus/regs.hxx"
12#include "dawn/io/common.hxx"
13#include "dawn/io/viewdata.hxx"
17static inline void modbus_set_bit(uint8_t *buf, uint16_t bit, uint8_t value)
19 uint8_t *
byte = &buf[bit / 8];
20 uint8_t mask = (uint8_t)(1u << (bit % 8));
28 *
byte &= (uint8_t)~mask;
32static inline uint8_t modbus_get_bit(
const uint8_t *buf, uint16_t bit)
34 return (uint8_t)((buf[bit / 8] >> (bit % 8)) & 0x1u);
39 static constexpr size_t default_window = 8;
43#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
44CProtoModbusRegs::SSeekableState *CProtoModbusRegs::findSeekableState(SProtoModbusRegs *reg)
46 for (
auto *state : vseekable)
48 if (state->reg == reg)
59CProtoModbusRegs::findGroup(uint16_t addr, uint16_t n,
const std::vector<SProtoModbusRegs *> ®s)
61 SProtoModbusRegs *reg =
nullptr;
66 for (
auto it = regs.rbegin(); it != regs.rend(); ++it)
70 if (v ==
nullptr || v->cfg ==
nullptr)
77 if ((addr >= v->cfg->start) && ((addr + n) <= (v->cfg->start + v->regs)))
88 const std::vector<SProtoModbusRegs *> ®s,
92 uint32_t end = start +
static_cast<uint32_t
>(count);
94 for (
auto const *existing : regs)
96 if (existing ==
nullptr || existing->cfg ==
nullptr)
101 uint32_t ex_start = existing->
cfg->
start;
102 uint32_t ex_end = ex_start +
static_cast<uint32_t
>(existing->regs);
104 if (start < ex_end && ex_start < end)
113void CProtoModbusRegs::cleanupGroups(std::vector<SProtoModbusRegs *> &groups)
115 for (
auto *v : groups)
117 delete[]
reinterpret_cast<uint8_t *
>(v->data);
119 for (
auto *b : v->iodata)
135 return (modbus_seekable_window_regs(reg) + 1) *
sizeof(uint16_t);
138 return reg->
iodata[index]->getDataSize();
141static inline void modbusReverseBytes(uint8_t *dst,
const uint8_t *src,
size_t len)
143 for (
size_t i = 0; i < len; i++)
145 dst[i] = src[len - 1 - i];
157 size_t regOffset = 0;
159 for (
size_t j = 0; j < reg->
iodata.size(); j++)
161 size_t bytes = getIoRegBytes(reg, j);
163 if (index >=
static_cast<int>(regOffset) && index <
static_cast<int>(regOffset + bytes))
165 *offsetInIo =
static_cast<size_t>(index) - regOffset;
166 return static_cast<int>(j);
175#if defined(CONFIG_DAWN_PROTO_MODBUS_COIL) || defined(CONFIG_DAWN_PROTO_MODBUS_DISCRETE)
176int CProtoModbusRegs::regReadWriteCoil(uint8_t *buff,
180 SProtoModbusRegs *reg)
184 uint8_t *ptr =
nullptr;
193 if (index >= reg->ios)
199 io = getIO_(reg->cfg->objid[index]);
200 iodata = reg->iodata[index];
201 ptr =
reinterpret_cast<uint8_t *
>(iodata->
getDataPtr());
222 for (
size_t i = 0; i < size; i++)
229 for (
size_t i = 0; i < size; i++)
242 index +=
static_cast<int>(size);
243 n -=
static_cast<uint16_t
>(size);
250int CProtoModbusRegs::regReadWriteCoilPacked(uint8_t *buff,
254 SProtoModbusRegs *reg)
258 uint8_t *ptr =
nullptr;
266 memset(buff, 0, (n + 7) / 8);
269 for (uint16_t bit = 0; bit < n; bit++)
271 size_t offsetInIo = 0;
272 const int io_index = findIoIndexByByteOffset(reg, index + bit, &offsetInIo);
274 if (io_index < 0 || io_index >= reg->ios)
285 io = getIO_(reg->cfg->objid[io_index]);
286 iodata = reg->iodata[io_index];
287 ptr =
reinterpret_cast<uint8_t *
>(iodata->
getDataPtr());
290 if (offsetInIo >= size)
308 modbus_set_bit(buff, bit, (ptr[offsetInIo] == 0) ? 0 : 1);
322 ptr[offsetInIo] = modbus_get_bit(buff, bit) ? 1 : 0;
338#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
339int CProtoModbusRegs::seekableRegRW(uint8_t *buff,
343 SProtoModbusRegs *reg,
344 SSeekableState *seekState)
348 uint8_t *ptr =
nullptr;
354 size_t windowRegs = seekState->windowRegs;
355 size_t regsPerIo = windowRegs + 1;
365 size_t ioIndex =
static_cast<size_t>(index) / regsPerIo;
366 size_t inIo =
static_cast<size_t>(index) % regsPerIo;
367 if (ioIndex >=
static_cast<size_t>(reg->ios))
375 if (inIo == windowRegs)
379 seekState->readProgressWords[ioIndex] = 0;
380 value =
static_cast<uint16_t
>(seekState->seekOffsets[ioIndex]);
381 *buff++ =
static_cast<uint8_t
>((value >> 8) & 0xff);
382 *buff++ =
static_cast<uint8_t
>(value & 0xff);
386 seekState->seekOffsets[ioIndex] =
387 static_cast<size_t>(((uint16_t)buff[0] << 8) | (uint16_t)buff[1]);
388 seekState->readProgressWords[ioIndex] = 0;
389 buff +=
sizeof(uint16_t);
399 io = getIO_(reg->cfg->objid[ioIndex]);
406 size_t accessWords = windowRegs - inIo;
412 iodata = reg->iodata[ioIndex];
413 ptr =
reinterpret_cast<uint8_t *
>(iodata->
getDataPtr());
414 size_t accessBytes = accessWords *
sizeof(uint16_t);
416 std::memset(ptr, 0, accessBytes);
418 size_t byteOffset = seekState->seekOffsets[ioIndex] + (inIo *
sizeof(uint16_t));
423 if (byteOffset < totalBytes)
425 ret = io->
getData(windowData, 1, byteOffset);
433 for (i = 0; i < accessWords; i++)
436 static_cast<uint16_t
>(ptr[i * 2]) | (
static_cast<uint16_t
>(ptr[i * 2 + 1]) << 8);
437 *buff++ =
static_cast<uint8_t
>((value >> 8) & 0xff);
438 *buff++ =
static_cast<uint8_t
>(value & 0xff);
441 size_t progressedWords = seekState->readProgressWords[ioIndex];
444 progressedWords = accessWords;
446 else if (progressedWords == inIo)
448 progressedWords += accessWords;
455 if (progressedWords >= windowRegs)
457 seekState->seekOffsets[ioIndex] += windowRegs *
sizeof(uint16_t);
461 seekState->readProgressWords[ioIndex] = progressedWords;
465 for (i = 0; i < accessWords; i++)
467 ptr[i * 2] = buff[1];
468 ptr[i * 2 + 1] = buff[0];
469 buff +=
sizeof(uint16_t);
472 ret = io->
setData(windowData, byteOffset);
479 seekState->readProgressWords[ioIndex] = 0;
482 index +=
static_cast<int>(accessWords);
483 n -=
static_cast<uint16_t
>(accessWords);
490#if defined(CONFIG_DAWN_PROTO_MODBUS_INPUT) || defined(CONFIG_DAWN_PROTO_MODBUS_HOLDING)
493 size_t regOffset = 0;
494 for (
size_t j = 0; j < reg->
iodata.size(); j++)
496 size_t words = getIoRegBytes(reg, j) /
sizeof(uint16_t);
498 if (index ==
static_cast<int>(regOffset))
500 return static_cast<int>(j);
509int CProtoModbusRegs::standardRegRW(uint8_t *buff,
513 SProtoModbusRegs *reg)
517 uint8_t *ptr =
nullptr;
524 int ioIndex = findIoIndexByRegOffset(reg, index);
525 if (ioIndex < 0 || ioIndex >= reg->ios)
531 io = getIO_(reg->cfg->objid[ioIndex]);
532 iodata = reg->iodata[ioIndex];
533 ptr =
reinterpret_cast<uint8_t *
>(iodata->
getDataPtr());
536 if (words == 0 || words > n)
554 modbusReverseBytes(buff, ptr, words *
sizeof(uint16_t));
555 buff += words *
sizeof(uint16_t);
559 modbusReverseBytes(ptr, buff, words *
sizeof(uint16_t));
560 buff += words *
sizeof(uint16_t);
570 index +=
static_cast<int>(words);
571 n -=
static_cast<uint16_t
>(words);
577int CProtoModbusRegs::regReadWriteHolding(uint8_t *buff,
581 SProtoModbusRegs *reg)
586# ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
589 SSeekableState *seekState = findSeekableState(reg);
590 err = (seekState !=
nullptr) ? seekableRegRW(buff, n, index, read, reg, seekState) : -EIO;
595 err = standardRegRW(buff, n, index, read, reg);
603CProtoModbusRegs::~CProtoModbusRegs()
607void CProtoModbusRegs::valloc_push_back(SProtoModbusIOBind *alloc)
609 valloc.push_back(alloc);
612#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
614 size_t *seekWindowRegs,
615 bool *addSeekableState)
617 *seekWindowRegs = modbus_seekable_window_regs(tmp);
618 if (*seekWindowRegs == 0)
620 DAWNERR(
"invalid seekable window size\n");
624 *addSeekableState =
true;
629 size_t *seekWindowRegs,
630 bool *addSeekableState)
634 *addSeekableState =
false;
635 DAWNERR(
"seekable register type disabled at compile time\n");
640static int validateRegisterIo(
CIOCommon *io, uint32_t type,
size_t seekWindowRegs)
644#ifdef CONFIG_DAWN_PROTO_MODBUS_COIL
649 DAWNERR(
"seekable IO not supported for coil regs\n");
655 DAWNERR(
"coil reg doesn't support write\n");
661 DAWNERR(
"unsupported IO type for coil\n");
672 DAWNERR(
"seekable IO not supported for coil regs\n");
678 DAWNERR(
"coil reg doesn't support write\n");
684 DAWNERR(
"unsupported IO type for packed coil\n");
692#ifdef CONFIG_DAWN_PROTO_MODBUS_DISCRETE
697 DAWNERR(
"seekable IO not supported for discrete regs\n");
701 if (io->
isRead() ==
false)
703 DAWNERR(
"discrete reg doesn't support read\n");
709 DAWNERR(
"unsupported IO type for discrete\n");
720 DAWNERR(
"seekable IO not supported for discrete regs\n");
724 if (io->
isRead() ==
false)
726 DAWNERR(
"discrete reg doesn't support read\n");
732 DAWNERR(
"unsupported IO type for packed discrete\n");
740#ifdef CONFIG_DAWN_PROTO_MODBUS_INPUT
745 DAWNERR(
"seekable IO not supported for input regs\n");
749 if (io->
isRead() ==
false)
751 DAWNERR(
"input reg doesn't support read\n");
757 if (bytes == 0 || bytes %
sizeof(uint16_t) != 0)
759 DAWNERR(
"unsupported IO type for input\n");
763 return static_cast<int>(bytes);
767#ifdef CONFIG_DAWN_PROTO_MODBUS_HOLDING
772 DAWNERR(
"seekable IO requires seekable reg type\n");
778 DAWNERR(
"holding reg doesn't support write\n");
784 if (bytes == 0 || bytes %
sizeof(uint16_t) != 0)
786 DAWNERR(
"unsupported IO type for holding\n");
790 return static_cast<int>(bytes);
794#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
799 DAWNERR(
"seekable reg requires seekable IO\n");
805 DAWNERR(
"seekable reg doesn't support read\n");
809 return static_cast<int>((seekWindowRegs + 1) *
sizeof(uint16_t));
815 DAWNERR(
"Unsupported Modbus reg type %" PRIu32
"\n", type);
821int CProtoModbusRegs::finalizeRegGroup(SProtoModbusRegs *tmp,
822 const SProtoModbusIOBind *v,
823 size_t seekWindowRegs,
824 bool addSeekableState)
828#ifdef CONFIG_DAWN_PROTO_MODBUS_COIL
832 tmp->regs = tmp->size /
sizeof(uint8_t);
833 auto *overlap = hasOverlap(vcoils, v->start, tmp->regs);
834 if (overlap !=
nullptr)
836 DAWNERR(
"overlapping coil ranges: start=%" PRIu32
" count=%zu overlaps start=%" PRIu32
845 vcoils.push_back(tmp);
850#ifdef CONFIG_DAWN_PROTO_MODBUS_DISCRETE
854 tmp->regs = tmp->size /
sizeof(uint8_t);
855 auto *overlap = hasOverlap(vdiscrete, v->start, tmp->regs);
856 if (overlap !=
nullptr)
858 DAWNERR(
"overlapping discrete ranges: start=%" PRIu32
859 " count=%zu overlaps start=%" PRIu32
" count=%zu\n",
867 vdiscrete.push_back(tmp);
872#ifdef CONFIG_DAWN_PROTO_MODBUS_INPUT
875 tmp->regs = tmp->size /
sizeof(uint16_t);
876 auto *overlap = hasOverlap(vinput, v->start, tmp->regs);
877 if (overlap !=
nullptr)
879 DAWNERR(
"overlapping input ranges: start=%" PRIu32
880 " count=%zu overlaps start=%" PRIu32
" count=%zu\n",
888 vinput.push_back(tmp);
893#ifdef CONFIG_DAWN_PROTO_MODBUS_HOLDING
897 tmp->regs = tmp->size /
sizeof(uint16_t);
898 auto *overlap = hasOverlap(vholding, v->start, tmp->regs);
900 if (overlap !=
nullptr)
902 DAWNERR(
"overlapping holding ranges: start=%" PRIu32
903 " count=%zu overlaps start=%" PRIu32
" count=%zu\n",
911 vholding.push_back(tmp);
913 if (addSeekableState)
915# ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
916 SSeekableState *state =
new (std::nothrow) SSeekableState();
917 if (state ==
nullptr)
919 DAWNERR(
"failed to allocate seekable state\n");
924 state->windowRegs = seekWindowRegs;
925 state->seekOffsets.assign(v->size, 0);
926 state->readProgressWords.assign(v->size, 0);
927 vseekable.push_back(state);
937 DAWNERR(
"Unsupported Modbus reg type %" PRIu32
"\n", v->type);
945int CProtoModbusRegs::createRegs()
952 for (
auto const *v : valloc)
954 SProtoModbusRegs *tmp;
955 size_t seekWindowRegs = 0;
956 bool addSeekableState =
false;
958 tmp =
new (std::nothrow) SProtoModbusRegs();
961 DAWNERR(
"failed to allocate register handler\n");
970 tmp->iodata.reserve(v->size);
974 int ret = setupSeekableRegs(tmp, &seekWindowRegs, &addSeekableState);
981 for (
size_t i = 0; i < v->size; i++)
986 io = getIO_(v->objid[i]);
989 DAWNERR(
"Failed to get IO 0x%08" PRIx32
"\n", v->objid[i]);
993 int sizeInc = validateRegisterIo(io, v->type, seekWindowRegs);
999 tmp->size +=
static_cast<size_t>(sizeInc);
1004 iobuffer = io->
ddata_alloc(1, seekWindowRegs *
sizeof(uint16_t));
1013 DAWNERR(
"failed to allocate register buffer\n");
1017 tmp->iodata.push_back(iobuffer);
1020 int ret = finalizeRegGroup(tmp, v, seekWindowRegs, addSeekableState);
1031int CProtoModbusRegs::destroyRegs()
1038#ifdef CONFIG_DAWN_PROTO_MODBUS_COIL
1039 cleanupGroups(vcoils);
1041#ifdef CONFIG_DAWN_PROTO_MODBUS_DISCRETE
1042 cleanupGroups(vdiscrete);
1044#ifdef CONFIG_DAWN_PROTO_MODBUS_INPUT
1045 cleanupGroups(vinput);
1047#ifdef CONFIG_DAWN_PROTO_MODBUS_HOLDING
1048 cleanupGroups(vholding);
1051#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
1052 for (
auto *state : vseekable)
1061 initialized =
false;
1066#ifdef CONFIG_DAWN_PROTO_MODBUS_COIL
1067int CProtoModbusRegs::coilsCb(uint8_t *buf,
1070 enum nxmb_regmode_e mode,
1073 SProtoModbusRegs *reg =
nullptr;
1076 bool read = (mode == NXMB_REG_READ);
1080 reg = findGroup(addr, ncoils, vcoils);
1086 index = (int)(addr - reg->cfg->start);
1088 DAWNINFO(
"coilsCb %d %d %d\n", addr, ncoils, index);
1092 ret_helper = regReadWriteCoilPacked(buf, ncoils, index, read, reg);
1096 ret_helper = regReadWriteCoil(buf, ncoils, index, read, reg);
1103#ifdef CONFIG_DAWN_PROTO_MODBUS_DISCRETE
1104int CProtoModbusRegs::discreteCb(uint8_t *buf, uint16_t addr, uint16_t ndiscrete,
void *priv)
1106 SProtoModbusRegs *reg =
nullptr;
1112 reg = findGroup(addr, ndiscrete, vdiscrete);
1118 index = (int)(addr - reg->cfg->start);
1120 DAWNINFO(
"discreteCb %d %d %d\n", addr, ndiscrete, index);
1124 ret_helper = regReadWriteCoilPacked(buf, ndiscrete, index,
true, reg);
1128 ret_helper = regReadWriteCoil(buf, ndiscrete, index,
true, reg);
1135#ifdef CONFIG_DAWN_PROTO_MODBUS_INPUT
1136int CProtoModbusRegs::inputCb(uint8_t *buf, uint16_t addr, uint16_t nregs,
void *priv)
1138 SProtoModbusRegs *reg =
nullptr;
1144 reg = findGroup(addr, nregs, vinput);
1150 index = (int)(addr - reg->cfg->start);
1152 DAWNINFO(
"inputCb %d %d %d\n", addr, nregs, index);
1154 ret_helper = regReadWriteHolding(buf, nregs, index,
true, reg);
1160#ifdef CONFIG_DAWN_PROTO_MODBUS_HOLDING
1161int CProtoModbusRegs::holdingCb(uint8_t *buf,
1164 enum nxmb_regmode_e mode,
1167 SProtoModbusRegs *reg =
nullptr;
1170 bool read = (mode == NXMB_REG_READ);
1174 reg = findGroup(addr, nregs, vholding);
1180 index = (int)(addr - reg->cfg->start);
1182 DAWNINFO(
"holdingCb %d %d %d\n", addr, nregs, index);
1184 ret_helper = regReadWriteHolding(buf, nregs, index, read, reg);
Base class for all I/O objects.
virtual bool isWrite() const =0
Check if IO supports write operations.
int setData(IODataCmn &data, size_t offset=0)
Set data for I/O (public interface with stats tracking).
int getData(IODataCmn &data, size_t len, size_t offset=0)
Get data from I/O (public interface with stats tracking).
virtual bool isSeekable() const
Check if IO supports partial (seekable) access.
virtual size_t getDataDim() const =0
Get data vector dimension.
io_ddata_t * ddata_alloc(size_t batch, size_t chunk_size=0)
Allocate data buffer for this I/O.
virtual size_t getDataSize() const =0
Get data size in bytes.
virtual bool isRead() const =0
Check if IO supports read operations.
@ MODBUS_TYPE_DISCRETE_PACKED
Read-only bits packed in bytes.
@ MODBUS_TYPE_INPUT
Read-only 16-bit words.
@ MODBUS_TYPE_SEEKABLE
Seekable window over holding regs.
@ MODBUS_TYPE_COIL_PACKED
Read-write bits packed in bytes.
@ MODBUS_TYPE_COIL
Read-write bits (1 byte/bit).
@ MODBUS_TYPE_DISCRETE
Read-only bits (1 byte/bit).
@ MODBUS_TYPE_HOLDING
Read-write 16-bit words.
Out-of-tree user-extension hooks for Dawn.
uint32_t type
Register type (MODBUS_TYPE_*).
uint32_t config
Register-type private configuration word.
uint32_t start
Start register address (0x0000-0xFFFF).
Runtime register group state.
std::vector< io_ddata_t * > iodata
I/O data for each object.
const SProtoModbusIOBind * cfg
Reference to bind config.
Non-owning I/O data view over caller-provided storage.
Heap-allocated dynamic I/O data buffer.
size_t getDataSize()
Get data size in bytes.
void * getDataPtr(size_t batch=0)
Get pointer to data only (skips timestamp if present).