OpenEtherCATsociety / SOEM

Simple Open Source EtherCAT Master
Other
1.23k stars 653 forks source link

How to determine the required iomap size at runtime for dynamic allocation? #805

Open kj4tmp opened 1 month ago

kj4tmp commented 1 month ago

I am wrapping SOEM in a higher level language and I would like to dynamically allocate the iomap.

Is this the suggested methodology to determine the required iomap size at runtime?

  1. allocate an iomap at some small size (say 256 bytes)
  2. run ecx_config_map_group to obtain the required iomap size
  3. resize the iomap to the required size obtained from step 2.
  4. re-run ecx_config_map_group and double check the size.
ArthurKetels commented 1 month ago

Are you that memory size constrained? You also have to take care of the group and slave structures, they are much larger.

The original reason to have the IOmap and slave structures compile time fixed is that we can to be able to run on memory constrained embedded platforms. You have to pre-plan a worst case scenario because you do not have the luxury of a large memory pool. And if the worst-case scenario works then all smaller scenarios work too.

When you dynamically allocate memory for a real time system you have to prefault the memory before use, otherwise you get page faults and latency will suffer.

kj4tmp commented 1 month ago

I am not memory constrained nor latency critical.

I am attempting to wrap SOEM for python(https://github.com/kj4tmp/pyecm) (a garbage collected language). I cannot generally expect the user to re-compile my python extension to obtain a large IO map when needed, so I am allowing the user to pass in things like iomap size, maxslave, etc.

But since I am dynamically allocating anyway, I might as well have the iomap just resize to the correct size without requiring user input.

//C++

#include <nanobind/nanobind.h>
#include <nanobind/stl/bind_vector.h>
#include <ethercat.h>
#include <nanobind/ndarray.h>
#include <nanobind/stl/tuple.h>
#include <iostream>
namespace nb = nanobind;

using namespace nb::literals;

using BytesVector = std::vector<uint8_t>;
using BytesArray = nb::ndarray<nb::numpy, uint8_t, nb::ndim<1>, nb::c_contig>;
using ECSlaveTVector = std::vector<ec_slavet>;
using ECGroupTVector = std::vector<ec_groupt>;

class SOEM_wrapper {
    public:
    ecx_contextt context;
    ecx_portt port;
    ECSlaveTVector slavelist;
    int slavecount;
    uint16_t maxslave;
    ECGroupTVector grouplist;
    uint8_t maxgroup;
    uint8_t esibuf [EC_MAXEEPBUF]; 
    uint32_t esimap [EC_MAXEEPBITMAP];
    ec_eringt elist;
    ec_idxstackT idxstack;
    boolean ecaterror;
    int64_t DCtime;
    ec_SMcommtypet SMcommtype;
    ec_PDOassignt PDOassign;
    ec_PDOdesct PDOdesc;
    ec_eepromSMt eepSM;
    ec_eepromFMMUt eepFMMU;

    //iomap
    BytesVector iomap;

    SOEM_wrapper(uint16_t maxslave_, uint8_t maxgroup_, size_t iomap_size_bytes){
        if (maxslave_ == 0) {
                throw std::invalid_argument("maxslave cannot be zero.");
            }
        if (maxgroup_ == 0) {
            throw std::invalid_argument("maxgroup cannot be zero.");
        }
        if (iomap_size_bytes == 0) {
            throw std::invalid_argument("iomap_size_bytes cannot be zero.");
        }

        slavelist.resize(maxslave_);
        maxslave = maxslave_;
        grouplist.resize(maxgroup_);
        maxgroup = maxgroup_;
        iomap.resize(iomap_size_bytes, 0);

        context.port = &port;
        context.slavelist = slavelist.data();
        context.slavecount = &slavecount;
        context.maxslave = maxslave;
        context.grouplist = grouplist.data();
        context.maxgroup = maxgroup;
        context.esibuf = &esibuf[0];
        context.esimap = &esimap[0];
        context.esislave = 0;
        context.elist = &elist;
        context.idxstack = &idxstack;
        context.ecaterror = &ecaterror;
        context.DCtime = &DCtime;
        context.SMcommtype = &SMcommtype;
        context.PDOassign = &PDOassign;
        context.PDOdesc = &PDOdesc;
        context.eepSM = &eepSM;
        context.eepFMMU = &eepFMMU;
        context.FOEhook = nullptr;
        context.EOEhook = nullptr;
        context.manualstatechange = 0;
        context.userdata = nullptr;
    }
    auto close(){
        return ecx_close(&this->context);
    }
ArthurKetels commented 1 month ago

Thanks for the context, now I understand better what you want to do and why.

Your fist post is the correct way to handle an optimal size IOmap. The disadvantage is that it takes more time because the mapping of slaves has to be done twice. And this is relatively slow. If you have hooks installed for special slave configuration, it gets even slower. Also new slaves might appear and existing slaves disappear.

The compute optimal way is to traverse the slavelist and grouplist data structure and recompute all pointer references to IOmap. In such a way the relative offsets remain the same between the old IOmap location and the new IOmap location.

As long you do not start a process data transmit/receive the IOmap will not be referenced. So it is safe to manipulate the pointers.

kj4tmp commented 1 month ago

For my understanding, if I use ecx_config_map_group with a group other than zero, I should provide a dedicated iomap for it?

But if I use group=0 then I can use the same iomap for all groups?