Driver Development

1. Indroduction
2. Interface
3. Module Implementation Step by Step
3.1. Create Module’s Files
3.2. Adding Module to the Build Process
3.3. Registering Module in the System
3.4. Initializing Module
3.5. Module Usage
3.6. Creating Configuration Script
4. Summary

1. Introduction

The drivers (that are part of the modules) are based on the unified software architecture; in other words, each driver has the same unified interface. The dnx RTOS uses suitable interfaces to communicate with drivers (devices). The interfaces are defined as functions and all of those functions must exist in a particular module. Each module can operate on several devices of the same kind, e.g. UART (UART1, UART2), SPI (SPI1, SPI2), etc. Each peripheral interface has usually the same registers, interrupts, and functionality. Some parts of the device can be different, but usually the difference is not significant. In summary, one module can handle many devices of the same kind. To handle all devices (of the same type) by one module, the user defines major and minor numbers that precise which device should be initialized. The pair of those numbers is called the interface or simply the driver. For example, the major number in the UART devices means that the user selects a peripheral number: UART1, UART2, etc. In this case, the minor number is not used because the UART peripheral cannot be divided into next parts (sub-devices). This is different for the SPI peripherals. The major number means that the user selects a particular SPI device: SPI1, SPI2, etc; the minor number means that the user selects a specified Chip Select: CS0, CS1, etc. As we can see, those numbers can be easily adjusted to the user needs and the peripheral restrictions.

A module is created for the specified microcontroller architecture or for any architecture when the code can work on any microcontroller, e.g. the TTY module. The modules are localized in the ./src/system/drivers folder. Each driver has its own folder, Makefile, and source files. There are few files that are required:

  • *_cfg.h – the file contains module configuration flags. These flags are connected to the main configuration localized in the ./config folder;
  • *_def.h – the file contains definitions that are used in the module and the system, e.g. the major and minor numbers;
  • *_ioctl.h – the file contains IO control requests and description referring to its usage;
  • *.c – the source files;
  • Makefile – the file indicates source files for compilation.

A new module can be added by using Configtool wizard. It is the simplest way to add a new module to the system with the minimal effort. This path is recommended in most cases.

2. Interface

The module is handled by specified interface functions:

  • API_MOD_INIT() – the function initializes the module. In this request the module will allocate memory (if needed), and other resources (e.g. semaphores).
  • API_MOD_RELEASE() – the function releases the allocated memory and used resources.
  • API_MOD_OPEN() – the function opens the device to read, write, and control operations.
  • API_MOD_CLOSE() – the function closes the opened device.
  • API_MOD_WRITE() – the function writes a data to the opened device.
  • API_MOD_READ() – the function reads a data from the opened device.
  • API_MOD_IOCTL() – the function does miscellaneous requests specified for the particular device.
  • API_MOD_FLUSH() – the function flushes the device cache (if it exists).
  • API_MOD_STAT() – the function gets information about the device (a size, the major and minor numbers).

The interface functions are created by macros, and due to this fact the name of module functions is hidden and known only by the system. The return type of the functions is also hidden, but in order to provide better understanding of modules the returned type is enclosed in square brackets.

2.1. The API_MOD_INIT()

[stdret_t] API_MOD_INIT(module_name,
                        void **device_handle,
                        u8_t major,
                        u8_t minor)

The function is called when the operating system, on the user request (by the driver_init() function), initializes a driver. Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module. This is the output pointer. A data from this pointer is used to pass the driver instance;
major a major device number;
minor a minor device number.

When the module is correctly initialized then the STD_RET_OK is returned, otherwise the STD_RET_ERROR and an appropriate errno value will be set.

2.2. The API_MOD_RELEASE()

[stdret_t] API_MOD_RELEASE(module_name,
                           void *device_handle)

The function is called when the operating system, on the user request (by the driver_release() function), releases a driver. Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module (the driver instance).

When the module is correctly released then the STD_RET_OK is returned, otherwise the STD_RET_ERROR and an appropriate errno value will be set.

