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();
149 size_t regOffset = 0;
151 for (
size_t j = 0; j < reg->
iodata.size(); j++)
153 size_t bytes = getIoRegBytes(reg, j);
155 if (index >=
static_cast<int>(regOffset) && index <
static_cast<int>(regOffset + bytes))
157 *offsetInIo =
static_cast<size_t>(index) - regOffset;
158 return static_cast<int>(j);
167#if defined(CONFIG_DAWN_PROTO_MODBUS_COIL) || defined(CONFIG_DAWN_PROTO_MODBUS_DISCRETE)
168int CProtoModbusRegs::regReadWriteCoil(uint8_t *buff,
172 SProtoModbusRegs *reg)
176 uint8_t *ptr =
nullptr;
185 if (index >= reg->ios)
191 io = getIO_(reg->cfg->objid[index]);
192 iodata = reg->iodata[index];
193 ptr =
reinterpret_cast<uint8_t *
>(iodata->
getDataPtr());
211 for (
size_t i = 0; i < size; i++)
218 for (
size_t i = 0; i < size; i++)
231 index +=
static_cast<int>(size);
232 n -=
static_cast<uint16_t
>(size);
239int CProtoModbusRegs::regReadWriteCoilPacked(uint8_t *buff,
243 SProtoModbusRegs *reg)
247 uint8_t *ptr =
nullptr;
255 memset(buff, 0, (n + 7) / 8);
258 for (uint16_t bit = 0; bit < n; bit++)
260 size_t offsetInIo = 0;
261 const int io_index = findIoIndexByByteOffset(reg, index + bit, &offsetInIo);
263 if (io_index < 0 || io_index >= reg->ios)
274 io = getIO_(reg->cfg->objid[io_index]);
275 iodata = reg->iodata[io_index];
276 ptr =
reinterpret_cast<uint8_t *
>(iodata->
getDataPtr());
279 if (offsetInIo >= size)
294 modbus_set_bit(buff, bit, (ptr[offsetInIo] == 0) ? 0 : 1);
305 ptr[offsetInIo] = modbus_get_bit(buff, bit) ? 1 : 0;
321#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
322int CProtoModbusRegs::seekableRegRW(uint8_t *buff,
326 SProtoModbusRegs *reg,
327 SSeekableState *seekState)
331 uint8_t *ptr =
nullptr;
337 size_t windowRegs = seekState->windowRegs;
338 size_t regsPerIo = windowRegs + 1;
348 size_t ioIndex =
static_cast<size_t>(index) / regsPerIo;
349 size_t inIo =
static_cast<size_t>(index) % regsPerIo;
350 if (ioIndex >=
static_cast<size_t>(reg->ios))
358 if (inIo == windowRegs)
362 seekState->readProgressWords[ioIndex] = 0;
363 value =
static_cast<uint16_t
>(seekState->seekOffsets[ioIndex]);
364 *buff++ =
static_cast<uint8_t
>((value >> 8) & 0xff);
365 *buff++ =
static_cast<uint8_t
>(value & 0xff);
369 seekState->seekOffsets[ioIndex] =
370 static_cast<size_t>(((uint16_t)buff[0] << 8) | (uint16_t)buff[1]);
371 seekState->readProgressWords[ioIndex] = 0;
372 buff +=
sizeof(uint16_t);
382 io = getIO_(reg->cfg->objid[ioIndex]);
389 size_t accessWords = windowRegs - inIo;
395 iodata = reg->iodata[ioIndex];
396 ptr =
reinterpret_cast<uint8_t *
>(iodata->
getDataPtr());
397 size_t accessBytes = accessWords *
sizeof(uint16_t);
399 std::memset(ptr, 0, accessBytes);
401 size_t byteOffset = seekState->seekOffsets[ioIndex] + (inIo *
sizeof(uint16_t));
406 if (byteOffset < totalBytes)
408 ret = io->
getData(windowData, 1, byteOffset);
416 for (i = 0; i < accessWords; i++)
419 static_cast<uint16_t
>(ptr[i * 2]) | (
static_cast<uint16_t
>(ptr[i * 2 + 1]) << 8);
420 *buff++ =
static_cast<uint8_t
>((value >> 8) & 0xff);
421 *buff++ =
static_cast<uint8_t
>(value & 0xff);
424 size_t progressedWords = seekState->readProgressWords[ioIndex];
427 progressedWords = accessWords;
429 else if (progressedWords == inIo)
431 progressedWords += accessWords;
438 if (progressedWords >= windowRegs)
440 seekState->seekOffsets[ioIndex] += windowRegs *
sizeof(uint16_t);
444 seekState->readProgressWords[ioIndex] = progressedWords;
448 for (i = 0; i < accessWords; i++)
450 ptr[i * 2] = buff[1];
451 ptr[i * 2 + 1] = buff[0];
452 buff +=
sizeof(uint16_t);
455 ret = io->
setData(windowData, byteOffset);
462 seekState->readProgressWords[ioIndex] = 0;
465 index +=
static_cast<int>(accessWords);
466 n -=
static_cast<uint16_t
>(accessWords);
473#if defined(CONFIG_DAWN_PROTO_MODBUS_INPUT) || defined(CONFIG_DAWN_PROTO_MODBUS_HOLDING)
476 size_t regOffset = 0;
477 for (
size_t j = 0; j < reg->
iodata.size(); j++)
479 size_t words = getIoRegBytes(reg, j) /
sizeof(uint16_t);
481 if (index ==
static_cast<int>(regOffset))
483 return static_cast<int>(j);
492int CProtoModbusRegs::standardRegRW(uint8_t *buff,
496 SProtoModbusRegs *reg)
500 uint8_t *ptr =
nullptr;
509 int ioIndex = findIoIndexByRegOffset(reg, index);
510 if (ioIndex < 0 || ioIndex >= reg->ios)
516 io = getIO_(reg->cfg->objid[ioIndex]);
517 iodata = reg->iodata[ioIndex];
518 ptr =
reinterpret_cast<uint8_t *
>(iodata->
getDataPtr());
536 for (i = 0; i < words; i++)
539 static_cast<uint16_t
>(ptr[i * 2]) | (
static_cast<uint16_t
>(ptr[i * 2 + 1]) << 8);
540 *buff++ =
static_cast<uint8_t
>((value >> 8) & 0xff);
541 *buff++ =
static_cast<uint8_t
>(value & 0xff);
546 for (i = 0; i < words; i++)
548 ptr[i * 2] = buff[1];
549 ptr[i * 2 + 1] = buff[0];
550 buff +=
sizeof(uint16_t);
561 index +=
static_cast<int>(words);
562 n -=
static_cast<uint16_t
>(words);
568int CProtoModbusRegs::regReadWriteHolding(uint8_t *buff,
572 SProtoModbusRegs *reg)
577# ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
580 SSeekableState *seekState = findSeekableState(reg);
581 err = (seekState !=
nullptr) ? seekableRegRW(buff, n, index, read, reg, seekState) : -EIO;
586 err = standardRegRW(buff, n, index, read, reg);
594CProtoModbusRegs::~CProtoModbusRegs()
598void CProtoModbusRegs::valloc_push_back(SProtoModbusIOBind *alloc)
600 valloc.push_back(alloc);
603#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
605 size_t *seekWindowRegs,
606 bool *addSeekableState)
608 *seekWindowRegs = modbus_seekable_window_regs(tmp);
609 if (*seekWindowRegs == 0)
611 DAWNERR(
"invalid seekable window size\n");
615 *addSeekableState =
true;
620 size_t *seekWindowRegs,
621 bool *addSeekableState)
625 *addSeekableState =
false;
626 DAWNERR(
"seekable register type disabled at compile time\n");
631static int validateRegisterIo(
CIOCommon *io, uint32_t type,
size_t seekWindowRegs)
635#ifdef CONFIG_DAWN_PROTO_MODBUS_COIL
640 DAWNERR(
"seekable IO not supported for coil regs\n");
646 DAWNERR(
"coil reg doesn't support write\n");
650 if (io->
isRead() ==
false)
652 DAWNERR(
"coil reg doesn't support read\n");
658 DAWNERR(
"unsupported IO type for coil\n");
669 DAWNERR(
"seekable IO not supported for coil regs\n");
675 DAWNERR(
"coil reg doesn't support write\n");
679 if (io->
isRead() ==
false)
681 DAWNERR(
"coil reg doesn't support read\n");
687 DAWNERR(
"unsupported IO type for packed coil\n");
695#ifdef CONFIG_DAWN_PROTO_MODBUS_DISCRETE
700 DAWNERR(
"seekable IO not supported for discrete regs\n");
704 if (io->
isRead() ==
false)
706 DAWNERR(
"discrete reg doesn't support read\n");
712 DAWNERR(
"unsupported IO type for discrete\n");
723 DAWNERR(
"seekable IO not supported for discrete regs\n");
727 if (io->
isRead() ==
false)
729 DAWNERR(
"discrete reg doesn't support read\n");
735 DAWNERR(
"unsupported IO type for packed discrete\n");
743#ifdef CONFIG_DAWN_PROTO_MODBUS_INPUT
748 DAWNERR(
"seekable IO not supported for input regs\n");
752 if (io->
isRead() ==
false)
754 DAWNERR(
"input reg doesn't support read\n");
760 if (bytes == 0 || bytes %
sizeof(uint16_t) != 0)
762 DAWNERR(
"unsupported IO type for input\n");
766 return static_cast<int>(bytes);
770#ifdef CONFIG_DAWN_PROTO_MODBUS_HOLDING
775 DAWNERR(
"seekable IO requires seekable reg type\n");
781 DAWNERR(
"holding reg requires rw IO\n");
787 if (bytes == 0 || bytes %
sizeof(uint16_t) != 0)
789 DAWNERR(
"unsupported IO type for holding\n");
793 return static_cast<int>(bytes);
797#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
802 DAWNERR(
"seekable reg requires seekable IO\n");
808 DAWNERR(
"seekable reg doesn't support read\n");
812 return static_cast<int>((seekWindowRegs + 1) *
sizeof(uint16_t));
818 DAWNERR(
"Unsupported Modbus reg type %" PRIu32
"\n", type);
824int CProtoModbusRegs::finalizeRegGroup(SProtoModbusRegs *tmp,
825 const SProtoModbusIOBind *v,
826 size_t seekWindowRegs,
827 bool addSeekableState)
831#ifdef CONFIG_DAWN_PROTO_MODBUS_COIL
835 tmp->regs = tmp->size /
sizeof(uint8_t);
836 auto *overlap = hasOverlap(vcoils, v->start, tmp->regs);
837 if (overlap !=
nullptr)
839 DAWNERR(
"overlapping coil ranges: start=%" PRIu32
" count=%zu overlaps start=%" PRIu32
848 vcoils.push_back(tmp);
853#ifdef CONFIG_DAWN_PROTO_MODBUS_DISCRETE
857 tmp->regs = tmp->size /
sizeof(uint8_t);
858 auto *overlap = hasOverlap(vdiscrete, v->start, tmp->regs);
859 if (overlap !=
nullptr)
861 DAWNERR(
"overlapping discrete ranges: start=%" PRIu32
862 " count=%zu overlaps start=%" PRIu32
" count=%zu\n",
870 vdiscrete.push_back(tmp);
875#ifdef CONFIG_DAWN_PROTO_MODBUS_INPUT
878 tmp->regs = tmp->size /
sizeof(uint16_t);
879 auto *overlap = hasOverlap(vinput, v->start, tmp->regs);
880 if (overlap !=
nullptr)
882 DAWNERR(
"overlapping input ranges: start=%" PRIu32
883 " count=%zu overlaps start=%" PRIu32
" count=%zu\n",
891 vinput.push_back(tmp);
896#ifdef CONFIG_DAWN_PROTO_MODBUS_HOLDING
900 tmp->regs = tmp->size /
sizeof(uint16_t);
901 auto *overlap = hasOverlap(vholding, v->start, tmp->regs);
903 if (overlap !=
nullptr)
905 DAWNERR(
"overlapping holding ranges: start=%" PRIu32
906 " count=%zu overlaps start=%" PRIu32
" count=%zu\n",
914 vholding.push_back(tmp);
916 if (addSeekableState)
918# ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
919 SSeekableState *state =
new (std::nothrow) SSeekableState();
920 if (state ==
nullptr)
922 DAWNERR(
"failed to allocate seekable state\n");
927 state->windowRegs = seekWindowRegs;
928 state->seekOffsets.assign(v->size, 0);
929 state->readProgressWords.assign(v->size, 0);
930 vseekable.push_back(state);
940 DAWNERR(
"Unsupported Modbus reg type %" PRIu32
"\n", v->type);
948int CProtoModbusRegs::createRegs()
955 for (
auto const *v : valloc)
957 SProtoModbusRegs *tmp;
958 size_t seekWindowRegs = 0;
959 bool addSeekableState =
false;
961 tmp =
new (std::nothrow) SProtoModbusRegs();
964 DAWNERR(
"failed to allocate register handler\n");
973 tmp->iodata.reserve(v->size);
977 int ret = setupSeekableRegs(tmp, &seekWindowRegs, &addSeekableState);
984 for (
size_t i = 0; i < v->size; i++)
989 io = getIO_(v->objid[i]);
992 DAWNERR(
"Failed to get IO 0x%08" PRIx32
"\n", v->objid[i]);
996 int sizeInc = validateRegisterIo(io, v->type, seekWindowRegs);
1002 tmp->size +=
static_cast<size_t>(sizeInc);
1007 iobuffer = io->
ddata_alloc(1, seekWindowRegs *
sizeof(uint16_t));
1016 DAWNERR(
"failed to allocate register buffer\n");
1020 tmp->iodata.push_back(iobuffer);
1023 int ret = finalizeRegGroup(tmp, v, seekWindowRegs, addSeekableState);
1034int CProtoModbusRegs::destroyRegs()
1041#ifdef CONFIG_DAWN_PROTO_MODBUS_COIL
1042 cleanupGroups(vcoils);
1044#ifdef CONFIG_DAWN_PROTO_MODBUS_DISCRETE
1045 cleanupGroups(vdiscrete);
1047#ifdef CONFIG_DAWN_PROTO_MODBUS_INPUT
1048 cleanupGroups(vinput);
1050#ifdef CONFIG_DAWN_PROTO_MODBUS_HOLDING
1051 cleanupGroups(vholding);
1054#ifdef CONFIG_DAWN_PROTO_MODBUS_SEEKABLE
1055 for (
auto *state : vseekable)
1064 initialized =
false;
1069#ifdef CONFIG_DAWN_PROTO_MODBUS_COIL
1070int CProtoModbusRegs::coilsCb(uint8_t *buf,
1073 enum nxmb_regmode_e mode,
1076 SProtoModbusRegs *reg =
nullptr;
1079 bool read = (mode == NXMB_REG_READ);
1083 reg = findGroup(addr, ncoils, vcoils);
1089 index = (int)(addr - reg->cfg->start);
1091 DAWNINFO(
"coilsCb %d %d %d\n", addr, ncoils, index);
1095 ret_helper = regReadWriteCoilPacked(buf, ncoils, index, read, reg);
1099 ret_helper = regReadWriteCoil(buf, ncoils, index, read, reg);
1106#ifdef CONFIG_DAWN_PROTO_MODBUS_DISCRETE
1107int CProtoModbusRegs::discreteCb(uint8_t *buf, uint16_t addr, uint16_t ndiscrete,
void *priv)
1109 SProtoModbusRegs *reg =
nullptr;
1115 reg = findGroup(addr, ndiscrete, vdiscrete);
1121 index = (int)(addr - reg->cfg->start);
1123 DAWNINFO(
"discreteCb %d %d %d\n", addr, ndiscrete, index);
1127 ret_helper = regReadWriteCoilPacked(buf, ndiscrete, index,
true, reg);
1131 ret_helper = regReadWriteCoil(buf, ndiscrete, index,
true, reg);
1138#ifdef CONFIG_DAWN_PROTO_MODBUS_INPUT
1139int CProtoModbusRegs::inputCb(uint8_t *buf, uint16_t addr, uint16_t nregs,
void *priv)
1141 SProtoModbusRegs *reg =
nullptr;
1147 reg = findGroup(addr, nregs, vinput);
1153 index = (int)(addr - reg->cfg->start);
1155 DAWNINFO(
"inputCb %d %d %d\n", addr, nregs, index);
1157 ret_helper = regReadWriteHolding(buf, nregs, index,
true, reg);
1163#ifdef CONFIG_DAWN_PROTO_MODBUS_HOLDING
1164int CProtoModbusRegs::holdingCb(uint8_t *buf,
1167 enum nxmb_regmode_e mode,
1170 SProtoModbusRegs *reg =
nullptr;
1173 bool read = (mode == NXMB_REG_READ);
1177 reg = findGroup(addr, nregs, vholding);
1183 index = (int)(addr - reg->cfg->start);
1185 DAWNINFO(
"holdingCb %d %d %d\n", addr, nregs, index);
1187 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).