2.3. The API_MOD_OPEN()

[stdret_t] API_MOD_OPEN(module_name,
                        void *device_handle,
                        vfs_open_flags_t flags)

The function is called when the device is opening (the user is opening device-file by using the fopen() function). Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module (the driver instance);
flags a flags that determines the file open operations. Possible flags: O_RDONLY, O_WRONLY, O_RDWR.

When the device is correctly opened then the STD_RET_OK is returned, otherwise the STD_RET_ERROR and an appropriate errno value will be set.

2.4. The API_MOD_CLOSE()

[stdret_t] API_MOD_CLOSE(module_name,
                         void *device_handle,
                         bool force)

The unction is called when the device is closing (the user is closing device-file by using the fclose() function). When the force flag is true then the device should be immediately closed. The force signal is set only by the operating system when owner task is killed. Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module (the driver instance);
force a flag that indicates the force close of the device.

When the device is correctly closed then the STD_RET_OK is returned, otherwise the STD_RET_ERROR and an appropriate errno value will be set. If the force flag is set then the STD_RET_OK will be returned.

2.5. The API_MOD_WRITE()

[ssize_t] API_MOD_WRITE(module_name,
                        void *device_handle,
                        const u8_t *src,
                        size_t count,
                        fpos_t *fpos,
                        struct vfs_fattr fattr)

The function is called when the user wants to write a data to the device. A data pointed by src of size count is write to the position pointed by fpos. A data is always represented in bytes. A write operation can be performed according to attributes fattr (non-blocking write). Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module (the driver instance);
src a data source pointer;
count a number of bytes to write;
fattr an attributes of write operation.

The device on success, returns a number of written bytes. On error, -1 is returned and an appropriate errno value will be set.

2.6. The API_MOD_READ()

[ssize_t] API_MOD_READ(module_name,
                       void *device_handle,
                       u8_t *dst,
                       size_t count,
                       fpos_t *fpos,
                       struct vfs_fattr fattr)

The function is called when the user wants to read a data from the device. A data pointed by dst of size count is reads from the position pointed by fpos. A data is always represented in bytes. A read operation can be performed according to attributes fattr (non-blocking read). Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module (the driver instance);
dst a data destination pointer;
count a number of bytes to read;
fattr an attributes of read operation.

The device on success, returns a number of read bytes. On error, -1 is returned and an appropriate errno value will be set.

2.7. The API_MOD_IOCTL()

[int] API_MOD_IOCTL(module_name,
                    void *device_handle,
                    int request,
                    void *arg)

The function is called when the user wants to do not precises by the interface operations (all non-standard actions). The function handle a request request with an argument pointed by arg. The argument is not necessary for all requests, some has not any arguments. If a request require a other type argument, then can be passed by pointer. Some requests require an integer arguments, in this case a pointer can be used as integer value directly. Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module (the driver instance);
request a request ID. The driver defines own pool of requests. The requests are defined in the *_ioctl.h file;
arg an argument of the request.

The return behavior is depending on the module implementation. A some ioctls returns the STD_RET_OK or the STD_RET_ERROR, but can also returns a negative values for errors and a positive value for the valid operations. In general, the return value will be an integer value.

2.8. The API_MOD_FLUSH()

[stdret_t] API_MOD_FLUSH(module_name,
                         void *device_handle)

The unction is called when the user or the system wants to flush a cached buffers to the device. Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module (the driver instance).

The driver on success returns the STD_RET_OK. On error, the STD_RET_ERROR is returned.

2.9. The API_MOD_STAT()

[stdret_t] API_MOD_STAT(module_name,
                        void *device_handle,
                        struct vfs_dev_stat *device_stat)

The function is called by the system if it wants to receive the device information (a size, the major and minor numbers). Arguments:

module_name a name of the module
device_handle a pointer to the memory region allocated by the module (the driver instance);
device_stat a pointer to the device information structure.

The driver returns the STD_RET_OK when information was updated, otherwise the STD_RET_ERROR.

3. Module Implementation Step by Step

The best way to understand how implement the module is a simple example. For this presentation purposes, we assume that our module will be called MY_MODULE. The module requirements:

  • The module will allocate the memory for internal purposes (a data buffer);
  • The module can be opened only by one program, the other programs will receive the EBUSY errno value;
  • A write will affect that the internal buffer of the module will be overwritten by the incoming data, with taking into account the file position;
  • A read access will affect that the internal buffer of module will be passed to application, with taking into account the file position;
  • The internal buffer of the module will be cleared if a specified IO control request will be invoked;
  • A flush method will be not used;
  • The information of the module will be passed to the system structure. The specified information will be passed: an internal buffer size and the major and minor numbers;
  • The module will be an virtual, it will not depends on the architecture of the microcontroller;
  • The size of the internal buffer of the module will be configurable from the configuration tool.

3.1. Create Module’s Files

To start working on the module code, we need the structure of the basic files. The templates of the files can be copied from the ./src/extra/templates/genericmod folder. If we use the Configtool and new module creator then we do not need to follow with below points. When you choice the “hard way” follow:

  1. Go to the ./src/system/drivers directory and create a new folder: my_module, and go to it,
  2. Create the specific files: my_module.c, my_module_def.h, my_module_ioctl.h, my_module_cfg.h,
  3. Then open the my_module_def.h file, and add the content:
    #ifndef _MY_MODULE_DEF_H_
    #define _MY_MODULE_DEF_H_
    
    #define _MY_MODULE_MAJOR_NUMBER 0
    #define _MY_MODULE_MINOR_NUMBER 0
    
    #endif /* _MY_MODULE_DEF_H_ */
        
  4. Save and close the my_module_def.h file. Open the my_module_cfg.h file, and add the content:
    #ifndef _MY_MODULE_CFG_H_
    #define _MY_MODULE_CFG_H_
    
    #define _MY_MODULE_CFG_BFR_SIZE 128
    
    #endif /* _MY_MODULE_CFG_H_ */
        
  5. Save and close the my_module_cfg.h file. Open the my_module_ioctl.h file, and add the content:
    #ifndef _MY_MODULE_IOCTL_H_
    #define _MY_MODULE_IOCTL_H_
    
    #include "core/ioctl_macros.h"
    
    #define IOCTL_MY_MODULE__CLEAR_BUFFER    _IO(MY_MODULE, 0)
    
    #endif /* _MY_MODULE_IOCTL_H_ */
        
  6. Save and close the my_module_ioctl.h file. Open the my_module.c file, and add the content:
    /* --- includes --- */
    #include "core/module.h"
    #include "my_module_cfg.h"
    #include "my_module_def.h"
    #include "my_module_ioctl.h"
    #include <dnx/misc.h>
    #include <dnx/thread.h>
    
    
    /* --- module name --- */
    MODULE_NAME("MY_MODULE");
    
    
    /* --- Local types, enums definitions --- */
    typedef struct {
            dev_lock_t lock;
            u8_t       buffer[_MY_MODULE_CFG_BFR_SIZE];
    } my_module_mem_t;
    
    
    /* --- Function definitions --- */
    
    API_MOD_INIT(MY_MODULE, void **device_handle, u8_t major, u8_t minor)
    {
            if (  major == _MY_MODULE_MAJOR_NUMBER
               && minor == _MY_MODULE_MINOR_NUMBER ) {
    
                    /* allocate memory for module */
                    my_module_mem_t *hdl = calloc(1, sizeof(my_module_mem_t));
                    if (hdl) {
                            *device_handle = hdl;
                            return STD_RET_OK;
                    } else {
                            /* errno = ENOMEM set in calloc() */
                            return STD_RET_ERROR;
                    }
            }
    
            errno = EINVAL;
            return STD_RET_ERROR;
    }
    
    
    API_MOD_RELEASE(MY_MODULE, void *device_handle)
    {
            my_module_mem_t *hdl = device_handle;
            stdret_t         ret = STD_RET_ERROR;
    
            critical_section_begin();
            if (device_is_unlocked(hdl->lock)) {
                    free(hdl);
                    ret = STD_RET_OK;
            } else {
                    errno = EBUSY;
            }
    
            critical_section_end();
            return ret;
    }
    
    
    API_MOD_OPEN(MY_MODULE, void *device_handle, vfs_open_flags_t flags)
    {
            UNUSED_ARG(flags);
    
            my_module_mem_t *hdl = device_handle;
    
            return device_lock(&hdl->lock) ? STD_RET_OK : STD_RET_ERROR;
    }
    
    
    API_MOD_CLOSE(MY_MODULE, void *device_handle, bool force)
    {
            my_module_mem_t *hdl = device_handle;
    
            if (device_is_access_granted(&hdl->lock) || force) {
                    device_unlock(&hdl->lock, force);
                    return STD_RET_OK;
            } else {
                    errno = EBUSY;
                    return STD_RET_ERROR;
            }
    }
    
    
    API_MOD_WRITE(MY_MODULE, void *device_handle, const u8_t *src, size_t count, fpos_t *fpos, struct vfs_fattr fattr)
    {
            UNUSED_ARG(fattr);
    
            my_module_mem_t *hdl = device_handle;
    
            if (device_is_access_granted(&hdl->lock)) {
    
                    if (*fpos < _MY_MODULE_CFG_BFR_SIZE) {
    
                            size_t size;
                            if (count + *fpos > _MY_MODULE_CFG_BFR_SIZE) {
                                    size = _MY_MODULE_CFG_BFR_SIZE - *fpos;
                            } else {
                                    size = count;
                            }
    
                            memcpy(hdl->buffer + *fpos, src, size);
    
                            return size;
    
                    } else {
                            return 0;
                    }
    
            } else {
                    errno = EBUSY;
                    return -1;
            }
    }
    
    
    API_MOD_READ(MY_MODULE, void *device_handle, u8_t *dst, size_t count, fpos_t *fpos, struct vfs_fattr fattr)
    {
            UNUSED_ARG(fattr);
    
            my_module_mem_t *hdl = device_handle;
    
            if (device_is_access_granted(&hdl->lock)) {
    
                    if (*fpos < _MY_MODULE_CFG_BFR_SIZE) {
    
                            size_t size;
                            if (count + *fpos > _MY_MODULE_CFG_BFR_SIZE) {
                                    size = _MY_MODULE_CFG_BFR_SIZE - *fpos;
                            } else {
                                    size = count;
                            }
    
                            memcpy(dst, hdl->buffer + *fpos, size);
    
                            return size;
    
                    } else {
                            return 0;
                    }
    
            } else {
                    errno = EBUSY;
                    return -1;
            }
    }
    
    
    API_MOD_IOCTL(MY_MODULE, void *device_handle, int request, void *arg)
    {
            UNUSED_ARG(arg);
    
            my_module_mem_t *hdl = device_handle;
    
            if (device_is_access_granted(&hdl->lock)) {
    
                    switch (request) {
                    case IOCTL_MY_MODULE__CLEAR_BUFFER:
                            memset(hdl->buffer, 0, _MY_MODULE_CFG_BFR_SIZE);
                            return STD_RET_OK;
                    default:
                            errno = EBADRQC;
                            return STD_RET_ERROR;
                    }
    
            } else {
                    errno = EBUSY;
                    return STD_RET_ERROR;
            }
    }
    
    
    API_MOD_FLUSH(MY_MODULE, void *device_handle)
    {
            UNUSED_ARG(device_handle);
    
            /* do nothing and always return OK */
    
            return STD_RET_OK;
    }
    
    
    API_MOD_STAT(MY_MODULE, void *device_handle, struct vfs_dev_stat *device_stat)
    {
            UNUSED_ARG(device_handle);
    
            device_stat->st_size  = _MY_MODULE_CFG_BFR_SIZE;
            device_stat->st_major = _MY_MODULE_MAJOR_NUMBER;
            device_stat->st_minor = _MY_MODULE_MINOR_NUMBER;
    
            return STD_RET_OK;
    }
        
  7. save and close the my_module.c file.

This implementation can be done after adding the files to the Makefile, because of convenience reasons.

3.2. Adding Module to the Build Process

When module was added by using Configtool then this steps can be skipped. This step can be done parallel to creation of the files of the module. To add the files to the build process, follow:

  1. Go to the ./src/system/drivers/my_module folder and create the Makefile file with the content:
    # Makefile for GNU make
    HDRLOC_ARCH += drivers/my_module
    
    ifeq ($(ENABLE_MY_MODULE), __YES__)
       CSRC_ARCH   += drivers/my_module/my_module.c
       CXXSRC_ARCH +=
    endif
        

    If the module depends on the microcontroller architecture (e.g. stm32f1), then add specified content:

    # Makefile for GNU make
    HDRLOC_ARCH += drivers/my_module
    
    ifeq ($(ENABLE_MY_MODULE), __YES__)
       ifeq ($(TARGET), stm32f1)
          CSRC_ARCH   += drivers/my_module/$(TARGET)/my_module.c
          CXXSRC_ARCH +=
       endif
    endif
        
  2. Save and close the created file. Open the ./src/system/drivers/Makefile file, and add the specified line:
    include $(DRV_LOC)/my_module/Makefile
        
  3. Save and close this file. As we can see, the module is compiled only when the ENABLE_MY_MODULE variable is defined and set to the __YES__ value. This flag can be modified during the configuration. To add this flag, please open the ./config/project/Makefile file, and add specified line to the modules enable flags section:
    ENABLE_MY_MODULE=__YES__
        
  4. Save and close this file. Open the ./config/project/flags.h file, and add specified line to the modules enable flags section:
    #define __ENABLE_MY_MODULE__ __YES__
        

After those steps the module is compiled in the build process. The module can be enabled or disabled by the configuration script (if was implemented).

3.3. Registering Module in the System

When module was added by using Configtool then this steps can be skipped. The module can be used only when it is registered in the system register. For this purpose, follow:

  1. Open the ./src/system/drivers/driver_registration.c file, and find the Modules include files section. In this section add specified chunk:
    #if (__ENABLE_MY_MODULE__)
    #       include "my_module_def.h"
    #endif
        

    If our module depends on the microcontroller architecture (e.g. stm32f1), add as follow:

    #if (__ENABLE_MY_MODULE__)
    #       ifdef ARCH_stm32f1
    #               include "stm32f1/my_module_def.h"
    #       endif
    #endif
        
  2. Next find the Modules interfaces section, and add:
    #if (__ENABLE_MY_MODULE__)
            _IMPORT_MODULE_INTERFACE(MY_MODULE);
    #endif
        

    This code imports and creates the interface for our module, that can be used by the drivers,

  3. The system from some reasons needs the registered name of the module. In this case find the _regdrv_module_name[] array, and add the specified code:
    #if (__ENABLE_MY_MODULE__)
    _MODULE_NAME(MY_MODULE),
    #endif
        
  4. Now, we need to register the driver/interface that can be used by us. For this purpose, find the _regdrv_driver_table[] array, and add:
    #if (__ENABLE_MY_MODULE__)
    _DRIVER_INTERFACE(MY_MODULE, "my_module", _MY_MODULE_MAJOR_NUMBER, _MY_MODULE_MINOR_NUMBER),
    #endif
        
  5. Save and close this file. Open the ./src/system/include/stdc/sys/ioctl.h file, and in the Include files section add line:
    #include "my_module_ioctl.h"
        

    If module is an architecture depending, then add the above line to the specified architecture section:

    #ifdef ARCH_stm32f1
    #include "stm32f1/my_module_ioctl.h"
    #endif
        
  6. Save file and close. Those are all steps required to register the module in the system.

3.4. Initializing Module

If our module is ready, then we can use it. The module can be initialized on demand in any time by invoking the driver_init() function. Mostly, the modules are initialized at system startup in the initd daemon. The module can be used only when the file node representing the driver is created. Add the specified line in the ./src/system/user/initd.c file in the run_level_0() function:

driver_init("my_module", "/dev/my_module");

As we can see, our module (driver) will be mounted in the /dev/my_module node. The access to the module can be done by the file open function. If we do not need to create the file node for the module, then we can initialize the module as follow:

driver_init("my_module", NULL);

In this case, we have no access to our device, because we have not the node that redirect us to the module. But always, we can create the node (many nodes, not only one) to previously initialized module. To do this use specified code:

int id = get_driver_ID("my_module");
if (id >= 0) {
         mknod("/dev/my_module", id);
} else {
         perror("Driver does not exist!");
}

3.5. Module Usage

Here is a simple example of usage of the implemented module. Create a new program and insert the code to it:

// ...

FILE *my_module = fopen("/dev/my_module", "r+");
if (my_module) {
        /* allocate buffer for data */
        char *data = malloc(100);

        /* write data to module */
        strcpy(data, "My module write test");
        fwrite(data, 1, strlen(data) + 1, my_module);
        fseek(my_module, 0, SEEK_SET);

        /* read data from module */
        memset(data, 0, 100);
        fread(data, 1, 50, my_module);
        printf("%s\n", data);

        /* clear buffer */
        ioctl(my_module, IOCTL_MY_MODULE_CLEAR_BUFFER);

        free(data);
        fclose(my_module);
} else {
        perror("/dev/my_module");
}

// ...

3.6. Creating Configuration Script

To configure the module from the configuration tool we need a Lua script. The script is read by the configtool that creates GUI wizard. The GUI is based on the wxWidgets framework that is ported to the the Lua interpreter (wxLua). The script configuration is stored in the xml file (./tools/configtool/config.xml). When our module is added to the xml configuration file then wizard script try find specified file, in our example: noarch-my_module.lua. To create the script for our module, follow:

  1. Go to the ./config/noarch folder, and create the my_module_flags.h file with content:
    #ifndef _MY_MODULE_FLAGS_H_
    #define _MY_MODULE_FLAGS_H_
    
    #define __MY_MODULE_BUFFER_SIZE__ 128
    
    #endif /* _MY_MODULE_FLAGS_H_ */
        

    If our module depens on the microcontroller architecture, then create the suitable file in the architecture folder, e.g: ./config/stm32f1. From this file, the configuration wizard will control the my_module,

  2. Save and close this file. Open the ./config/project/flags.h file, and add the specified line in the include noarch files section:
    #include "../noarch/my_module_flags.h"
        

    If module depends on the microcontroller architecture, add this line in the specified architecture section,

  3. Save and close this file. Now, we can connect our module configuration to the global configuration. In this purpose, open the ./src/system/drivers/my_module/my_module_cfg.h file, and modify the line:
    #define _MY_MODULE_CFG_BFR_SIZE 128
        

    to the:

    #define _MY_MODULE_CFG_BFR_SIZE __MY_MODULE_BUFFER_SIZE__
        

    Save and close this file. From this modification the configuration of our module can be controlled by the wizard,

  4. Go to the ./tools/configtool folder, and create the file: noarch-my_module.lua with content:
    module(..., package.seeall)
    
    
    --==============================================================================
    -- EXTERNAL MODULES
    --==============================================================================
    require("wx")
    require("modules/ctcore")
    
    
    --==============================================================================
    -- GLOBAL OBJECTS
    --==============================================================================
    -- module main object
    my_module = {}
    
    
    --==============================================================================
    -- LOCAL OBJECTS
    --==============================================================================
    local modified = ct:new_modify_indicator()
    local ui = {}
    local ID = {}
    
    
    --==============================================================================
    -- LOCAL FUNCTIONS
    --==============================================================================
    --------------------------------------------------------------------------------
    -- @brief  Function loads all controls from configuration scripts
    -- @param  None
    -- @return None
    --------------------------------------------------------------------------------
    local function load_configuration()
            ui.CheckBox_enable:SetValue(ct:get_module_state("my_module"))
    end
    
    
    --------------------------------------------------------------------------------
    -- @brief  Function save configuration
    -- @param  None
    -- @return None
    --------------------------------------------------------------------------------
    local function save_configuration()
            ct:enable_module("my_module", ui.CheckBox_enable:GetValue())
            modified:no()
    end
    
    
    --------------------------------------------------------------------------------
    -- @brief  Event is called when module enable checkbox is changed
    -- @param  this         event object
    -- @return None
    --------------------------------------------------------------------------------
    local function checkbox_enable_updated(this)
            modified:yes()
    end
    
    
    --==============================================================================
    -- GLOBAL FUNCTIONS
    --==============================================================================
    --------------------------------------------------------------------------------
    -- @brief  Function creates a new window
    -- @param  parent       parent window
    -- @return New window handle
    --------------------------------------------------------------------------------
    function my_module:create_window(parent)
            ui = {}
    
            ID = {}
            ID.CHECKBOX_ENABLE = wx.wxNewId()
    
            -- create new scrolled window
            ui.window = wx.wxScrolledWindow(parent, wx.wxID_ANY)
    
            -- add main sizer and module enable checkbox
            ui.FlexGridSizer1 = wx.wxFlexGridSizer(0, 1, 0, 0)
            ui.CheckBox_enable = wx.wxCheckBox(ui.window, ID.CHECKBOX_ENABLE, "Enable module", wx.wxDefaultPosition, wx.wxSize(ct.CONTROL_X_SIZE, -1))
            ui.FlexGridSizer1:Add(ui.CheckBox_enable, 1, bit.bor(wx.wxALL,wx.wxALIGN_LEFT,wx.wxALIGN_CENTER_VERTICAL), 5)
    
            -- set main sizer and scroll rate
            ui.window:SetSizer(ui.FlexGridSizer1)
            ui.window:SetScrollRate(10, 10)
    
            -- connect signals
            ui.window:Connect(ID.CHECKBOX_ENABLE, wx.wxEVT_COMMAND_CHECKBOX_CLICKED, checkbox_enable_updated)
    
            -- load configuration
            load_configuration()
            modified:no()
    
            return ui.window
    end
    
    
    --------------------------------------------------------------------------------
    -- @brief  Function returns module name
    -- @param  None
    -- @return Module name
    --------------------------------------------------------------------------------
    function my_module:get_window_name()
            return "MY_MODULE"
    end
    
    
    --------------------------------------------------------------------------------
    -- @brief  Function is called by parent when window is selected
    -- @param  None
    -- @return None
    --------------------------------------------------------------------------------
    function my_module:selected()
    end
    
    
    --------------------------------------------------------------------------------
    -- @brief  Function returns modify status
    -- @param  None
    -- @return If data is modified true is returned, otherwise false
    --------------------------------------------------------------------------------
    function my_module:is_modified()
            return modified:get_value()
    end
    
    
    --------------------------------------------------------------------------------
    -- @brief  Function save configuration
    -- @return None
    --------------------------------------------------------------------------------
    function my_module:save()
            save_configuration()
    end
    
    
    --------------------------------------------------------------------------------
    -- @brief  Function discard modified configuration
    -- @return None
    --------------------------------------------------------------------------------
    function my_module:discard()
            load_configuration()
            modified:no()
    end
    
    
    --------------------------------------------------------------------------------
    -- @brief  Function returns module handler
    -- @param  None
    -- @return Module handler
    --------------------------------------------------------------------------------
    function get_handler()
            return my_module
    end
        

Summary

The process of the implementation of module is not complicated, but there are several steps that must be done. The presented example is easy, more advanced modules can be implemented with more powerful features by using all system features, e.g. mutexes, sempahores, etc. To see more modules, please study the content of the ./src/system/drivers folder.