Forráskód Böngészése

Support LAN9662

Adds PPM/CPM hardware offload using LAN9662 RTE.
Use EVB-LAN9662 for evaluation and test.

Change-Id: I704e70c0d400084c44fd580998fb78ad5cfed247
Ola Björsne 3 éve
szülő
commit
de035b4867
51 módosított fájl, 11322 hozzáadás és 37 törlés
  1. 0 2
      CMakeLists.txt
  2. 380 0
      doc/getting_started_LAN9662.rst
  3. 1 0
      doc/index.rst
  4. 1 1
      samples/pn_dev/sampleapp_common.c
  5. 11 1
      samples/pn_dev/sampleapp_common.h
  6. 23 0
      samples/pn_dev_lan9662/CMakeLists.txt
  7. 417 0
      samples/pn_dev_lan9662/GSDML-V2.4-RT-Labs-P-Net-LAN9662-20220511.xml
  8. 68 0
      samples/pn_dev_lan9662/S90_start_profinet.sh
  9. 442 0
      samples/pn_dev_lan9662/app_data.c
  10. 121 0
      samples/pn_dev_lan9662/app_data.h
  11. 394 0
      samples/pn_dev_lan9662/app_gsdml.c
  12. 199 0
      samples/pn_dev_lan9662/app_gsdml.h
  13. 52 0
      samples/pn_dev_lan9662/app_log.c
  14. 75 0
      samples/pn_dev_lan9662/app_log.h
  15. 318 0
      samples/pn_dev_lan9662/app_shm.c
  16. 104 0
      samples/pn_dev_lan9662/app_shm.h
  17. 722 0
      samples/pn_dev_lan9662/app_utils.c
  18. 382 0
      samples/pn_dev_lan9662/app_utils.h
  19. 1235 0
      samples/pn_dev_lan9662/sampleapp_common.c
  20. 148 0
      samples/pn_dev_lan9662/sampleapp_common.h
  21. 79 0
      samples/pn_dev_lan9662/switchdev-profinet-example.sh
  22. 17 0
      samples/pn_shm_tool/CMakeLists.txt
  23. 509 0
      samples/pn_shm_tool/pn_shm_tool.c
  24. 31 0
      samples/pn_shm_tool/shm_LED_handler.sh
  25. 39 0
      samples/pn_shm_tool/shm_button_handler.sh
  26. 15 0
      samples/pn_shm_tool/shm_echo_all.sh
  27. 32 0
      samples/pn_shm_tool/shm_read_all.sh
  28. 29 0
      samples/pn_shm_tool/shm_write_all_inputs.sh
  29. 122 0
      samples/pn_shm_tool/test.sh
  30. 3 7
      src/drivers/drivers.cmake
  31. 0 24
      src/drivers/drivers_options.cmake
  32. 41 0
      src/drivers/lan9662/add_inbound_vcap_rule.sh
  33. 42 0
      src/drivers/lan9662/add_mera_lib.cmake
  34. 47 0
      src/drivers/lan9662/add_outbound_vcap_rule.sh
  35. 20 0
      src/drivers/lan9662/del_vcap_rule.sh
  36. 154 0
      src/drivers/lan9662/driver.cmake
  37. 49 0
      src/drivers/lan9662/include/driver_config.h
  38. 89 0
      src/drivers/lan9662/include/pnet_lan9662_api.h
  39. 52 0
      src/drivers/lan9662/pnet_driver_options.h.in
  40. 37 0
      src/drivers/lan9662/set_profinet_leds
  41. 473 0
      src/drivers/lan9662/src/pf_cpm_driver_lan9662.c
  42. 2836 0
      src/drivers/lan9662/src/pf_lan9662_mera.c
  43. 335 0
      src/drivers/lan9662/src/pf_lan9662_mera.h
  44. 129 0
      src/drivers/lan9662/src/pf_mera_trace.c
  45. 38 0
      src/drivers/lan9662/src/pf_mera_trace.h
  46. 328 0
      src/drivers/lan9662/src/pf_ppm_driver_lan9662.c
  47. 182 0
      src/drivers/lan9662/src/pf_rte_uio.c
  48. 59 0
      src/drivers/lan9662/src/pf_rte_uio.h
  49. 343 0
      src/drivers/lan9662/src/pf_sram_uio.c
  50. 68 0
      src/drivers/lan9662/src/pf_sram_uio.h
  51. 31 2
      src/ports/linux/sampleapp_main.c

+ 0 - 2
CMakeLists.txt

@@ -155,8 +155,6 @@ configure_file (
   ${PROFINET_BINARY_DIR}/include/pnet_version.h
   )
 
-include(${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/drivers_options.cmake)
-
 # Generate config options
 configure_file (
   options.h.in

+ 380 - 0
doc/getting_started_LAN9662.rst

@@ -0,0 +1,380 @@
+LAN9662
+=======
+
+Introduction
+------------
+Add description of LAN9662 when datasheet is available.
+
+For links to Microchip LAN9662 resources see section  :ref:`lan9662-resources`.
+
+Known limitations:
+
+- One byte write operations are not supported from
+  LAN9662 RTE to the I/O FPGA. This means the sample submodule
+  Digital Output 1x8 is not working in full rte mode.
+- Submodule default values are not applied on AR teardown
+  in full rte mode.
+
+LAN9662 Profinet Solution
+-------------------------
+
+P-Net implements support for the networking hardware offload features in the LAN9662.
+When hardware offload is enabled, Profinet cyclic data frames are handled by the LAN9662 Real
+Time Engine (RTE). During AR / connection establishment the RTE is configured by the P-Net stack and
+sending and receiving cyclic data is handed over the RTE. The P-Net stack still handles
+all non-cyclic Profinet frames.
+
+The cyclic process data is defined by the Profinet device application.
+In Profinet the device input data is sent to the controller using the Producer Protocol Machine (PPM) and
+the output data is received using the the Consumer Protocol Machine (CPM).
+For the PPM side the RTE is configured to read input data sources and build a frame
+that is periodically sent to the controller.
+For the CPM side the RTE is configured to extract output data in received
+frames and write it to configured output sinks.
+
+The RTE process data can be mapped to SRAM or a QSPI HW device.
+
+When hardware offload is enabled all process data by default is mapped to SRAM
+by P-Net and the application uses the P-Net API the same way as usual.
+
+To map the process data to a QSPI interface the application uses the P-Net API functions
+``pnet_mera_input_set_rte_config()`` for input data and
+``pnet_mera_output_set_rte_config()`` for output data.
+When a QSPI data source or sink is configured the process data
+is handled by the configured HW device and not by the application.
+The P-Net stack still keeps a copy of the process data in SRAM
+and the application can read the process data from this copy.
+
+All P-Net interactions with the LAN9662 RTE is done through the MERA library provided by Microchip.
+Detailed information about the LAN9662 and the RTE can be found in the references in section :ref:`lan9662-resources`.
+
+Table below shows how the the P-Net API for handling the process data is affected when RTE is enabled.
+
++---------------------------------+-----------+------------------------------------------------------+
+| API Function                    | RTE-SRAM  | RTE-QSPI                                             |
++=================================+===========+======================================================+
+| pnet_input_set_data_and_iops    | Supported | Not supported. Input IO data from QSPI.              |
++---------------------------------+-----------+------------------------------------------------------+
+| pnet_input_get_data_and_iops    | Supported | Supported                                            |
++---------------------------------+-----------+------------------------------------------------------+
+| net_input_get_iocs              | Supported | Supported                                            |
++---------------------------------+-----------+------------------------------------------------------+
+| pnet_output_get_data_and_iops   | Supported | Supported for reading output IO data mapped to QSPI. |
++---------------------------------+-----------+------------------------------------------------------+
+| pnet_output_set_iocs            | Supported | Supported                                            |
++---------------------------------+-----------+------------------------------------------------------+
+| pnet_mera_output_set_rte_config | Not used  | Used for configuration                               |
++---------------------------------+-----------+------------------------------------------------------+
+| pnet_mera_input_set_rte_config  | Not used  | Used for configuration                               |
++---------------------------------+-----------+------------------------------------------------------+
+
+
+Build-time configuration
+------------------------
+Support for LAN9962 features in the P-Net stack is enabled using the following CMake options:
+
+- PNET_OPTION_DRIVER_ENABLE - Enable the P-Net support for hardware drivers in general
+- PNET_OPTION_DRIVER_LAN9662 - Enable the support for the LAN9662 driver
+- PNET_OPTION_LAN9662_SHOW_RTE_INFO - Print the RTE configuration to the application log
+
+The LAN9662 sample is built with the following P-Net options::
+
+    set(HAS_PNET ON)
+    set(BUILD_TESTING OFF)
+    set(PNET_OPTION_DRIVER_ENABLE ON)
+    set(PNET_OPTION_DRIVER_LAN9662 ON)
+    set(PNET_OPTION_LAN9662_SHOW_RTE_INFO OFF)
+    set(PNET_MAX_PHYSICAL_PORTS "2" CACHE STRING "LAN9962 2 Ports" FORCE)
+    set(PNET_MAX_SLOTS "13" CACHE STRING "LAN9962 sample application" FORCE)
+    set(PNET_MAX_SUBSLOTS "4" CACHE STRING "LAN9962 2 Ports" FORCE)
+    set(LOG_LEVEL "FATAL" CACHE STRING "Enable logging" FORCE)
+
+
+Run-time configuration
+----------------------
+The build-time configuration described in previous section enables the support for the LAN9662 driver.
+The application decides if the hardware offload shall be used when p-net stack is initialized.
+The hardware offload feature is enabled using the driver_enable parameter part of
+the configuration (pnet_cfg_t) passed to P-Net during initialization.
+When set to true, LAN9662 specific CPM and PPM drivers are used in the P-Net stack and handling of cyclic data frames
+is handed off to the LAN9662 RTE during AR establishment.
+The driver_config parameter contains base IDs for various resources in the LAN9662 RTE.
+In a system where P-Net is the only feature using the RTE these don't need to be considered.
+If not, the IDs must be set not to conflict with other parts of the system.
+
+Input data is mapped to QSPI using the operation ``pnet_input_set_rte_config()``.
+Output data is mapped to QSPI using the operation ``pnet_output_set_rte_config()``.
+
+
+EVB-LAN9662
+-----------
+Both the default P-Net sample application and the LAN9662 sample application can be run on the EVB-LAN9662.
+Utility scripts for configuration of LEDs and buttons and starting the applications are provided in the ``p-net/samples/pn_dev_lan9662/`` directory.
+EVB-LAN9662 Features used by P-Net samples:
+
+- Shell is available on the EVB-LAN9662 USB connector marked ``CONSOLE``. Use 115200 baud, no flow control.
+- The io-fpga is connected to the EVB-LAN9662-Carrier USB connector marked ``FPGA SPI``.
+- Two ethernet ports are supported. Sample scripts show how to configure a network bridge.
+
+LAN9662 Sample Application
+--------------------------
+
+The application focus on the process data and its mapping to the RTE.
+The source code is is found in ``/p-net/samples/pn_dev_lan9662/``.
+The sample application builds for and runs on the EVB-LAN9662.
+
+It supports the following I/O-data:
+
+============== ======================= =========================================== ============
+[Slot,Subslot] Name                    Shared memory area                          FPGA addr [func]
+============== ======================= =========================================== ============
+[1,1]          Digital Input 1x8       /dev/shm/pnet-in-1-1-digital_input_1x8      0x100 [mem]
+[2,1]          Digital Output 1x8      /dev/shm/pnet-out-2-1-digital_output_1x8    0x104 [mem]
+[3,1]          Digital Input 1x64      /dev/shm/pnet-in-3-1-digital_input_1x64     0x108 [mem]
+[4,1]          Digital Input 2x32a     /dev/shm/pnet-in-4-1-digital_input_2x32_a   0x110 [mem]
+[5,1]          Digital Input 2x32b     /dev/shm/pnet-in-5-1-digital_input_2x32_b   0x118 [mem]
+[6,1]          Digital Input 1x800     /dev/shm/pnet-in-6-1-digital_input_1x800    0x120 [mem]
+[7,1]          Digital Output 1x64     /dev/shm/pnet-out-7-1-digital_out_1x64      0x184 [mem]
+[8,1]          Digital Output 2x32a    /dev/shm/pnet-out-8-1-digital_output_2x32_a 0x18c [mem]
+[9,1]          Digital Output 2x32b    /dev/shm/pnet-out-9-1-digital_output_2x32_b 0x194 [mem]
+[10,1]         Digital Output 1x800    /dev/shm/pnet-out-10-1-digital_output_1x800 0x19c [mem]
+[11,1]         Digital Input Port A    Not supported                               0x200 [gpios]
+[12,1]         Digital Output Port A   Not supported                               0x10  [gpios]
+============== ======================= =========================================== ============
+
+Note that the I/Os on slots 11 and 12 are available at the EVB-LAN9662 pin lists IN-A and OUT-A.
+The sample application gsdml file is available at ``/p-net/samples/pn_dev_lan9662/``.
+
+The application has three modes of operation. The mode is a runtime configuration defined by the mode (-m) argument:
+
+-m none     RTE disabled. Application process data mapped to shared memory.
+-m cpu      RTE enabled. RTE maps process data to SRAM. Application process data mapped to shared memory.
+-m full     RTE enabled. RTE maps process data to QSPI. Application process data mapped to io-fpga.
+
+**Mode none**
+
+In this mode the input/output data is mapped to shared memory.
+The shared memory can be accessed using the pn_shm_tool or by another
+application in the system. HW offload is disabled and the default data
+path of the P-Net stack is used.
+
+The shared memory is accessed using the pn_shm_tool. Run ``/usr/bin/pn_shm_tool -h`` for further details.
+
+**Mode cpu**
+
+This mode shows how to use the P-Net with LAN9662 RTE SRAM data.
+Also in this mode the input/output data is mapped to shared memory.
+The shared memory can be accessed using the pn_shm_tool or by another
+application in the system. HW offload is enabled and the cyclic data is
+is handled by the LAN9662 RTE. To a user of the application it is no
+difference to the "none"-mode. However P-Net copies application process
+data to SRAM which is mapped to the cyclic data frames handled by the RTE.
+
+The shared memory is accessed using the pn_shm_tool. Run ``/usr/bin/pn_shm_tool -h`` for further details.
+
+**Mode full**
+
+This mode shows how to use P-Net with LAN9662 RTE QSPI data.
+Shared memory is not used. The input/output data is mapped to
+the IO-FPGA on the EVB-LAN9662.
+
+The fpga is accessed using the mera-iofpga-rw tool. Run ``mera-iofpga-rw -h`` for further details.
+Note that Port A outputs and Port A inputs can be accessed without the mera-iofpga-rw tool since they are physically available on the EVB-LAN9662.
+
+Note that the mera-iofpga-rw tool is run on a host system, not on the LAN9662.
+See :ref:`lan9662-resources` for further information on the io-fpga tool.
+
+Running the LAN9662 Sample Application
+--------------------------------------
+Start the LAN992 sample application using the script ``switchdev-profinet-example.sh``.
+The log from a scenario with a PLC using input port A and output port A is shown below.
+
+log::
+
+    switchdev-profinet-example.sh
+    Starting switchdev-profinet-example
+    [   31.365807] EXT4-fs (mmcblk0p2): recovery complete
+    [   31.371838] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null). Quota mode: disabled.
+    ANA_PGID[61]                                                              = 0x0000010f -> 0x000001ff
+    net.ipv6.conf.br0.disable_ipv6 = 1
+    [   31.647312] lan966x-switch e2000000.switch eth0: PHY [e200413c.mdio-mii:01] driver [Microchip INDY Gigabit Internal] (irq=POLL)
+    [   31.658804] lan966x-switch e2000000.switch eth0: configuring for phy/gmii link mode
+    [   31.786815] 8021q: adding VLAN 0 to HW filter on device eth0
+    [   31.877381] lan966x-switch e2000000.switch eth1: PHY [e200413c.mdio-mii:02] driver [Microchip INDY Gigabit Internal] (irq=POLL)
+    [   31.888861] lan966x-switch e2000000.switch eth1: configuring for phy/gmii link mode
+    [   32.016817] 8021q: adding VLAN 0 to HW filter on device eth1
+    [   32.032693] br0: port 1(eth0) entered blocking state
+    [   32.037722] br0: port 1(eth0) entered disabled state
+    [   32.042987] device eth0 entered promiscuous mode
+    [   32.047909] br0: port 1(eth0) entered blocking state
+    [   32.052820] br0: port 1(eth0) entered forwarding state
+    [   32.068835] br0: port 2(eth1) entered blocking state
+    [   32.073767] br0: port 2(eth1) entered disabled state
+    [   32.079243] device eth1 entered promiscuous mode
+    [   32.084021] br0: port 2(eth1) entered blocking state
+    [   32.088977] br0: port 2(eth1) entered forwarding state
+    QSYS_SW_PORT_MODE[4]                                                      = 0x00005002 -> 0x00045000
+    Starting LAN9662 Profinet sample application
+    RTE mode: full
+
+    ** Starting P-Net sample application 0.2.0 **
+    Number of slots:      13 (incl slot for DAP module)
+    P-net log level:      2 (DEBUG=0, FATAL=4)
+    App log level:        0 (DEBUG=0, FATAL=4)
+    Max number of ports:  2
+    Network interfaces:   br0,eth0,eth1
+    Button1 file:
+    Button2 file:
+    Default station name: lan9662-dev
+    Management port:      br0 12:A9:2D:16:93:83
+    Physical port [1]:    eth0 12:A9:2D:16:93:81
+    Physical port [2]:    eth1 12:A9:2D:16:93:82
+    Hostname:             vcoreiii
+    IP address:           0.0.0.0
+    Netmask:              0.0.0.0
+    Gateway:              0.0.0.0
+    Storage directory:    /tmp/pn_data
+
+    Application RTE mode "full"
+    Slot [1,1] Digital Input 1x8 mapped to FPGA address 0x100
+    Slot [2,1] Digital Output 1x8 mapped to FPGA address 0x104
+    Slot [3,1] Digital Input 1x64 mapped to FPGA address 0x108
+    Slot [4,1] Digital Input 2x32 a mapped to FPGA address 0x110
+    Slot [5,1] Digital Input 2x32 b mapped to FPGA address 0x118
+    Slot [6,1] Digital Input 1x800 mapped to FPGA address 0x120
+    Slot [7,1] Digital Output 1x64 mapped to FPGA address 0x184
+    Slot [8,1] Digital Output 2x32 a mapped to FPGA address 0x18c
+    Slot [9,1] Digital Output 2x32 b mapped to FPGA address 0x194
+    Slot [10,1] Digital Output 1x800 mapped to FPGA address 0x19c
+    Slot [11,1] Digital Input Port A mapped to FPGA address 0x200
+    Slot [12,1] Digital Output Port A mapped to FPGA address 0x10
+    Profinet signal LED indication. New state: 0
+    LED 2 new state 0
+    Network script for br0:  Set IP 0.0.0.0   Netmask 0.0.0.0   Gateway 0.0.0.0   Permanent: 1   Hostname: lan9662-dev   Skip setting hostname: true
+    No valid default gateway given. Skipping setting default gateway.
+    LED 1 new state 0
+    Plug DAP module and its submodules
+    Module plug indication API 0
+    [0] Pull old module
+    [0] Plug module. Module ID: 0x1 "DAP 1"
+    Submodule plug indication API 0
+    [0,1] Pull old submodule.
+    [0,1] Plug submodule. Submodule ID: 0x1 Data Dir: NO_IO In: 0 Out: 0 "DAP Identity 1"
+    Submodule plug indication API 0
+    [0,32768] Pull old submodule.
+    [0,32768] Plug submodule. Submodule ID: 0x8000 Data Dir: NO_IO In: 0 Out: 0 "DAP Interface 1"
+    Submodule plug indication API 0
+    [0,32769] Pull old submodule.
+    [0,32769] Plug submodule. Submodule ID: 0x8001 Data Dir: NO_IO In: 0 Out: 0 "DAP Port 1"
+    Submodule plug indication API 0
+    [0,32770] Pull old submodule.
+    [0,32770] Plug submodule. Submodule ID: 0x8002 Data Dir: NO_IO In: 0 Out: 0 "DAP Port 2"
+    Waiting for PLC connect request
+
+    [   32.544399] br0: port 1(eth0) entered disabled state
+    [   32.555613] br0: port 2(eth1) entered disabled state
+    [   34.164904] lan966x-switch e2000000.switch eth1: Link is Up - 100Mbps/Full - flow control off
+    [   34.173514] IPv6: ADDRCONF(NETDEV_CHANGE): eth1: link becomes ready
+    [   34.179964] br0: port 2(eth1) entered blocking state
+    [   34.184904] br0: port 2(eth1) entered forwarding state
+    [   34.884950] lan966x-switch e2000000.switch eth0: Link is Up - 1Gbps/Full - flow control rx/tx
+    [   34.893743] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
+    [   34.900201] br0: port 1(eth0) entered blocking state
+    [   34.905155] br0: port 1(eth0) entered forwarding state
+    [   37.214719] NOHZ tick-stop error: Non-RCU local softirq work is pending, handler #08!!!
+    Network script for br0:  Set IP 192.168.0.50   Netmask 255.255.255.0   Gateway 192.168.0.50   Permanent: 0   Hostname: lan9662-dev   Skip setting hostname: true
+    [   39.225858] NOHZ tick-stop error: Non-RCU local softirq work is pending, handler #08!!!
+    [   39.235956] NOHZ tick-stop error: Non-RCU local softirq work is pending, handler #08!!!
+    Module plug indication API 0
+    [11] Pull old module
+    [11] Plug module. Module ID: 0x100b "DI Port A"
+    Submodule plug indication API 0
+    [11,1] Pull old submodule.
+    [11,1] Plug submodule. Submodule ID: 0x200b Data Dir: INPUT In: 4 Out: 0 "Digital Input Port A"
+    [11,1,"Digital Input Port A"]            Set RTE QSPI address 0x200
+    Module plug indication API 0
+    [12] Pull old module
+    [12] Plug module. Module ID: 0x100c "DO Port A"
+    Submodule plug indication API 0
+    [12,1] Pull old submodule.
+    [12,1] Plug submodule. Submodule ID: 0x200c Data Dir: OUTPUT In: 0 Out: 4 "Digital Output Port A"
+    [12,1,"Digital Output Port A"]           Set RTE QSPI address 0x10
+    PLC connect indication. AREP: 1
+    ANA_RT_VLAN_PCP[1].PCP_MASK                                                                                       = 0x00000000 -> 0x000000ff
+    ANA_RT_VLAN_PCP[1].VLAN_ID                                                                                        = 0x00000000 -> 0x00000000
+    ANA_RT_VLAN_PCP[1].VLAN_PCP_ENA                                                                                   = 0x00000000 -> 0x00000001
+    vcap add is1 10 3 s1_rt first 0 rt_vlan_idx 1 0x7 l2_mac 12:A9:2D:16:93:83 ff:ff:ff:ff:ff:ff rt_type 1 0x3 rt_frmid 32769 0xffff s1_rt rtp_id 5 fwd_ena 1 fwd_mas0
+    key field first: value: 0
+    key field rt_vlan_idx: value: 01 mask: 07
+    key field l2_mac: value: 8393162da912 mask: ffffffffffff
+    key field rt_type: value: 01[   39.549942] NOHZ tick-stop error: Non-RCU local softirq work is pending, handler #08!!!
+    mask: 03
+    key field rt_frmid: value: 00008001 mask: 0000ffff
+    act field rtp_id: val[   39.565901] NOHZ tick-stop error: Non-RCU local softirq work is pending, handler #08!!!
+    ue: 0x5
+    act field fwd_ena: value: 0x1
+    act field fwd_mask: value: 0x10
+    Event indication PNET_EVENT_STARTUP   AREP: 1
+    PLC dcontrol message. AREP: 1  Command: PRM_END
+    Event indication PNET_EVENT_PRMEND   AREP: 1
+    [0,1,"DAP Identity 1"]                   Set input data and IOPS. Size: 0 IOPS: GOOD
+    [0,32768,"DAP Interface 1"]              Set input data and IOPS. Size: 0 IOPS: GOOD
+    [0,32769,"DAP Port 1"]                   Set input data and IOPS. Size: 0 IOPS: GOOD
+    [0,32770,"DAP Port 2"]                   Set input data and IOPS. Size: 0 IOPS: GOOD
+    [11,1,"Digital Input Port A"]            Set input data and IOPS. Size: 4 IOPS: GOOD
+    [12,1,"Digital Output Port A"]           Set output IOCS: GOOD
+    vcap add is1 10 2 s1_rt first 0 l2_mac 12:A9:2D:16:93:83 ff:ff:ff:ff:ff:ff rt_vlan_idx 0 0x7 rt_frmid 32768 0xffff s1_rt rtp_id 4 rtp_subid 0 rte_inb_upd 1 fwd_e0
+    key field first: value: 0
+    key field l2_mac: value: 8393162da912 mask: ffffffffffff
+    key field rt_vlan_idx: value: 00 mask: 07
+    key field rt_frmid: value: 00008000 mask: 0000ffff
+    act field rtp_id: value: 0x4
+    act field rtp_subid: value: 0x0
+    act field rte_inb_upd: value: 0x1
+    act field fwd_ena: value: 0x1
+    act field fwd_mask: value: 0x10
+    [11,1,"Digital Input Port A"]            PLC reports Consumer Status (IOCS) GOOD
+    Application will signal that it is ready for data, for AREP 1.
+    Event indication PNET_EV[   39.883951] NOHZ tick-stop error: Non-RCU local softirq work is pending, handler #08!!!
+    ENT_APPLRDY   AREP: 1
+    Event indication PNET_EVENT_DATA   AREP: 1
+    Cyclic data transmission started
+
+    PLC ccontrol message confirmation. AREP: 1  Status codes: 0 0 0 0
+    [12,1,"Digital Output Port A"]           PLC reports Provider Status (IOPS) GOOD
+
+
+
+Building the LAN9662 Sample Application
+---------------------------------------
+Add step by step guide describing how to build the LAN9662
+sample application and which Microchip resources to download
+when that information is available.
+
+
+P-Net on LAN9962 Application Summary
+------------------------------------
+- To map process data to QSPI the application must use the operations ``pnet_output_set_rte_config()`` and ``pnet_input_set_rte_config()``
+- If process data is handled by the application and not mapped to QSPI the hardware offload can be enabled and used without any change in the application. API usage is identical except that the hardware offload is enabled during stack initialization.
+- ``p-net/samples/pn_dev_lan9662/switchdev-profinet-example.sh`` shows the required systems configurations for a 2 port Profinet device application.
+
+.. _lan9662-resources:
+
+LAN9662 Resources
+-----------------
+
+Microchip provides pre-built buildroot images for the LAN9662 which is used
+to run the Profinet sample application:
+
+- http://mscc-ent-open-source.s3-eu-west-1.amazonaws.com/index.html?prefix=public_root/
+
+To build the Profinet sample application the following bsp and toolchain are used:
+
+- http://mscc-ent-open-source.s3-eu-west-1.amazonaws.com/public_root/bsp/mscc-brsdk-arm-2021.09.tar.gz
+- http://mscc-ent-open-source.s3-eu-west-1.amazonaws.com/public_root/toolchain/mscc-toolchain-bin-2021.02-090.tar.gz
+
+Documentation and data sheets:
+
+- http://mscc-ent-open-source.s3-eu-west-1.amazonaws.com/public_root/bsp/mscc-brsdk-doc-2021.09.html
+- Add more documents when LAN9662 documentation is available.
+

+ 1 - 0
doc/index.rst

@@ -33,6 +33,7 @@ documentation.
    linuxtiming.rst
    compliancetest.rst
    yoctobuild.rst
+   getting_started_LAN9662.rst
    abbreviations.rst
    api_documentation.rst
    _copied/LICENSE.md

+ 1 - 1
samples/pn_dev/sampleapp_common.c

@@ -128,7 +128,7 @@ static bool app_is_connected_to_controller (app_data_t * app)
    return app->main_api.arep != UINT32_MAX;
 }
 
-app_data_t * app_init (const pnet_cfg_t * pnet_cfg)
+app_data_t * app_init (const pnet_cfg_t * pnet_cfg, const app_args_t * app_args)
 {
    APP_LOG_INFO ("Init P-Net stack and sample application\n");
 

+ 11 - 1
samples/pn_dev/sampleapp_common.h

@@ -39,6 +39,14 @@ extern "C" {
 #define APP_TICKS_READ_BUTTONS 10
 #define APP_TICKS_UPDATE_DATA  100
 
+/** HW Offload configuration. */
+typedef enum
+{
+   MODE_HW_OFFLOAD_NONE = 0,
+   MODE_HW_OFFLOAD_CPU,
+   MODE_HW_OFFLOAD_FULL,
+} app_mode_t;
+
 /** Command line arguments for sample application */
 typedef struct app_args
 {
@@ -53,6 +61,7 @@ typedef struct app_args
    int show;
    bool factory_reset;
    bool remove_files;
+   app_mode_t mode;
 } app_args_t;
 
 typedef enum
@@ -77,9 +86,10 @@ void app_pnet_cfg_init_default (pnet_cfg_t * pnet_cfg);
  * called.
  *
  * @param pnet_cfg               In:    P-Net configuration
+ * @param app_args               In:    Application arguments
  * @return Application handle, NULL on error
  */
-app_data_t * app_init (const pnet_cfg_t * pnet_cfg);
+app_data_t * app_init (const pnet_cfg_t * pnet_cfg, const app_args_t * app_args);
 
 /**
  * Start application main loop

+ 23 - 0
samples/pn_dev_lan9662/CMakeLists.txt

@@ -0,0 +1,23 @@
+#********************************************************************
+#        _       _         _
+#  _ __ | |_  _ | |  __ _ | |__   ___
+# | '__|| __|(_)| | / _` || '_ \ / __|
+# | |   | |_  _ | || (_| || |_) |\__ \
+# |_|    \__|(_)|_| \__,_||_.__/ |___/
+#
+# http://www.rt-labs.com
+# Copyright 2017 rt-labs AB, Sweden.
+# See LICENSE file in the project root for full license information.
+#*******************************************************************/
+
+target_include_directories(pn_lan9662
+  PRIVATE
+  ${PROFINET_SOURCE_DIR}/src
+  ${PROFINET_SOURCE_DIR}/src/drivers/lan9662
+  ${PROFINET_BINARY_DIR}/src
+  )
+
+
+target_link_libraries (pn_lan9662 PUBLIC profinet mera)
+
+install (TARGETS pn_lan9662 DESTINATION bin)

+ 417 - 0
samples/pn_dev_lan9662/GSDML-V2.4-RT-Labs-P-Net-LAN9662-20220511.xml

@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<ISO15745Profile xmlns="http://www.profibus.com/GSDML/2003/11/DeviceProfile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.profibus.com/GSDML/2003/11/DeviceProfile ..\xsd\GSDML-DeviceProfile-V2.4.xsd">
+   <!-- ProfileHeader definition as defined in ISO 15745-1. Please do not change the content. -->
+   <ProfileHeader>
+      <ProfileIdentification>PROFINET Device Profile</ProfileIdentification>
+      <ProfileRevision>1.00</ProfileRevision>
+      <ProfileName>Device Profile for PROFINET Devices</ProfileName>
+      <ProfileSource>PROFIBUS Nutzerorganisation e. V. (PNO)</ProfileSource>
+      <ProfileClassID>Device</ProfileClassID>
+      <ISO15745Reference>
+         <ISO15745Part>4</ISO15745Part>
+         <ISO15745Edition>1</ISO15745Edition>
+         <ProfileTechnology>GSDML</ProfileTechnology>
+      </ISO15745Reference>
+   </ProfileHeader>
+   <ProfileBody>
+      <DeviceIdentity VendorID="0x0493" DeviceID="0x9662">
+         <InfoText TextId="IDT_INFO_Device"/>
+         <VendorName Value="RT-Labs"/>
+      </DeviceIdentity>
+      <DeviceFunction>
+         <Family MainFamily="I/O" ProductFamily="P-Net Samples"/>
+      </DeviceFunction>
+      <ApplicationProcess>
+         <DeviceAccessPointList>
+            <DeviceAccessPointItem ID="IDD_1" PNIO_Version="V2.4" PhysicalSlots="0..12" ModuleIdentNumber="0x00000001" MinDeviceInterval="32" DNS_CompatibleName="lan9662-dev" FixedInSlots="0" ObjectUUID_LocalIndex="1" DeviceAccessSupported="false" MultipleWriteSupported="true" CheckDeviceID_Allowed="true" NameOfStationNotTransferable="false" LLDP_NoD_Supported="true" ResetToFactoryModes="1..2">
+               <ModuleInfo>
+                  <Name TextId="IDT_MODULE_NAME_DAP1"/>
+                  <InfoText TextId="IDT_INFO_DAP1"/>
+                  <VendorName Value="RT-Labs"/>
+                  <OrderNumber Value="12345 Abcdefghijk"/>
+                  <HardwareRelease Value="A1.0"/>
+                  <SoftwareRelease Value="V0.1.0"/>
+               </ModuleInfo>
+               <CertificationInfo ConformanceClass="A" ApplicationClass="" NetloadClass="I"/>
+               <IOConfigData MaxInputLength="1440" MaxOutputLength="1440"/>
+               <UseableModules>
+                  <ModuleItemRef ModuleItemTarget="IDM_1" AllowedInSlots="1"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_2" AllowedInSlots="2"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_3" AllowedInSlots="3"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_4" AllowedInSlots="4"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_5" AllowedInSlots="5"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_6" AllowedInSlots="6"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_7" AllowedInSlots="7"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_8" AllowedInSlots="8"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_9" AllowedInSlots="9"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_10" AllowedInSlots="10"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_11" AllowedInSlots="11"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_12" AllowedInSlots="12"/>
+               </UseableModules>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDS_1" SubmoduleIdentNumber="0x00000001" Writeable_IM_Records="1 2 3" MayIssueProcessAlarm="false">
+                     <IOData/>
+                     <ModuleInfo>
+                        <Name TextId="IDT_MODULE_NAME_DAP1"/>
+                        <InfoText TextId="IDT_INFO_DAP1"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+               <SystemDefinedSubmoduleList>
+                  <InterfaceSubmoduleItem ID="IDS_I" SubmoduleIdentNumber="0x00008000" SubslotNumber="32768" TextId="IDT_NAME_IS" SupportedRT_Classes="RT_CLASS_1" SupportedProtocols="LLDP" NetworkComponentDiagnosisSupported="false" PTP_BoundarySupported="true" DCP_BoundarySupported="true">
+                     <ApplicationRelations StartupMode="Advanced">
+                        <TimingProperties SendClock="32" ReductionRatio="1 2 4 8 16 32 64 128 256 512"/>
+                     </ApplicationRelations>
+                  </InterfaceSubmoduleItem>
+                  <PortSubmoduleItem ID="IDS_P1" SubmoduleIdentNumber="0x00008001" SubslotNumber="32769" TextId="IDT_NAME_PS1" MaxPortRxDelay="350" MaxPortTxDelay="160">
+                     <MAUTypeList>
+                        <MAUTypeItem Value="30"/>
+                        <MAUTypeItem Value="16"/>
+                        <MAUTypeItem Value="5"/>
+                     </MAUTypeList>
+                  </PortSubmoduleItem>
+                  <PortSubmoduleItem ID="IDS_P2" SubmoduleIdentNumber="0x00008002" SubslotNumber="32770" TextId="IDT_NAME_PS2" MaxPortRxDelay="350" MaxPortTxDelay="160">
+                     <MAUTypeList>
+                        <MAUTypeItem Value="30"/>
+                        <MAUTypeItem Value="16"/>
+                        <MAUTypeItem Value="5"/>
+                     </MAUTypeList>
+                  </PortSubmoduleItem>
+               </SystemDefinedSubmoduleList>
+            </DeviceAccessPointItem>
+         </DeviceAccessPointList>
+         <ModuleList>
+            <ModuleItem ID="IDM_1" ModuleIdentNumber="0x00001001">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_1"/>
+                  <InfoText TextId="TOK_InfoText_Module_1"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_1" SubmoduleIdentNumber="0x00002001" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Input Consistency="All items consistency">
+                           <DataItem DataType="Unsigned8" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Input>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_1"/>
+                        <InfoText TextId="TOK_InfoText_Module_1"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_2" ModuleIdentNumber="0x00001002">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_2"/>
+                  <InfoText TextId="TOK_InfoText_Module_2"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_2" SubmoduleIdentNumber="0x00002002" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Output Consistency="All items consistency">
+                           <DataItem DataType="Unsigned8" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Output>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_2"/>
+                        <InfoText TextId="TOK_InfoText_Module_2"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_3" ModuleIdentNumber="0x00001003">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_3"/>
+                  <InfoText TextId="TOK_InfoText_Module_3"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_3" SubmoduleIdentNumber="0x00002003" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Input Consistency="All items consistency">
+                           <DataItem DataType="Unsigned64" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Input>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_3"/>
+                        <InfoText TextId="TOK_InfoText_Module_3"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_4" ModuleIdentNumber="0x00001004">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_4"/>
+                  <InfoText TextId="TOK_InfoText_Module_4"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_4" SubmoduleIdentNumber="0x00002004" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Input Consistency="All items consistency">
+                           <DataItem DataType="Unsigned32" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                           <DataItem DataType="Unsigned32" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Input>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_4"/>
+                        <InfoText TextId="TOK_InfoText_Module_4"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_5" ModuleIdentNumber="0x00001005">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_5"/>
+                  <InfoText TextId="TOK_InfoText_Module_5"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_5" SubmoduleIdentNumber="0x00002005" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Input Consistency="All items consistency">
+                           <DataItem DataType="Unsigned32" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                           <DataItem DataType="Unsigned32" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Input>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_5"/>
+                        <InfoText TextId="TOK_InfoText_Module_5"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_6" ModuleIdentNumber="0x00001006">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_6"/>
+                  <InfoText TextId="TOK_InfoText_Module_6"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_6" SubmoduleIdentNumber="0x00002006" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Input Consistency="All items consistency">
+                           <DataItem DataType="OctetString" Length="100" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Input>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_6"/>
+                        <InfoText TextId="TOK_InfoText_Module_6"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_7" ModuleIdentNumber="0x00001007">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_7"/>
+                  <InfoText TextId="TOK_InfoText_Module_7"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_7" SubmoduleIdentNumber="0x00002007" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Output Consistency="All items consistency">
+                           <DataItem DataType="Unsigned64" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Output>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_7"/>
+                        <InfoText TextId="TOK_InfoText_Module_7"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_8" ModuleIdentNumber="0x00001008">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_8"/>
+                  <InfoText TextId="TOK_InfoText_Module_8"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_8" SubmoduleIdentNumber="0x00002008" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Output Consistency="All items consistency">
+                           <DataItem DataType="Unsigned32" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                           <DataItem DataType="Unsigned32" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Output>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_8"/>
+                        <InfoText TextId="TOK_InfoText_Module_8"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_9" ModuleIdentNumber="0x00001009">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_9"/>
+                  <InfoText TextId="TOK_InfoText_Module_9"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_9" SubmoduleIdentNumber="0x00002009" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Output Consistency="All items consistency">
+                           <DataItem DataType="Unsigned32" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                           <DataItem DataType="Unsigned32" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Output>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_9"/>
+                        <InfoText TextId="TOK_InfoText_Module_9"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_10" ModuleIdentNumber="0x0000100A">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_10"/>
+                  <InfoText TextId="TOK_InfoText_Module_10"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_10" SubmoduleIdentNumber="0x0000200A" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Output Consistency="All items consistency">
+                           <DataItem DataType="OctetString" Length="100" TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Output>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_10"/>
+                        <InfoText TextId="TOK_InfoText_Module_10"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+            <ModuleItem ID="IDM_11" ModuleIdentNumber="0x0000100B">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_11"/>
+                  <InfoText TextId="TOK_InfoText_Module_11"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_11" SubmoduleIdentNumber="0x0000200B" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Input Consistency="All items consistency">
+                           <DataItem DataType="Unsigned32"  TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Input>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_11"/>
+                        <InfoText TextId="TOK_InfoText_Module_11"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+             <ModuleItem ID="IDM_12" ModuleIdentNumber="0x0000100C">
+               <ModuleInfo>
+                  <Name TextId="TOK_Name_Module_12"/>
+                  <InfoText TextId="TOK_InfoText_Module_12"/>
+                  <HardwareRelease Value="1.0"/>
+                  <SoftwareRelease Value="1.0"/>
+               </ModuleInfo>
+               <VirtualSubmoduleList>
+                  <VirtualSubmoduleItem ID="IDSM_12" SubmoduleIdentNumber="0x0000200C" MayIssueProcessAlarm="false">
+                     <IOData>
+                        <Output Consistency="All items consistency">
+                           <DataItem DataType="Unsigned32"  TextId="TOK_Dummy" UseAsBits="false">
+                           </DataItem>
+                        </Output>
+                     </IOData>
+                     <ModuleInfo>
+                        <Name TextId="TOK_Name_Module_12"/>
+                        <InfoText TextId="TOK_InfoText_Module_12"/>
+                     </ModuleInfo>
+                  </VirtualSubmoduleItem>
+               </VirtualSubmoduleList>
+            </ModuleItem>
+         </ModuleList>
+         <LogBookEntryList>
+            <LogBookEntryItem Status="2130510">
+               <!--Custom log book entry for sample application-->
+               <!--Error code 0x20  Error decode 0x82  Error code 1 0x4E-->
+               <ErrorCode2Value>
+                  <Name TextId="IDT_CUSTOM_LOGBOOK_1"/>
+               </ErrorCode2Value>
+            </LogBookEntryItem>
+         </LogBookEntryList>
+         <GraphicsList>
+            <GraphicItem ID="RT-LabsStackImage" GraphicFile="GSDML-RT-LABS-STACK" />
+         </GraphicsList>
+         <ExternalTextList>
+            <PrimaryLanguage>
+               <Text TextId="IDT_INFO_Device" Value="https://github.com/rtlabs-com/p-net"/>
+               <Text TextId="IDT_MODULE_NAME_DAP1" Value="P-Net LAN9662 Sample"/>
+               <Text TextId="IDT_INFO_DAP1" Value="Profinet LAN9662 sample https://github.com/rtlabs-com/p-net"/>
+               <Text TextId="IDT_CUSTOM_DIAG_1" Value="Custom diagnosis in USI format"/>
+               <Text TextId="IDT_CUSTOM_DIAG_1_VALUE" Value="Custom diagnosis value"/>
+               <Text TextId="IDT_CUSTOM_LOGBOOK_1" Value="Custom Logbook entry"/>
+
+               <Text TextId="IDT_NAME_IS" Value="X1"/>
+               <Text TextId="IDT_NAME_PS1" Value="X1 P1"/>
+               <Text TextId="IDT_NAME_PS2" Value="X1 P2"/>
+
+               <!--module names-->
+               <Text TextId="TOK_Name_Module_1" Value="DI 1x8"/>
+               <Text TextId="TOK_Name_Module_2" Value="DO 1x8"/>
+               <Text TextId="TOK_Name_Module_3" Value="DI 1x64"/>
+               <Text TextId="TOK_Name_Module_4" Value="DI 2x32a"/>
+               <Text TextId="TOK_Name_Module_5" Value="DI 2x32b"/>
+               <Text TextId="TOK_Name_Module_6" Value="DI 1x800"/>
+               <Text TextId="TOK_Name_Module_7" Value="DO 1x64"/>
+               <Text TextId="TOK_Name_Module_8" Value="DO 2x32a"/>
+               <Text TextId="TOK_Name_Module_9" Value="DO 2x32b"/>
+               <Text TextId="TOK_Name_Module_10" Value="DO 1x800"/>
+               <Text TextId="TOK_Name_Module_11" Value="DI Port A"/>
+               <Text TextId="TOK_Name_Module_12" Value="DO Port A"/>
+
+               <!--module info -->
+               <Text TextId="TOK_InfoText_Module_1" Value="Digital In 1x8"/>
+               <Text TextId="TOK_InfoText_Module_2" Value="Digital Out 1x8"/>
+               <Text TextId="TOK_InfoText_Module_3" Value="Digital In 1x64"/>
+               <Text TextId="TOK_InfoText_Module_4" Value="Digital In 2x32"/>
+               <Text TextId="TOK_InfoText_Module_5" Value="Digital In 2x32"/>
+               <Text TextId="TOK_InfoText_Module_6" Value="Digital In 1x800"/>
+               <Text TextId="TOK_InfoText_Module_7" Value="Digital Out 1x64"/>
+               <Text TextId="TOK_InfoText_Module_8" Value="Digital Out 2x32"/>
+               <Text TextId="TOK_InfoText_Module_9" Value="Digital Out 2x32"/>
+               <Text TextId="TOK_InfoText_Module_10" Value="Digital Out 1x800"/>
+               <Text TextId="TOK_InfoText_Module_11" Value="Digital In Port A"/>
+               <Text TextId="TOK_InfoText_Module_12" Value="Digital Out Port A"/>
+
+               <!--dataitem name-->
+               <Text TextId="TOK_Dummy" Value="Dummy"/>
+
+            </PrimaryLanguage>
+         </ExternalTextList>
+      </ApplicationProcess>
+   </ProfileBody>
+</ISO15745Profile>

+ 68 - 0
samples/pn_dev_lan9662/S90_start_profinet.sh

@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# Initialization script for automatic
+# start of the pn_lan9662 sample application.
+# Copy this script to overlay/etc/init.d/
+# to start the application automatically.
+#
+
+start() {
+    # Assume a valid partion on /dev/mmcblk0p2
+    mkdir /tmp/pn_data
+    mount -o sync /dev/mmcblk0p2 /tmp/pn_data
+
+    echo "Configure network settings for pn_lan9662 profinet sample application"
+    # Create bridge
+    ip link add name br0 type bridge
+    symreg ANA_PGID[61] 0x1ff
+    ip link set br0 address 12:A9:2D:16:93:89
+    ip link set br0 up
+    sysctl -w net.ipv6.conf.br0.disable_ipv6=1
+
+    ip link set eth0 master br0
+    ip link set eth1 master br0
+
+    ip link set eth0 up
+    ip link set eth1 up
+
+    # Enable forwarding to chip port 4 (RTE)
+    symreg qsys_sw_port_mode[4] 0x45000
+
+    # Start Profinet application
+    # Supported modes: none, cpu, full
+    # Read mode from file
+    mode="full"
+    if test -f /tmp/pn_data/mode.txt; then
+        mode=$(cat /tmp/pn_data/mode.txt)
+    fi
+
+    echo "Starting LAN9662 Profinet sample application"
+    echo "RTE mode: $mode"
+
+    /usr/bin/pn_lan9662 -m $mode -vvvv -p /tmp/pn_data &
+
+    # Enable for interleaved ART-Tester logs
+    #tcpdump -i br0 udp port 514 -v -a &
+}
+
+stop() {
+    printf "Todo - Stop Profinet application"
+}
+
+case "$1" in
+  start)
+        start
+        ;;
+  stop)
+        stop
+        ;;
+  restart|reload)
+        stop
+        start
+        ;;
+  *)
+        echo "Usage: $0 {start|stop|restart}"
+        exit 1
+esac
+
+exit $?

+ 442 - 0
samples/pn_dev_lan9662/app_data.c

@@ -0,0 +1,442 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#include "app_data.h"
+#include "app_shm.h"
+#include "app_utils.h"
+#include "app_gsdml.h"
+#include "app_log.h"
+#include "sampleapp_common.h"
+#include "osal.h"
+#include "pnal.h"
+#include <pnet_api.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * IO FPGA address mapping.
+ */
+#define APP_DATA_FPGA_START_ADDRESS 0x100
+#define ALIGN32(x)                  ((((x)) + 3) & ~3)
+
+#define APP_FPGA_ADDRESS_DIGITAL_IN_1x8 (APP_DATA_FPGA_START_ADDRESS)
+#define APP_FPGA_ADDRESS_DIGITAL_OUT_1x8                                       \
+   (APP_FPGA_ADDRESS_DIGITAL_IN_1x8 + ALIGN32 (APP_GSDML_SIZE_DIGITAL_IN_1x8))
+#define APP_FPGA_ADDRESS_DIGITAL_IN_1x64                                       \
+   (APP_FPGA_ADDRESS_DIGITAL_OUT_1x8 + ALIGN32 (APP_GSDML_SIZE_DIGITAL_OUT_1x8))
+#define APP_FPGA_ADDRESS_DIGITAL_IN_2x32a                                      \
+   (APP_FPGA_ADDRESS_DIGITAL_IN_1x64 + ALIGN32 (APP_GSDML_SIZE_DIGITAL_IN_1x64))
+#define APP_FPGA_ADDRESS_DIGITAL_IN_2x32b                                      \
+   (APP_FPGA_ADDRESS_DIGITAL_IN_2x32a +                                        \
+    ALIGN32 (APP_GSDML_SIZE_DIGITAL_IN_2x32a))
+#define APP_FPGA_ADDRESS_DIGITAL_IN_1x800                                      \
+   (APP_FPGA_ADDRESS_DIGITAL_IN_2x32b +                                        \
+    ALIGN32 (APP_GSDML_SIZE_DIGITAL_IN_2x32b))
+#define APP_FPGA_ADDRESS_DIGITAL_OUT_1x64                                      \
+   (APP_FPGA_ADDRESS_DIGITAL_IN_1x800 +                                        \
+    ALIGN32 (APP_GSDML_SIZE_DIGITAL_IN_1x800))
+#define APP_FPGA_ADDRESS_DIGITAL_OUT_2x32a                                     \
+   (APP_FPGA_ADDRESS_DIGITAL_OUT_1x64 +                                        \
+    ALIGN32 (APP_GSDML_SIZE_DIGITAL_OUT_1x64))
+#define APP_FPGA_ADDRESS_DIGITAL_OUT_2x32b                                     \
+   (APP_FPGA_ADDRESS_DIGITAL_OUT_2x32a +                                       \
+    ALIGN32 (APP_GSDML_SIZE_DIGITAL_OUT_2x32a))
+#define APP_FPGA_ADDRESS_DIGITAL_OUT_1x800                                     \
+   (APP_FPGA_ADDRESS_DIGITAL_OUT_2x32b +                                       \
+    ALIGN32 (APP_GSDML_SIZE_DIGITAL_OUT_2x32b))
+
+#define APP_FPGA_ADDRESS_DIGITAL_IN_PORT_A  0x200
+#define APP_FPGA_ADDRESS_DIGITAL_OUT_PORT_A 0x10
+
+typedef struct submodule_fpga_config
+{
+   uint16_t address;
+   const uint8_t * default_data;
+} submodule_fpga_config_t;
+
+typedef struct submodule
+{
+   const app_gsdml_submodule_t * info;
+
+   /* shm -> pnet*/
+   app_shm_t * indata_shm;
+   uint8_t * in_data;
+
+   /* pnet -> shm */
+   app_shm_t * outdata_shm;
+   uint8_t * out_data;
+
+   /* FPGA address for full offload mode */
+   const submodule_fpga_config_t * fpga_config;
+
+} submodule_t;
+
+typedef struct fpga_map
+{
+   uint32_t submodule_id;
+   submodule_fpga_config_t fpga_config;
+} fpga_map_t;
+
+static const uint8_t app_data_default[APP_GSDML_MAX_SUBMODULE_DATA_SIZE] = {0};
+
+/**
+ * Configuration of FPGA addresses for the different
+ * submodules when running full offload.
+ */
+static const fpga_map_t fpga[] = {
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x8,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_IN_1x8,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x8,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_OUT_1x8,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x64,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_IN_1x64,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_2x32a,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_IN_2x32a,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_2x32b,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_IN_2x32b,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x800,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_IN_1x800,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x64,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_OUT_1x64,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_2x32a,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_OUT_2x32a,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_2x32b,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_OUT_2x32b,
+        .default_data = app_data_default}},
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x800,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_OUT_1x800,
+        .default_data = app_data_default}},
+
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_PORT_A,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_IN_PORT_A,
+        .default_data = app_data_default}},
+
+   {.submodule_id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_PORT_A,
+    .fpga_config =
+       {.address = APP_FPGA_ADDRESS_DIGITAL_OUT_PORT_A,
+        .default_data = app_data_default}},
+};
+
+submodule_t submodules[APP_GSDML_MAX_SUBMODULES] = {0};
+
+/**
+ * Get a submodule using its submodule id
+ * @param id            In:  Submodule id
+ * @return Reference to submodule. NULL if id is not found.
+ **/
+static submodule_t * get_submodule_by_id (uint32_t id)
+{
+   uint16_t i;
+
+   for (i = 0; i < NELEMENTS (submodules); i++)
+   {
+      if (submodules[i].info != NULL && submodules[i].info->id == id)
+      {
+         return &submodules[i];
+      }
+   }
+
+   return NULL;
+}
+
+/**
+ * Get submodule fpga config using its id.
+ * @param id            In:  Submodule id
+ * @return Reference to fgpa mapping. NULL if id is not found.
+ **/
+static const submodule_fpga_config_t * get_fpga_config_by_id (uint32_t id)
+{
+   uint32_t i = 0;
+
+   while (i < NELEMENTS (fpga))
+   {
+      if (fpga[i].submodule_id == id)
+      {
+         return &fpga[i].fpga_config;
+      }
+      i++;
+   }
+   return NULL;
+}
+
+int app_data_get_fpga_info_by_id (
+   uint32_t id,
+   uint16_t * address,
+   const uint8_t ** default_data)
+{
+   const submodule_fpga_config_t * config = get_fpga_config_by_id (id);
+
+   if (config != NULL)
+   {
+      *address = config->address;
+      *default_data = config->default_data;
+      return 0;
+   }
+   return -1;
+}
+
+/**
+ * Allocate a submodule from submodules array.
+ * The returned submodule is uninitialized.
+ * @return Reference to submodule. NULL if allocation fails.
+ **/
+static submodule_t * alloc_submodule (void)
+{
+   uint32_t i = 0;
+
+   while (i < NELEMENTS (submodules))
+   {
+      if (submodules[i].info == NULL)
+      {
+         return &submodules[i];
+      }
+      i++;
+   }
+   return NULL;
+}
+
+/**
+ * Initialize submodule
+ * Set module and submodule configurations.
+ * Configure shared memory regions.
+ * Allocate buffers.
+ * Will generate assert on failure.
+ * @param module_cfg       In:  Module configuration
+ * @param submodule_cfg    In:  Submodule configuration
+ * @param app_mode         In:  Application mode
+ **/
+static void app_data_init_submodule (
+   const app_gsdml_module_t * module_cfg,
+   const app_gsdml_submodule_t * submodule_cfg,
+   app_mode_t app_mode)
+{
+   submodule_t * submodule;
+
+   CC_ASSERT (module_cfg != NULL);
+   CC_ASSERT (submodule_cfg != NULL);
+
+   submodule = alloc_submodule();
+   CC_ASSERT (submodule != NULL);
+
+   submodule->info = submodule_cfg;
+   if (app_mode == MODE_HW_OFFLOAD_FULL)
+   {
+      submodule->fpga_config = get_fpga_config_by_id (submodule_cfg->id);
+      CC_ASSERT (submodule->fpga_config != NULL);
+   }
+   else
+   {
+      submodule->fpga_config = NULL;
+   }
+
+   if (submodule->info->insize > 0)
+   {
+      submodule->in_data = malloc (submodule->info->insize);
+      CC_ASSERT (submodule->in_data != NULL);
+
+      if (app_mode != MODE_HW_OFFLOAD_FULL)
+      {
+         submodule->indata_shm = app_shm_create_input (
+            submodule->info->name,
+            module_cfg->fixed_slot,
+            submodule_cfg->fixed_subslot,
+            submodule->info->insize);
+         CC_ASSERT (submodule->indata_shm != NULL);
+      }
+      else
+      {
+         APP_LOG_INFO (
+            "[%u,%u,\"%s\"] mapped to FPGA address 0x%x\n",
+            module_cfg->fixed_slot,
+            submodule_cfg->fixed_subslot,
+            submodule->info->name,
+            submodule->fpga_config->address);
+      }
+   }
+
+   if (submodule->info->outsize > 0)
+   {
+      submodule->out_data = malloc (submodule->info->outsize);
+      CC_ASSERT (submodule->out_data != NULL);
+
+      if (app_mode != MODE_HW_OFFLOAD_FULL)
+      {
+         submodule->outdata_shm = app_shm_create_output (
+            submodule->info->name,
+            module_cfg->fixed_slot,
+            submodule_cfg->fixed_subslot,
+            submodule->info->outsize);
+         CC_ASSERT (submodule->outdata_shm != NULL);
+      }
+      else
+      {
+         APP_LOG_INFO (
+            "[%u,%u,\"%s\"] mapped to FPGA address 0x%x\n",
+            module_cfg->fixed_slot,
+            submodule_cfg->fixed_subslot,
+            submodule->info->name,
+            submodule->fpga_config->address);
+      }
+   }
+}
+
+int app_data_init (app_mode_t app_mode)
+{
+   uint16_t i, j;
+   const app_gsdml_module_t * module_cfg;
+   const app_gsdml_submodule_t * submodule_cfg;
+   uint32_t modules_array_len;
+   const app_gsdml_module_t ** app_gsdml_modules;
+
+   app_gsdml_modules = app_gsdml_get_modules (&modules_array_len);
+
+   for (i = 0; i < modules_array_len; i++)
+   {
+      module_cfg = app_gsdml_modules[i];
+
+      j = 0;
+      while (module_cfg->submodules[j] != 0)
+      {
+         submodule_cfg =
+            app_gsdml_get_submodule_cfg (module_cfg->submodules[j]);
+         app_data_init_submodule (module_cfg, submodule_cfg, app_mode);
+         j++;
+      }
+   }
+   return 0;
+}
+
+uint8_t * app_data_get_input_data (
+   uint32_t submodule_id,
+   uint16_t * size,
+   uint8_t * iops)
+{
+   int error = -1;
+   submodule_t * submodule;
+
+   if (size == NULL || iops == NULL)
+   {
+      return NULL;
+   }
+
+   submodule = get_submodule_by_id (submodule_id);
+
+   if (submodule == NULL)
+   {
+      /* Handle unsupported submodules part of process data */
+      *iops = PNET_IOXS_BAD;
+      return NULL;
+   }
+
+   *size = submodule->info->insize;
+   if (submodule->fpga_config == NULL)
+   {
+      error = app_shm_read (submodule->indata_shm, submodule->in_data, *size);
+      CC_ASSERT (error == 0);
+   }
+   else
+   {
+      /* Input is mapped to FPGA. Data is not available to
+       * application. Use default value set status.
+       * P-Net needs an initial value during initialization.
+       */
+      memcpy (submodule->in_data, submodule->fpga_config->default_data, *size);
+   }
+   *iops = PNET_IOXS_GOOD;
+
+   return submodule->in_data;
+}
+
+uint8_t * app_data_get_output_data_buffer (
+   uint32_t submodule_id,
+   uint16_t * size)
+{
+   submodule_t * submodule = get_submodule_by_id (submodule_id);
+
+   CC_ASSERT (size != NULL);
+   if (submodule == NULL)
+   {
+      *size = 0;
+      return NULL;
+   }
+
+   *size = submodule->info->outsize;
+   return submodule->out_data;
+}
+
+int app_data_set_output_data (
+   uint32_t submodule_id,
+   uint8_t * data,
+   uint16_t size)
+{
+   int error = 0;
+   submodule_t * submodule = get_submodule_by_id (submodule_id);
+
+   if (data == NULL || submodule == NULL || size == 0)
+   {
+      return -1;
+   }
+
+   if (submodule->fpga_config == NULL)
+   {
+      /* Only write shared memory if output is not mapped
+       * to FPGA.
+       */
+      error = app_shm_write (submodule->outdata_shm, data, size);
+   }
+
+   return error;
+}
+
+int app_data_set_default_outputs (void)
+{
+   uint16_t i;
+
+   for (i = 0; i < NELEMENTS (submodules); i++)
+   {
+      if (submodules[i].outdata_shm != NULL)
+      {
+         if (submodules[i].fpga_config == NULL)
+         {
+            /* Only write shared memory if output is not mapped
+             * to FPGA.
+             */
+            app_shm_reset (submodules[i].outdata_shm);
+         }
+      }
+   }
+   return 0;
+}

+ 121 - 0
samples/pn_dev_lan9662/app_data.h

@@ -0,0 +1,121 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef APP_DATA_H
+#define APP_DATA_H
+
+#include "sampleapp_common.h"
+
+/**
+ * @file
+ * @brief Sample application data interface
+ *
+ * Functions for:
+ * - Getting input data
+ * - Setting ouput data
+ * - Setting default output state
+ *
+ * In this LAN9662 sample application the submodule data
+ * is mapped to shared memory found in /dev/shm on Linux.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Initialize data submodules.
+ * During the initialization the shared memory areas
+ * supported by the application are created and the
+ * mapping between the shared memory and the submodules
+ * is configured.
+ * @param app_mode  In:  Application mode
+ * @return 0 on success, -1 on error
+ */
+int app_data_init (app_mode_t app_mode);
+
+/**
+ * Get PNIO input data using submodule id.
+ * When this function is called the mapped shared
+ * memory is copied to the submodule input buffer.
+ * A reference to the the input buffer is returned.
+ * @param submodule_id  In:  Submodule id
+ * @param size          Out: Size of pnio data
+ * @param iops          Out: Provider status. If for example
+ *                           a sensor is failing or a short
+ *                           circuit detected on digital
+ *                           input this shall be set to BAD.
+ *                           Set to BAD for unsupported submodules
+ *                           or data can not be provided.
+ * @return Reference to PNIO data, NULL on error
+ */
+uint8_t * app_data_get_input_data (
+   uint32_t submodule_id,
+   uint16_t * size,
+   uint8_t * iops);
+
+/**
+ * Get FPGA data start address for submodule data
+ * @param id            In:  Submodule id
+ * @param address       Out: FPGA start address
+ * @param default_data  Out: Submodule default data
+ * @return 0 on success, -1 on error
+ */
+int app_data_get_fpga_info_by_id (
+   uint32_t id,
+   uint16_t * address,
+   const uint8_t ** default_data);
+
+/**
+ * Get PNIO output data buffer using submodule id.
+ * @param submodule_id  In:  Submodule id
+ * @param size          Out: Size of pnio data. If submodule
+ *                           is not supported size is set to 0.
+ * @return Reference to PNIO data, NULL on error
+ */
+uint8_t * app_data_get_output_data_buffer (
+   uint32_t submodule_id,
+   uint16_t * size);
+
+/**
+ * Set PNIO output data using module id.
+ * When this operation is called the data is
+ * written the mapped shared memory.
+ * @param submodule_id  In:  Submodule id
+ * @param data          In:  Reference to output data
+ * @param size          In:  Length of output data
+ * @return 0 on success, -1 on error
+ */
+int app_data_set_output_data (
+   uint32_t submodule_id,
+   uint8_t * data,
+   uint16_t size);
+
+/**
+ * Set default values for all outputs.
+ * When this operation is called the mapped
+ * shared memory is set to 0.
+ * @return 0 on success, -1 on error
+ */
+int app_data_set_default_outputs (void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_DATA_H */

+ 394 - 0
samples/pn_dev_lan9662/app_gsdml.c

@@ -0,0 +1,394 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+/**
+ * @file
+ * @brief Device properties defined by the GSDML device definition
+ *
+ * Functions for getting module, submodule and parameter
+ * configurations using their IDs.
+ *
+ * Important:
+ * Any change in this file may require an update of the GSDML file.
+ * Note that when the GSDML file is updated it has to be reloaded
+ * in your Profinet engineering tool. PLC applications may be affected.
+ *
+ * Design requires unique submodule IDs.
+ */
+
+#include "sampleapp_common.h"
+#include "app_utils.h"
+#include "app_gsdml.h"
+#include "app_log.h"
+#include "osal.h"
+#include "pnal.h"
+#include <pnet_api.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/************************* Modules ***************************/
+
+static const app_gsdml_module_t dap_1 = {
+   .id = PNET_MOD_DAP_IDENT,
+   .name = "DAP 1",
+   .fixed_slot = 0,
+   .submodules = {
+      PNET_SUBMOD_DAP_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_PORT_1_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_PORT_2_IDENT,
+      0}};
+
+static const app_gsdml_module_t module_di_1x8 = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_IN_1x8,
+   .name = "DI 1x8",
+   .fixed_slot = 1,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x8, 0},
+};
+
+static const app_gsdml_module_t module_do_1x8 = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_OUT_1x8,
+   .name = "DO 1x8",
+   .fixed_slot = 2,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x8, 0}};
+
+static const app_gsdml_module_t module_di_1x64 = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_IN_1x64,
+   .name = "DI 1x64",
+   .fixed_slot = 3,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x64, 0},
+};
+
+static const app_gsdml_module_t module_di_2x32a = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_IN_2x32a,
+   .name = "DI 2x32a",
+   .fixed_slot = 4,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_IN_2x32a, 0},
+};
+
+static const app_gsdml_module_t module_di_2x32b = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_IN_2x32b,
+   .name = "DI 2x32b",
+   .fixed_slot = 5,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_IN_2x32b, 0},
+};
+
+static const app_gsdml_module_t module_di_1x800 = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_IN_1x800,
+   .name = "DI 1x800",
+   .fixed_slot = 6,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x800, 0},
+};
+
+static const app_gsdml_module_t module_do_1x64 = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_OUT_1x64,
+   .name = "DO 1x64",
+   .fixed_slot = 7,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x64, 0},
+};
+
+static const app_gsdml_module_t module_do_2x32a = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_OUT_2x32a,
+   .name = "DO 2x32a",
+   .fixed_slot = 8,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_OUT_2x32a, 0},
+};
+
+static const app_gsdml_module_t module_do_2x32b = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_OUT_2x32b,
+   .name = "DO 2x32b",
+   .fixed_slot = 9,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_OUT_2x32b, 0},
+};
+
+static const app_gsdml_module_t module_do_1x800 = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_OUT_1x800,
+   .name = "DO 1x800",
+   .fixed_slot = 10,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x800, 0},
+};
+
+static const app_gsdml_module_t module_di_port_a = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_IN_PORT_A,
+   .name = "DI Port A",
+   .fixed_slot = 11,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_IN_PORT_A, 0},
+};
+
+static const app_gsdml_module_t module_do_port_a = {
+   .id = APP_GSDML_MOD_ID_DIGITAL_OUT_PORT_A,
+   .name = "DO Port A",
+   .fixed_slot = 12,
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_OUT_PORT_A, 0},
+};
+/************************* Submodules ***************************/
+
+static const app_gsdml_submodule_t dap_identity_1 = {
+   .name = "DAP Identity 1",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_IDENT,
+   .fixed_subslot = PNET_SUBSLOT_DAP_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_interface_1 = {
+   .name = "DAP Interface 1",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_IDENT,
+   .fixed_subslot = PNET_SUBSLOT_DAP_INTERFACE_1_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_port_1 = {
+   .name = "DAP Port 1",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_PORT_1_IDENT,
+   .fixed_subslot = PNET_SUBSLOT_DAP_INTERFACE_1_PORT_1_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_port_2 = {
+   .name = "DAP Port 2",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_PORT_2_IDENT,
+   .fixed_subslot = PNET_SUBSLOT_DAP_INTERFACE_1_PORT_2_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_port_3 = {
+   .name = "DAP Port 3",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_PORT_3_IDENT,
+   .fixed_subslot = PNET_SUBSLOT_DAP_INTERFACE_1_PORT_3_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_port_4 = {
+   .name = "DAP Port 4",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_PORT_4_IDENT,
+   .fixed_subslot = PNET_SUBSLOT_DAP_INTERFACE_1_PORT_4_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_di_1x8 = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x8,
+   .fixed_subslot = 1,
+   .name = "Digital Input 1x8",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_INPUT,
+   .insize = APP_GSDML_SIZE_DIGITAL_IN_1x8,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_do_1x8 = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x8,
+   .fixed_subslot = 1,
+   .name = "Digital Output 1x8",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_OUTPUT,
+   .insize = 0,
+   .outsize = APP_GSDML_SIZE_DIGITAL_OUT_1x8,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_di_1x64 = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x64,
+   .fixed_subslot = 1,
+   .name = "Digital Input 1x64",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_INPUT,
+   .insize = APP_GSDML_SIZE_DIGITAL_IN_1x64,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_di_2x32a = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_2x32a,
+   .fixed_subslot = 1,
+   .name = "Digital Input 2x32 a",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_INPUT,
+   .insize = 8,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_di_2x32b = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_2x32b,
+   .fixed_subslot = 1,
+   .name = "Digital Input 2x32 b",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_INPUT,
+   .insize = APP_GSDML_SIZE_DIGITAL_IN_2x32b,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_di_1x800 = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x800,
+   .fixed_subslot = 1,
+   .name = "Digital Input 1x800",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_INPUT,
+   .insize = APP_GSDML_SIZE_DIGITAL_IN_1x800,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_do_1x64 = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x64,
+   .fixed_subslot = 1,
+   .name = "Digital Output 1x64",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_OUTPUT,
+   .insize = 0,
+   .outsize = APP_GSDML_SIZE_DIGITAL_OUT_1x64,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_do_2x32a = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_2x32a,
+   .fixed_subslot = 1,
+   .name = "Digital Output 2x32 a",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_OUTPUT,
+   .insize = 0,
+   .outsize = APP_GSDML_SIZE_DIGITAL_OUT_2x32a,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_do_2x32b = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_2x32b,
+   .fixed_subslot = 1,
+   .name = "Digital Output 2x32 b",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_OUTPUT,
+   .insize = 0,
+   .outsize = APP_GSDML_SIZE_DIGITAL_OUT_2x32b,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_do_1x800 = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x800,
+   .fixed_subslot = 1,
+   .name = "Digital Output 1x800",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_OUTPUT,
+   .insize = 0,
+   .outsize = APP_GSDML_SIZE_DIGITAL_OUT_1x800,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_di_port_a = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_PORT_A,
+   .fixed_subslot = 1,
+   .name = "Digital Input Port A",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_INPUT,
+   .insize = APP_GSDML_SIZE_DIGITAL_IN_PORT_A,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submodule_do_port_a = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT_PORT_A,
+   .fixed_subslot = 1,
+   .name = "Digital Output Port A",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_OUTPUT,
+   .insize = 0,
+   .outsize = APP_GSDML_SIZE_DIGITAL_OUT_PORT_A,
+   .parameters = {0}};
+
+/* List of supported modules */
+static const app_gsdml_module_t * app_gsdml_modules[] = {
+   &dap_1,
+   &module_di_1x8,
+   &module_do_1x8,
+   &module_di_1x64,
+   &module_di_2x32a,
+   &module_di_2x32b,
+   &module_di_1x800,
+   &module_do_1x64,
+   &module_do_2x32a,
+   &module_do_2x32b,
+   &module_do_1x800,
+   &module_di_port_a,
+   &module_do_port_a};
+
+/* List of supported submodules */
+static const app_gsdml_submodule_t * app_gsdml_submodules[] = {
+   &dap_identity_1,
+   &dap_interface_1,
+   &dap_port_1,
+   &dap_port_2,
+   &dap_port_3,
+   &dap_port_4,
+
+   &submodule_di_1x8,
+   &submodule_do_1x8,
+   &submodule_di_1x64,
+   &submodule_di_2x32a,
+   &submodule_di_2x32b,
+   &submodule_di_1x800,
+   &submodule_do_1x64,
+   &submodule_do_2x32a,
+   &submodule_do_2x32b,
+   &submodule_do_1x800,
+   &submodule_di_port_a,
+   &submodule_do_port_a};
+
+const app_gsdml_module_t * app_gsdml_get_module_cfg (uint32_t id)
+{
+   uint32_t i;
+   for (i = 0; i < NELEMENTS (app_gsdml_modules); i++)
+   {
+      if (app_gsdml_modules[i]->id == id)
+      {
+         return app_gsdml_modules[i];
+      }
+   }
+   return NULL;
+}
+
+const app_gsdml_module_t ** app_gsdml_get_modules (uint32_t * array_len)
+{
+   *array_len = NELEMENTS (app_gsdml_modules);
+   return app_gsdml_modules;
+}
+
+const app_gsdml_submodule_t ** app_gsdml_get_submodules (uint32_t * array_len)
+{
+   *array_len = NELEMENTS (app_gsdml_submodules);
+   return app_gsdml_submodules;
+}
+
+const app_gsdml_submodule_t * app_gsdml_get_submodule_cfg (uint32_t id)
+{
+   uint32_t i;
+   for (i = 0; i < NELEMENTS (app_gsdml_submodules); i++)
+   {
+      if (app_gsdml_submodules[i]->id == id)
+      {
+         return app_gsdml_submodules[i];
+      }
+   }
+   return NULL;
+}

+ 199 - 0
samples/pn_dev_lan9662/app_gsdml.h

@@ -0,0 +1,199 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef APP_GSDML_H
+#define APP_GSDML_H
+
+/**
+ * @file
+ * @brief Device properties defined by the GSDML device definition
+ *
+ * Functions for getting module, submodule and parameter
+ * configurations using their IDs.
+ *
+ * Important:
+ * Any change in this file may require an update of the GSDML file.
+ * Note that when the GSDML file is updated it has to be reloaded
+ * in your Profinet engineering tool. PLC applications may be affected.
+ *
+ * Design requires unique submodule IDs.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pnet_api.h>
+
+#define APP_GSDML_API 0
+
+#define APP_GSDML_DEFAULT_STATION_NAME "lan9662-dev"
+
+/* GSDML tag: VendorID */
+#define APP_GSDML_VENDOR_ID 0x0493
+
+/* GSDML tag: DeviceID */
+#define APP_GSDML_DEVICE_ID 0x9662
+
+/* Allowed: 'V', 'R', 'P', 'U', 'T' */
+#define APP_GSDML_SW_REV_PREFIX     'V'
+#define APP_GSDML_PROFILE_ID        0x1234
+#define APP_GSDML_PROFILE_SPEC_TYPE 0x5678
+
+/* GSDML tag: Writeable_IM_Records */
+#define APP_GSDML_IM_SUPPORTED                                                 \
+   (PNET_SUPPORTED_IM1 | PNET_SUPPORTED_IM2 | PNET_SUPPORTED_IM3)
+
+/* GSDML tag: OrderNumber */
+#define APP_GSDML_ORDER_ID "12345 Abcdefghijk"
+/* GSDML tag: ModuleInfo / Name */
+#define APP_GSDML_PRODUCT_NAME "P-Net LAN9662 Sample"
+
+/* GSDML tag: MinDeviceInterval */
+#define APP_GSDML_MIN_DEVICE_INTERVAL 32 /* 1ms */
+
+#define APP_GSDML_DIAG_CUSTOM_USI 0x1234
+
+/* See "Specification for GSDML" 8.26 LogBookEntryItem for allowed values */
+#define APP_GSDML_LOGBOOK_ERROR_CODE   0x20 /* Manufacturer specific */
+#define APP_GSDML_LOGBOOK_ERROR_DECODE 0x82 /* Manufacturer specific */
+#define APP_GSDML_LOGBOOK_ERROR_CODE_1 PNET_ERROR_CODE_1_FSPM
+#define APP_GSDML_LOGBOOK_ERROR_CODE_2 0x00       /* Manufacturer specific */
+#define APP_GSDML_LOGBOOK_ENTRY_DETAIL 0xFEE1DEAD /* Manufacturer specific */
+
+#define APP_GSDML_MAX_SUBMODULES 20
+
+#define APP_GSDML_ALARM_PAYLOAD_SIZE 1 /* bytes */
+
+#define APP_GSDML_MOD_ID_DIGITAL_IN_1x8     0x00001001
+#define APP_GSDML_MOD_ID_DIGITAL_OUT_1x8    0x00001002
+#define APP_GSDML_MOD_ID_DIGITAL_IN_1x64    0x00001003
+#define APP_GSDML_MOD_ID_DIGITAL_IN_2x32a   0x00001004
+#define APP_GSDML_MOD_ID_DIGITAL_IN_2x32b   0x00001005
+#define APP_GSDML_MOD_ID_DIGITAL_IN_1x800   0x00001006
+#define APP_GSDML_MOD_ID_DIGITAL_OUT_1x64   0x00001007
+#define APP_GSDML_MOD_ID_DIGITAL_OUT_2x32a  0x00001008
+#define APP_GSDML_MOD_ID_DIGITAL_OUT_2x32b  0x00001009
+#define APP_GSDML_MOD_ID_DIGITAL_OUT_1x800  0x0000100A
+#define APP_GSDML_MOD_ID_DIGITAL_IN_PORT_A  0x0000100B
+#define APP_GSDML_MOD_ID_DIGITAL_OUT_PORT_A 0x0000100C
+
+#define APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x8     0x00002001
+#define APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x8    0x00002002
+#define APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x64    0x00002003
+#define APP_GSDML_SUBMOD_ID_DIGITAL_IN_2x32a   0x00002004
+#define APP_GSDML_SUBMOD_ID_DIGITAL_IN_2x32b   0x00002005
+#define APP_GSDML_SUBMOD_ID_DIGITAL_IN_1x800   0x00002006
+#define APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x64   0x00002007
+#define APP_GSDML_SUBMOD_ID_DIGITAL_OUT_2x32a  0x00002008
+#define APP_GSDML_SUBMOD_ID_DIGITAL_OUT_2x32b  0x00002009
+#define APP_GSDML_SUBMOD_ID_DIGITAL_OUT_1x800  0x0000200A
+#define APP_GSDML_SUBMOD_ID_DIGITAL_IN_PORT_A  0x0000200B
+#define APP_GSDML_SUBMOD_ID_DIGITAL_OUT_PORT_A 0x0000200C
+
+#define APP_GSDML_SIZE_DIGITAL_IN_1x8     1
+#define APP_GSDML_SIZE_DIGITAL_OUT_1x8    1
+#define APP_GSDML_SIZE_DIGITAL_IN_1x64    8
+#define APP_GSDML_SIZE_DIGITAL_IN_2x32a   8
+#define APP_GSDML_SIZE_DIGITAL_IN_2x32b   8
+#define APP_GSDML_SIZE_DIGITAL_IN_1x800   100
+#define APP_GSDML_SIZE_DIGITAL_OUT_1x64   8
+#define APP_GSDML_SIZE_DIGITAL_OUT_2x32a  8
+#define APP_GSDML_SIZE_DIGITAL_OUT_2x32b  8
+#define APP_GSDML_SIZE_DIGITAL_OUT_1x800  100
+#define APP_GSDML_SIZE_DIGITAL_IN_PORT_A  4
+#define APP_GSDML_SIZE_DIGITAL_OUT_PORT_A 4
+
+#define APP_GSDML_MAX_SUBMODULE_DATA_SIZE 100
+
+/* TODO support virtual and pluggable submodules. */
+typedef struct cfg_module
+{
+   uint32_t id;
+   const char * name;
+   uint32_t fixed_slot;   /* Set to UINT32_MAX if not fixed */
+   uint32_t submodules[]; /* Variable length, ends with 0*/
+} app_gsdml_module_t;
+
+typedef struct app_gsdml_submodule
+{
+   uint32_t id;
+   const char * name;
+   uint32_t api;
+   uint32_t fixed_subslot; /* Set to UINT32_MAX if not fixed */
+   pnet_submodule_dir_t data_dir;
+   uint16_t insize;
+   uint16_t outsize;
+   uint16_t parameters[]; /* Variable length, ends with 0 */
+} app_gsdml_submodule_t;
+
+typedef struct app_gsdml_param
+{
+   uint32_t index;
+   const char * name;
+   uint16_t length;
+} app_gsdml_param_t;
+
+/**
+ * Get array of supported modules
+ * @param array_len  Out: Length of array
+ * @return Modules array
+ */
+const app_gsdml_module_t ** app_gsdml_get_modules (uint32_t * array_len);
+
+/**
+ * Get array of supported submodules
+ * @param array_len  Out: Length of array
+ * @return Submodules array
+ */
+const app_gsdml_submodule_t ** app_gsdml_get_submodules (uint32_t * array_len);
+
+/**
+ * Get array of supported parameters
+ * @param array_len  Out: Length of array
+ * @return Parameters array
+ */
+const app_gsdml_param_t ** app_gsdml_get_parameters (uint32_t * array_len);
+
+/**
+ * Get module configuration from module id
+ * @param module_id  In: Module id
+ * @return Module configuration, NULL if not found
+ */
+const app_gsdml_module_t * app_gsdml_get_module_cfg (uint32_t module_id);
+
+/**
+ * Get submodule module configuration from submodule id
+ * @param submodule_id  In: Submodule id
+ * @return Submodule configuration, NULL if not found
+ */
+const app_gsdml_submodule_t * app_gsdml_get_submodule_cfg (
+   uint32_t submodule_id);
+
+/**
+ * Get parameter configuration from parameter index
+ * @param submodule_id  In: Submodule id
+ * @param index         In: Parameters index
+ * @return Parameter configuration, NULL if not found
+ */
+const app_gsdml_param_t * app_gsdml_get_parameter_cfg (
+   uint32_t submodule_id,
+   uint32_t index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_GSDML_H */

+ 52 - 0
samples/pn_dev_lan9662/app_log.c

@@ -0,0 +1,52 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#include "app_log.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+static int32_t log_level = APP_DEFAULT_LOG_LEVEL;
+
+void app_log_set_log_level (int32_t level)
+{
+   log_level = level;
+}
+
+void app_log (int32_t level, const char * fmt, ...)
+{
+   va_list list;
+
+   if (level >= log_level)
+   {
+      va_start (list, fmt);
+      vprintf (fmt, list);
+      va_end (list);
+      fflush (stdout);
+   }
+}
+
+void app_log_print_bytes (int32_t level, const uint8_t * bytes, uint32_t len)
+{
+   if (level >= log_level)
+   {
+      printf ("  Bytes: ");
+      for (uint32_t i = 0; i < len; i++)
+      {
+         printf ("%02X ", bytes[i]);
+      }
+      printf ("\n");
+   }
+}

+ 75 - 0
samples/pn_dev_lan9662/app_log.h

@@ -0,0 +1,75 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef APP_LOG_H
+#define APP_LOG_H
+
+/**
+ * @file
+ * @brief Application debug log utility
+ *
+ * Runtime configurable debug log using printf()
+ * Levels matches levels used in P-Net.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#define APP_LOG_LEVEL_DEBUG   0x00
+#define APP_LOG_LEVEL_INFO    0x01
+#define APP_LOG_LEVEL_WARNING 0x02
+#define APP_LOG_LEVEL_ERROR   0x03
+#define APP_LOG_LEVEL_FATAL   0x04
+
+#define APP_DEFAULT_LOG_LEVEL APP_LOG_LEVEL_FATAL
+
+#define APP_LOG(level, ...) app_log (level, __VA_ARGS__)
+
+#define APP_LOG_DEBUG(...)   APP_LOG (APP_LOG_LEVEL_DEBUG, __VA_ARGS__)
+#define APP_LOG_INFO(...)    APP_LOG (APP_LOG_LEVEL_INFO, __VA_ARGS__)
+#define APP_LOG_WARNING(...) APP_LOG (APP_LOG_LEVEL_WARNING, __VA_ARGS__)
+#define APP_LOG_ERROR(...)   APP_LOG (APP_LOG_LEVEL_ERROR, __VA_ARGS__)
+#define APP_LOG_FATAL(...)   APP_LOG (APP_LOG_LEVEL_FATAL, __VA_ARGS__)
+
+/**
+ * Print log message depending on level
+ * Use the APP_LOG_xxxxx macros instead of this function.
+ * @param level         In: Message log level
+ * @param fmt           In: Log message format string
+ */
+void app_log (int32_t level, const char * fmt, ...);
+
+/**
+ * Log an array of bytes
+ * @param level         In: Log level
+ * @param bytes         In: Array of bytes
+ * @param length        In: Length of array
+ */
+void app_log_print_bytes (int32_t level, const uint8_t * bytes, uint32_t length);
+
+/**
+ * Set log level
+ * @param level         In: Log level
+ */
+void app_log_set_log_level (int32_t level);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_LOG_H */

+ 318 - 0
samples/pn_dev_lan9662/app_shm.c

@@ -0,0 +1,318 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#include "app_shm.h"
+#include "app_log.h"
+
+#include <semaphore.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define APP_SHM_MAX_FILE_NAME_LEN 40
+
+typedef struct app_shm_t
+{
+   char name[APP_SHM_MAX_FILE_NAME_LEN];
+   int fd;
+   caddr_t mem_address;
+   sem_t * sem;
+   size_t size;
+} app_shm_t;
+
+/**
+ * Format name string
+ *
+ * Replace space " " with "-"
+ * Convert uppercase characters to lowercase.
+ * @param name  In:  NULL terminated string
+ * @return 0 on success, -1 on error
+ **/
+static int format_name (char * name)
+{
+   uint32_t i;
+
+   if (name == NULL)
+   {
+      return -1;
+   }
+
+   for (i = 0; name[i] != '\0'; i++)
+   {
+      name[i] = tolower (name[i]);
+      if (name[i] == ' ')
+      {
+         name[i] = '_';
+      }
+   }
+
+   return 0;
+}
+
+/**
+ * Initialise an existing shared memory area handle
+ *
+ * @param handle     In:    Handle
+ * @param name       In:    Name
+ * @param slot       In:    Slot number
+ * @param subslot    In:    Subslot number
+ * @param size       In:    Size in bytes
+ * @param is_output  In:    True if the shared memory area is an output
+ * @return 0 on success, -1 on error
+ **/
+static int app_shm_init (
+   app_shm_t * handle,
+   const char * name,
+   uint32_t slot,
+   uint32_t subslot,
+   uint32_t size,
+   bool is_output)
+{
+   const int shm_flags = O_RDWR | O_CREAT;
+
+   if (handle == NULL || name == NULL || size == 0)
+   {
+      return -1;
+   }
+   memset (handle, 0, sizeof (app_shm_t));
+
+   /* Only include slot and subslot numbers if both module and
+      submodules are fixed in slot and subslot */
+   if (slot != UINT32_MAX && subslot != UINT32_MAX)
+   {
+      snprintf (
+         handle->name,
+         APP_SHM_MAX_FILE_NAME_LEN - 1,
+         "pnet-%s-%d-%d-%s",
+         is_output ? "out" : "in",
+         slot,
+         subslot,
+         name);
+   }
+   else
+   {
+      snprintf (
+         handle->name,
+         APP_SHM_MAX_FILE_NAME_LEN - 1,
+         "pnet-%s-%s",
+         is_output ? "out" : "in",
+         name);
+   }
+
+   format_name (handle->name);
+
+   handle->size = size;
+
+   handle->fd = shm_open (handle->name, shm_flags, 0666);
+
+   if (handle->fd < 0)
+   {
+      APP_LOG_ERROR (
+         "Failed to open shared memory\n"
+         "  file name: %s\n"
+         "  Input files is expected to be created by data generating "
+         "  application\n",
+         handle->name);
+
+      return -1;
+   }
+
+   if (ftruncate (handle->fd, handle->size) != 0)
+   {
+      return -1;
+   }
+
+   handle->mem_address = mmap (
+      NULL,
+      handle->size,
+      PROT_READ | PROT_WRITE,
+      MAP_SHARED,
+      handle->fd,
+      0);
+
+   if (handle->mem_address == MAP_FAILED)
+   {
+      return -1;
+   }
+
+   handle->sem = sem_open (handle->name, O_CREAT, 0666, 1);
+
+   if (handle->sem == NULL)
+   {
+      return -1;
+   }
+
+   /* sem_unlink (handle->name); TODO */
+   APP_LOG_DEBUG ("shared memory area created\n");
+   APP_LOG_DEBUG ("  name=%s\n", handle->name);
+   APP_LOG_DEBUG ("  size=%lu\n", handle->size);
+   APP_LOG_DEBUG ("  backing file = /dev/shm/%s\n", handle->name);
+
+   return 0;
+}
+
+app_shm_t * app_shm_create_input (
+   const char * name,
+   int slot,
+   int subslot,
+   int size)
+{
+   app_shm_t * handle = (app_shm_t *)malloc (sizeof (app_shm_t));
+
+   if (handle != NULL)
+   {
+      if (app_shm_init (handle, name, slot, subslot, size, false) != 0)
+      {
+         app_shm_destroy (handle);
+         handle = NULL;
+      }
+   }
+
+   return handle;
+}
+
+app_shm_t * app_shm_create_output (
+   const char * name,
+   int slot,
+   int subslot,
+   int size)
+{
+   app_shm_t * handle = (app_shm_t *)malloc (sizeof (app_shm_t));
+
+   if (handle != NULL)
+   {
+      if (app_shm_init (handle, name, slot, subslot, size, true) != 0)
+      {
+         app_shm_destroy (handle);
+         handle = NULL;
+      }
+   }
+
+   return handle;
+}
+
+/**
+ * Free resources related to a shared memory area.
+ *
+ * Does not free the memory allocated for the handle itself.
+ *
+ * @param handle     In:    Handle
+ **/
+static void app_shm_free_resources (app_shm_t * handle)
+{
+   if (handle == NULL)
+   {
+      return;
+   }
+
+   if (handle->mem_address != 0)
+   {
+      munmap (handle->mem_address, handle->size);
+   }
+
+   if (handle->fd >= 0)
+   {
+      close (handle->fd);
+   }
+
+   if (handle->sem != NULL)
+   {
+      sem_close (handle->sem);
+      /* sem_unlink TODO */
+   }
+
+   unlink (handle->name);
+}
+
+void app_shm_destroy (app_shm_t * handle)
+{
+   if (handle != NULL)
+   {
+      app_shm_free_resources (handle);
+      free (handle);
+   }
+}
+
+int app_shm_read (app_shm_t * handle, void * data, size_t size)
+{
+   if (handle == NULL || size == 0)
+   {
+      return -1;
+   }
+
+   if (sem_wait (handle->sem) != 0)
+   {
+      return -1;
+   }
+
+   memcpy (data, handle->mem_address, size);
+
+   if (sem_post (handle->sem) != 0)
+   {
+      return -1;
+   }
+
+   return 0;
+}
+
+int app_shm_write (app_shm_t * handle, void * data, size_t size)
+{
+   if (handle == NULL || data == NULL || size > handle->size)
+   {
+      return -1;
+   }
+
+   if (sem_wait (handle->sem) != 0)
+   {
+      return -1;
+   }
+
+   memcpy (handle->mem_address, data, size);
+
+   if (sem_post (handle->sem) != 0)
+   {
+      return -1;
+   }
+
+   return 0;
+}
+
+int app_shm_reset (app_shm_t * handle)
+{
+   if (handle == NULL)
+   {
+      return -1;
+   }
+
+   if (sem_wait (handle->sem) != 0)
+   {
+      return -1;
+   }
+
+   memset (handle->mem_address, 0, handle->size);
+
+   if (sem_post (handle->sem) != 0)
+   {
+      return -1;
+   }
+
+   return 0;
+}

+ 104 - 0
samples/pn_dev_lan9662/app_shm.h

@@ -0,0 +1,104 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef APP_SHM_H
+#define APP_SHM_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct app_shm_t app_shm_t;
+
+/**
+ * Create a shared memory area used as input.
+ *
+ * Shared memory is read by this application.
+ * Intended for submodule input data.
+ * The slot and and subslot info is used to
+ * generate the name of the shared memory area.
+ *
+ * @param name       In:  Submodule id
+ * @param slot       In:  Slot number
+ * @param subslot    In:  Subslot number
+ * @param size       In:  Size of area
+ * @return Handle to created shared memory area. NULL on error.
+ */
+app_shm_t * app_shm_create_input (
+   const char * name,
+   int slot,
+   int subslot,
+   int size);
+
+/**
+ * Create a shared memory area used as output.
+ *
+ * Shared memory is written by this application.
+ * Intended for submodule output data.
+ * The slot and and subslot info is used to
+ * generate the name of the shared memory area.
+ *
+ * @param name       In:  Submodule id
+ * @param slot       In:  Slot number
+ * @param subslot    In:  Subslot number
+ * @param size       In:  Size of area
+ * @return Handle to created shared memory area. NULL on error.
+ */
+app_shm_t * app_shm_create_output (
+   const char * name,
+   int slot,
+   int subslot,
+   int size);
+
+/**
+ * Free the resources of a shared memory area
+ *
+ * @param handle       In:  Shared memory object handle
+ */
+void app_shm_destroy (app_shm_t * handle);
+
+/**
+ * Read shared memory area into a buffer
+ *
+ * @param handle     In:  Shared memory object handle
+ * @param data       In:  Start of buffer
+ * @param size       In:  Number of bytes to read
+ * @return 0 on success, -1 on error
+ */
+int app_shm_read (app_shm_t * handle, void * data, size_t size);
+
+/**
+ * Write to shared memory
+ *
+ * @param handle     In:  Shared memory object handle
+ * @param data       In:  Start of buffer
+ * @param size       In:  Number of bytes to write
+ * @return 0 on success, -1 on error
+ */
+int app_shm_write (app_shm_t * handle, void * data, size_t size);
+
+/**
+ * Set shared memory to zero
+ *
+ * @param handle     In:  Shared memory object handle
+ * @return 0 on success, -1 on error
+ */
+int app_shm_reset (app_shm_t * handle);
+
+#endif /* APP_SHM_H */

+ 722 - 0
samples/pn_dev_lan9662/app_utils.c

@@ -0,0 +1,722 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#define _GNU_SOURCE /* For asprintf() */
+
+#include "app_utils.h"
+#include "app_log.h"
+#include "app_gsdml.h"
+#include "sampleapp_common.h"
+#include "osal.h"
+#include "osal_log.h" /* For LOG_LEVEL */
+#include "pnal.h"
+#include <pnet_api.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define GET_HIGH_BYTE(id) ((id >> 8) & 0xFF)
+#define GET_LOW_BYTE(id)  (id & 0xFF)
+
+void app_utils_ip_to_string (pnal_ipaddr_t ip, char * outputstring)
+{
+   snprintf (
+      outputstring,
+      PNAL_INET_ADDRSTR_SIZE,
+      "%u.%u.%u.%u",
+      (uint8_t) ((ip >> 24) & 0xFF),
+      (uint8_t) ((ip >> 16) & 0xFF),
+      (uint8_t) ((ip >> 8) & 0xFF),
+      (uint8_t) (ip & 0xFF));
+}
+
+void app_utils_mac_to_string (pnet_ethaddr_t mac, char * outputstring)
+{
+   snprintf (
+      outputstring,
+      PNAL_ETH_ADDRSTR_SIZE,
+      "%02X:%02X:%02X:%02X:%02X:%02X",
+      mac.addr[0],
+      mac.addr[1],
+      mac.addr[2],
+      mac.addr[3],
+      mac.addr[4],
+      mac.addr[5]);
+}
+
+const char * app_utils_submod_dir_to_string (pnet_submodule_dir_t direction)
+{
+   const char * s = "<error>";
+
+   switch (direction)
+   {
+   case PNET_DIR_NO_IO:
+      s = "NO_IO";
+      break;
+   case PNET_DIR_INPUT:
+      s = "INPUT";
+      break;
+   case PNET_DIR_OUTPUT:
+      s = "OUTPUT";
+      break;
+   case PNET_DIR_IO:
+      s = "INPUT_OUTPUT";
+      break;
+   }
+
+   return s;
+}
+
+const char * app_utils_ioxs_to_string (pnet_ioxs_values_t ioxs)
+{
+   const char * s = "<error>";
+   switch (ioxs)
+   {
+   case PNET_IOXS_BAD:
+      s = "BAD";
+      break;
+   case PNET_IOXS_GOOD:
+      s = "GOOD";
+      break;
+   }
+
+   return s;
+}
+
+char * app_utils_get_subslot_string (const app_subslot_t * subslot)
+{
+   static char buf[40];
+   snprintf (
+      buf,
+      sizeof (buf),
+      "[%u,%u,\"%s\"]",
+      subslot->slot_nbr,
+      subslot->subslot_nbr,
+      subslot->submodule_name);
+   return buf;
+}
+
+void app_utils_get_error_code_strings (
+   uint16_t err_cls,
+   uint16_t err_code,
+   const char ** err_cls_str,
+   const char ** err_code_str)
+{
+   if (err_cls_str == NULL || err_cls_str == NULL)
+   {
+      return;
+   }
+   *err_cls_str = "Not decoded";
+   *err_code_str = "Not decoded";
+
+   switch (err_cls)
+   {
+   case PNET_ERROR_CODE_1_RTA_ERR_CLS_PROTOCOL:
+      *err_cls_str = "Real-Time Acyclic Protocol";
+      switch (err_code)
+      {
+      case PNET_ERROR_CODE_2_ABORT_AR_CONSUMER_DHT_EXPIRED:
+         *err_code_str = "Device missed cyclic data "
+                         "deadline, device terminated AR";
+         break;
+      case PNET_ERROR_CODE_2_ABORT_AR_CMI_TIMEOUT:
+         *err_code_str = "Communication initialization "
+                         "timeout, device terminated AR";
+         break;
+      case PNET_ERROR_CODE_2_ABORT_AR_RELEASE_IND_RECEIVED:
+         *err_code_str = "AR release indication received";
+         break;
+      case PNET_ERROR_CODE_2_ABORT_DCP_STATION_NAME_CHANGED:
+         *err_code_str = "DCP station name changed, "
+                         "device terminated AR";
+         break;
+      case PNET_ERROR_CODE_2_ABORT_DCP_RESET_TO_FACTORY:
+         *err_code_str = "DCP reset to factory or factory "
+                         "reset, device terminated AR";
+         break;
+      }
+      break;
+
+   case PNET_ERROR_CODE_1_CTLDINA:
+      *err_cls_str = "CTLDINA = Name and IP assignment from controller";
+      switch (err_code)
+      {
+      case PNET_ERROR_CODE_2_CTLDINA_ARP_MULTIPLE_IP_ADDRESSES:
+         *err_code_str = "Multiple users of same IP address";
+         break;
+      }
+      break;
+   }
+}
+void app_utils_copy_ip_to_struct (
+   pnet_cfg_ip_addr_t * destination_struct,
+   pnal_ipaddr_t ip)
+{
+   destination_struct->a = ((ip >> 24) & 0xFF);
+   destination_struct->b = ((ip >> 16) & 0xFF);
+   destination_struct->c = ((ip >> 8) & 0xFF);
+   destination_struct->d = (ip & 0xFF);
+}
+
+const char * app_utils_dcontrol_cmd_to_string (
+   pnet_control_command_t control_command)
+{
+   const char * s = NULL;
+
+   switch (control_command)
+   {
+   case PNET_CONTROL_COMMAND_PRM_BEGIN:
+      s = "PRM_BEGIN";
+      break;
+   case PNET_CONTROL_COMMAND_PRM_END:
+      s = "PRM_END";
+      break;
+   case PNET_CONTROL_COMMAND_APP_RDY:
+      s = "APP_RDY";
+      break;
+   case PNET_CONTROL_COMMAND_RELEASE:
+      s = "RELEASE";
+      break;
+   default:
+      s = "<error>";
+      break;
+   }
+
+   return s;
+}
+
+const char * app_utils_event_to_string (pnet_event_values_t event)
+{
+   const char * s = "<error>";
+
+   switch (event)
+   {
+   case PNET_EVENT_ABORT:
+      s = "PNET_EVENT_ABORT";
+      break;
+   case PNET_EVENT_STARTUP:
+      s = "PNET_EVENT_STARTUP";
+      break;
+   case PNET_EVENT_PRMEND:
+      s = "PNET_EVENT_PRMEND";
+      break;
+   case PNET_EVENT_APPLRDY:
+      s = "PNET_EVENT_APPLRDY";
+      break;
+   case PNET_EVENT_DATA:
+      s = "PNET_EVENT_DATA";
+      break;
+   }
+
+   return s;
+}
+
+int app_utils_pnet_cfg_init_default (pnet_cfg_t * cfg)
+{
+   memset (cfg, 0, sizeof (pnet_cfg_t));
+
+   cfg->tick_us = APP_TICK_INTERVAL_US;
+
+   /* Identification & Maintenance */
+
+   cfg->im_0_data.im_vendor_id_hi = GET_HIGH_BYTE (APP_GSDML_VENDOR_ID);
+   cfg->im_0_data.im_vendor_id_lo = GET_LOW_BYTE (APP_GSDML_VENDOR_ID);
+
+   cfg->im_0_data.im_hardware_revision = 1;
+   cfg->im_0_data.im_sw_revision_prefix = APP_GSDML_SW_REV_PREFIX;
+   cfg->im_0_data.im_sw_revision_functional_enhancement = PNET_VERSION_MAJOR;
+   cfg->im_0_data.im_sw_revision_bug_fix = PNET_VERSION_MINOR;
+   cfg->im_0_data.im_sw_revision_internal_change = PNET_VERSION_PATCH;
+   cfg->im_0_data.im_revision_counter = 0; /* Only 0 allowed according
+                                                       to standard */
+   cfg->im_0_data.im_profile_id = APP_GSDML_PROFILE_ID;
+   cfg->im_0_data.im_profile_specific_type = APP_GSDML_PROFILE_SPEC_TYPE;
+   cfg->im_0_data.im_version_major = 1;
+   cfg->im_0_data.im_version_minor = 1;
+   cfg->im_0_data.im_supported = APP_GSDML_IM_SUPPORTED;
+   strcpy (cfg->im_0_data.im_order_id, APP_GSDML_ORDER_ID);
+   strcpy (cfg->im_0_data.im_serial_number, "00001");
+   strcpy (cfg->im_1_data.im_tag_function, "my function");
+   strcpy (cfg->im_1_data.im_tag_location, "my location");
+   strcpy (cfg->im_2_data.im_date, "2020-09-03 13:53");
+   strcpy (cfg->im_3_data.im_descriptor, "my descriptor");
+   strcpy (cfg->im_4_data.im_signature, ""); /* Functional safety */
+
+   /* Device configuration */
+   cfg->device_id.vendor_id_hi = GET_HIGH_BYTE (APP_GSDML_VENDOR_ID);
+   cfg->device_id.vendor_id_lo = GET_LOW_BYTE (APP_GSDML_VENDOR_ID);
+   cfg->device_id.device_id_hi = GET_HIGH_BYTE (APP_GSDML_DEVICE_ID);
+   cfg->device_id.device_id_lo = GET_LOW_BYTE (APP_GSDML_DEVICE_ID);
+
+   cfg->oem_device_id.vendor_id_hi = 0xc0;
+   cfg->oem_device_id.vendor_id_lo = 0xff;
+   cfg->oem_device_id.device_id_hi = 0xee;
+   cfg->oem_device_id.device_id_lo = 0x01;
+   strcpy (cfg->product_name, APP_GSDML_PRODUCT_NAME);
+
+   cfg->send_hello = true;
+
+   /* Timing */
+   cfg->min_device_interval = APP_GSDML_MIN_DEVICE_INTERVAL;
+
+   /* Should be set by application as part of network configuration. */
+   cfg->num_physical_ports = 1;
+
+   snprintf (
+      cfg->station_name,
+      sizeof (cfg->station_name),
+      APP_GSDML_DEFAULT_STATION_NAME);
+
+   /* Diagnosis mechanism */
+   /* Use "Extended channel diagnosis" instead of
+    * "Qualified channel diagnosis" format on the wire,
+    * as this is better supported by Wireshark.
+    */
+   cfg->use_qualified_diagnosis = false;
+
+#if PNET_OPTION_DRIVER_ENABLE
+   cfg->driver_enable = false;
+   cfg->driver_config.mera.vcam_base_id = PNET_LAN9662_VCAM_BASE;
+   cfg->driver_config.mera.rtp_base_id = PNET_LAN9662_RTP_BASE;
+   cfg->driver_config.mera.wal_base_id = PNET_LAN9662_WAL_BASE;
+   cfg->driver_config.mera.ral_base_id = PNET_LAN9662_RAL_BASE;
+#endif
+
+   return 0;
+}
+
+int app_utils_get_netif_namelist (
+   const char * arg_str,
+   uint16_t max_port,
+   app_utils_netif_namelist_t * p_if_list,
+   uint16_t * p_num_ports)
+{
+   int ret = 0;
+   uint16_t i = 0;
+   uint16_t j = 0;
+   uint16_t if_index = 0;
+   uint16_t number_of_given_names = 1;
+   uint16_t if_list_size = max_port + 1;
+   char c;
+
+   if (max_port == 0)
+   {
+      printf ("Error: max_port is 0.\n");
+      return -1;
+   }
+
+   memset (p_if_list, 0, sizeof (*p_if_list));
+   c = arg_str[i++];
+   while (c != '\0')
+   {
+      if (c != ',')
+      {
+         if (if_index < if_list_size)
+         {
+            p_if_list->netif[if_index].name[j++] = c;
+         }
+      }
+      else
+      {
+         if (if_index < if_list_size)
+         {
+            p_if_list->netif[if_index].name[j++] = '\0';
+            j = 0;
+            if_index++;
+         }
+         number_of_given_names++;
+      }
+
+      c = arg_str[i++];
+   }
+
+   if (max_port == 1 && number_of_given_names > 1)
+   {
+      printf ("Error: Only 1 network interface expected as max_port is 1.\n");
+      return -1;
+   }
+   if (number_of_given_names == 2)
+   {
+      printf ("Error: It is illegal to give 2 interface names. Use 1, or one "
+              "more than the number of physical interfaces.\n");
+      return -1;
+   }
+   if (number_of_given_names > max_port + 1)
+   {
+      printf (
+         "Error: You have given %u interface names, but max is %u as "
+         "PNET_MAX_PHYSICAL_PORTS is %u.\n",
+         number_of_given_names,
+         max_port + 1,
+         max_port);
+      return -1;
+   }
+
+   if (number_of_given_names == 1)
+   {
+      if (strlen (p_if_list->netif[0].name) == 0)
+      {
+         printf ("Error: Zero length network interface name.\n");
+         return -1;
+      }
+      else
+      {
+         p_if_list->netif[1] = p_if_list->netif[0];
+         *p_num_ports = 1;
+      }
+   }
+   else
+   {
+      for (i = 0; i < number_of_given_names; i++)
+      {
+         if (strlen (p_if_list->netif[i].name) == 0)
+         {
+            printf ("Error: Zero length network interface name (%d).\n", i);
+            return -1;
+         }
+      }
+
+      *p_num_ports = number_of_given_names - 1;
+   }
+
+   return ret;
+}
+
+int app_utils_pnet_cfg_init_netifs (
+   const char * netif_list_str,
+   app_utils_netif_namelist_t * if_list,
+   uint16_t * number_of_ports,
+   pnet_if_cfg_t * if_cfg)
+{
+   int ret = 0;
+   int i = 0;
+   pnal_ipaddr_t ip;
+   pnal_ipaddr_t netmask;
+   pnal_ipaddr_t gateway;
+
+   ret = app_utils_get_netif_namelist (
+      netif_list_str,
+      PNET_MAX_PHYSICAL_PORTS,
+      if_list,
+      number_of_ports);
+   if (ret != 0)
+   {
+      return ret;
+   }
+   if_cfg->main_netif_name = if_list->netif[0].name;
+
+   for (i = 1; i <= *number_of_ports; i++)
+   {
+      if_cfg->physical_ports[i - 1].netif_name = if_list->netif[i].name;
+   }
+
+   /* Read IP, netmask, gateway from operating system */
+   ip = pnal_get_ip_address (if_cfg->main_netif_name);
+   netmask = pnal_get_netmask (if_cfg->main_netif_name);
+   gateway = pnal_get_gateway (if_cfg->main_netif_name);
+
+   app_utils_copy_ip_to_struct (&if_cfg->ip_cfg.ip_addr, ip);
+   app_utils_copy_ip_to_struct (&if_cfg->ip_cfg.ip_gateway, gateway);
+   app_utils_copy_ip_to_struct (&if_cfg->ip_cfg.ip_mask, netmask);
+
+   return ret;
+}
+
+static void app_utils_print_mac_address (const char * netif_name)
+{
+   pnal_ethaddr_t pnal_mac_addr;
+   if (pnal_get_macaddress (netif_name, &pnal_mac_addr) == 0)
+   {
+      APP_LOG_INFO (
+         "%02X:%02X:%02X:%02X:%02X:%02X\n",
+         pnal_mac_addr.addr[0],
+         pnal_mac_addr.addr[1],
+         pnal_mac_addr.addr[2],
+         pnal_mac_addr.addr[3],
+         pnal_mac_addr.addr[4],
+         pnal_mac_addr.addr[5]);
+   }
+   else
+   {
+      APP_LOG_ERROR ("Failed read mac address\n");
+   }
+}
+
+void app_utils_print_network_config (
+   pnet_if_cfg_t * if_cfg,
+   uint16_t number_of_ports)
+{
+   uint16_t i;
+   char hostname_string[PNAL_HOSTNAME_MAX_SIZE]; /* Terminated string */
+
+   APP_LOG_INFO ("Management port:      %s ", if_cfg->main_netif_name);
+   app_utils_print_mac_address (if_cfg->main_netif_name);
+   for (i = 1; i <= number_of_ports; i++)
+   {
+      APP_LOG_INFO (
+         "Physical port [%u]:    %s ",
+         i,
+         if_cfg->physical_ports[i - 1].netif_name);
+
+      app_utils_print_mac_address (if_cfg->physical_ports[i - 1].netif_name);
+   }
+
+   if (pnal_get_hostname (hostname_string) != 0)
+   {
+      hostname_string[0] = '\0';
+   }
+
+   APP_LOG_INFO ("Hostname:             %s\n", hostname_string);
+   APP_LOG_INFO (
+      "IP address:           %u.%u.%u.%u\n",
+      if_cfg->ip_cfg.ip_addr.a,
+      if_cfg->ip_cfg.ip_addr.b,
+      if_cfg->ip_cfg.ip_addr.c,
+      if_cfg->ip_cfg.ip_addr.d);
+   APP_LOG_INFO (
+      "Netmask:              %u.%u.%u.%u\n",
+      if_cfg->ip_cfg.ip_mask.a,
+      if_cfg->ip_cfg.ip_mask.b,
+      if_cfg->ip_cfg.ip_mask.c,
+      if_cfg->ip_cfg.ip_mask.d);
+   APP_LOG_INFO (
+      "Gateway:              %u.%u.%u.%u\n",
+      if_cfg->ip_cfg.ip_gateway.a,
+      if_cfg->ip_cfg.ip_gateway.b,
+      if_cfg->ip_cfg.ip_gateway.c,
+      if_cfg->ip_cfg.ip_gateway.d);
+}
+
+void app_utils_print_ioxs_change (
+   app_subslot_t * subslot,
+   const char * ioxs_str,
+   uint8_t ioxs_current,
+   uint8_t ioxs_new)
+{
+   if (ioxs_current != ioxs_new)
+   {
+      if (ioxs_new == PNET_IOXS_BAD || ioxs_new == PNET_IOXS_GOOD)
+      {
+         APP_LOG_DEBUG (
+            "  %-40s PLC reports %s %s \n",
+            app_utils_get_subslot_string (subslot),
+            ioxs_str,
+            app_utils_ioxs_to_string (ioxs_new));
+      }
+      else if (ioxs_new != PNET_IOXS_GOOD)
+      {
+         APP_LOG_WARNING (
+            "  %-40s PLC reports %s 0x%x Is it in STOP mode?\n",
+            app_utils_get_subslot_string (subslot),
+            ioxs_str,
+            ioxs_new);
+      }
+   }
+}
+
+int app_utils_plug_module (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint32_t id,
+   const char * name)
+{
+   if (slot_nbr >= PNET_MAX_SLOTS)
+   {
+      return -1;
+   }
+
+   p_api->slots[slot_nbr].module_id = id;
+   p_api->slots[slot_nbr].plugged = true;
+   p_api->slots[slot_nbr].name = name;
+
+   return 0;
+}
+
+int app_utils_pull_module (app_api_t * p_api, uint16_t slot_nbr)
+{
+   if (slot_nbr >= PNET_MAX_SLOTS)
+   {
+      return -1;
+   }
+
+   p_api->slots[slot_nbr].plugged = false;
+
+   return 0;
+}
+
+app_subslot_t * app_utils_plug_submodule (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   uint32_t submodule_ident,
+   const pnet_data_cfg_t * p_data_cfg,
+   const char * submodule_name,
+   app_utils_cyclic_callback cyclic_callback,
+   void * tag)
+{
+   uint16_t subslot_ix;
+
+   if (slot_nbr >= PNET_MAX_SLOTS || p_api == NULL || p_data_cfg == NULL)
+   {
+      return NULL;
+   }
+
+   for (subslot_ix = 0; subslot_ix < PNET_MAX_SUBSLOTS; subslot_ix++)
+   {
+      if (p_api->slots[slot_nbr].subslots[subslot_ix].used == false)
+      {
+         app_subslot_t * p_subslot =
+            &p_api->slots[slot_nbr].subslots[subslot_ix];
+
+         p_subslot->used = true;
+         p_subslot->plugged = true;
+         p_subslot->slot_nbr = slot_nbr;
+         p_subslot->subslot_nbr = subslot_nbr;
+         p_subslot->submodule_name = submodule_name;
+         p_subslot->submodule_id = submodule_ident;
+         p_subslot->data_cfg = *p_data_cfg;
+         p_subslot->cyclic_callback = cyclic_callback;
+         p_subslot->tag = tag;
+         p_subslot->iocs = 0;
+         return p_subslot;
+      }
+   }
+
+   return NULL;
+}
+
+int app_utils_pull_submodule (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr)
+{
+   app_subslot_t * p_subslot = NULL;
+
+   if (slot_nbr >= PNET_MAX_SUBSLOTS || p_api == NULL)
+   {
+      return -1;
+   }
+
+   p_subslot = app_utils_subslot_get (p_api, slot_nbr, subslot_nbr);
+   if (p_subslot != NULL)
+   {
+      memset (p_subslot, 0, sizeof (app_subslot_t));
+      p_subslot->used = false;
+      return 0;
+   }
+
+   return -1;
+}
+
+/**
+ * Get subslot application information.
+ * @param p_appdata        InOut: Application state.
+ * @param slot_nbr         In:    Slot number.
+ * @param subslot_nbr      In:    Subslot number. Range 0 - 0x9FFF.
+ * @return Reference to application subslot,
+ *         NULL if subslot is not found/plugged.
+ */
+app_subslot_t * app_utils_subslot_get (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr)
+{
+   uint16_t subslot_ix;
+
+   if (slot_nbr >= PNET_MAX_SLOTS || p_api == NULL)
+   {
+      return NULL;
+   }
+
+   for (subslot_ix = 0; subslot_ix < PNET_MAX_SUBSLOTS; subslot_ix++)
+   {
+      if (p_api->slots[slot_nbr].subslots[subslot_ix].subslot_nbr == subslot_nbr)
+      {
+         return &p_api->slots[slot_nbr].subslots[subslot_ix];
+      }
+   }
+
+   return NULL;
+}
+
+bool app_utils_subslot_is_input (const app_subslot_t * p_subslot)
+{
+   if (
+      p_subslot != NULL && (p_subslot->data_cfg.data_dir == PNET_DIR_INPUT ||
+                            p_subslot->data_cfg.data_dir == PNET_DIR_IO))
+   {
+      return true;
+   }
+
+   return false;
+}
+
+/**
+ * Return true if subslot is neither input or output.
+ *
+ * This is applies for DAP submodules/slots
+ * @param p_subslot     In: Reference to subslot.
+ * @return true if subslot is input or input/output.
+ *         false if not.
+ */
+bool app_utils_subslot_is_no_io (const app_subslot_t * p_subslot)
+{
+   if (p_subslot != NULL && p_subslot->data_cfg.data_dir == PNET_DIR_NO_IO)
+   {
+      return true;
+   }
+
+   return false;
+}
+
+/**
+ * Return true if subslot is output.
+ *
+ * @param p_subslot     In: Reference to subslot.
+ * @return true if subslot is output or input/output,
+ *         false if not.
+ */
+bool app_utils_subslot_is_output (const app_subslot_t * p_subslot)
+{
+   if (
+      p_subslot != NULL && (p_subslot->data_cfg.data_dir == PNET_DIR_OUTPUT ||
+                            p_subslot->data_cfg.data_dir == PNET_DIR_IO))
+   {
+      return true;
+   }
+
+   return false;
+}
+
+void app_utils_cyclic_data_poll (app_api_t * p_api)
+{
+   uint16_t slot;
+   uint16_t subslot;
+   app_subslot_t * p_subslot;
+
+   for (slot = 0; slot < PNET_MAX_SLOTS; slot++)
+   {
+      for (subslot = 0; subslot < PNET_MAX_SUBSLOTS; subslot++)
+      {
+         p_subslot = &p_api->slots[slot].subslots[subslot];
+         if (p_subslot->plugged && p_subslot->cyclic_callback != NULL)
+         {
+            p_subslot->cyclic_callback (p_subslot, p_subslot->tag);
+         }
+      }
+   }
+}

+ 382 - 0
samples/pn_dev_lan9662/app_utils.h

@@ -0,0 +1,382 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef APP_UTILS_H
+#define APP_UTILS_H
+
+/**
+ * @file
+ * @brief Application utilities and helper functions
+ *
+ * Functions for getting string representation of
+ * P-Net events, error codes and more.
+ *
+ * API, slot and subslot administration.
+ *
+ * Initialization of P-Net configuration from app_gsdml.h.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "osal.h"
+#include "pnal.h"
+#include <pnet_api.h>
+
+typedef struct app_utils_netif_name
+{
+   char name[PNET_INTERFACE_NAME_MAX_SIZE];
+} app_utils_netif_name_t;
+
+typedef struct app_utils_netif_namelist
+{
+   app_utils_netif_name_t netif[PNET_MAX_PHYSICAL_PORTS + 1];
+} app_utils_netif_namelist_t;
+
+typedef struct app_subslot app_subslot_t;
+typedef void (*app_utils_cyclic_callback) (app_subslot_t * subslot, void * tag);
+
+/**
+ * Information of submodule plugged into a subslot.
+ * Note that submodule data is not stored here but must
+ * be handled by the submodule implementation.
+ * All parameters are initialized by the app_utils_plug_submodule()
+ * function.
+ * The cyclic_callback is generated when app_utils_cyclic_data_poll()
+ * is called. Typically on the tick event in the main task.
+ * tag parameter is passed with the cyclic_callback and
+ * is typically a handle to a submodule on application.
+ */
+typedef struct app_subslot
+{
+   bool used;
+   bool plugged;
+   uint16_t slot_nbr;
+   uint16_t subslot_nbr;
+   uint32_t submodule_id;
+   const char * submodule_name;
+   pnet_data_cfg_t data_cfg;
+   uint8_t iocs;
+   uint8_t iops;
+
+   app_utils_cyclic_callback cyclic_callback;
+   void * tag;
+} app_subslot_t;
+
+/**
+ * Information of module plugged into
+ * slot and array of subslots for admin
+ * of submodules.
+ */
+typedef struct app_slot
+{
+   bool plugged;
+   uint32_t module_id;
+   const char * name;
+   app_subslot_t subslots[PNET_MAX_SUBSLOTS];
+} app_slot_t;
+
+/**
+ * Profinet API state
+ * Used to admin modules is plugged into
+ * slots and submodules into subslots.
+ */
+typedef struct app_api_t
+{
+   uint32_t api_id;
+   uint32_t arep;
+   app_slot_t slots[PNET_MAX_SLOTS];
+} app_api_t;
+
+/**
+ * Convert IP address to string
+ * @param ip               In:    IP address
+ * @param outputstring     Out:   Resulting string buffer. Should have size
+ *                                PNAL_INET_ADDRSTR_SIZE.
+ */
+void app_utils_ip_to_string (pnal_ipaddr_t ip, char * outputstring);
+
+/**
+ * Get string description of data direction
+ * @param direction               In:    Submodule data direction
+ * @return String representation of data direction
+ */
+const char * app_utils_submod_dir_to_string (pnet_submodule_dir_t direction);
+
+/**
+ * Get string description of pnio produced or consumer status
+ * @param ioxs               In:    Producer or consumer status (iops/iocs)
+ * @return String representation of ioxs (iops/iocs)
+ */
+const char * app_utils_ioxs_to_string (pnet_ioxs_values_t ioxs);
+
+/**
+ * Get string representation of application subslot in format
+ * [slot,subslot,module name]
+ * This function has an internal buffer for temp storage
+ * of the generated string.
+ * @param subslot            In: Subslot
+ * @return String representation of subslot.
+ */
+char * app_utils_get_subslot_string (const app_subslot_t * subslot);
+
+/**
+ * Convert MAC address to string
+ * @param mac              In:    MAC address
+ * @param outputstring     Out:   Resulting string buffer. Should have size
+ *                                PNAL_ETH_ADDRSTR_SIZE.
+ */
+void app_utils_mac_to_string (pnet_ethaddr_t mac, char * outputstring);
+
+/**
+ * Convert error code to string format
+ * Only common error codes supported.
+ * Todo: Add rest of error codes.
+ *
+ * @param err_cls        In:   The error class. See PNET_ERROR_CODE_1_*
+ * @param err_code       In:   The error code. See PNET_ERROR_CODE_2_*
+ * @param err_cls_str    Out:   The error class string
+ * @param err_code_str   Out:   The error code string
+ */
+void app_utils_get_error_code_strings (
+   uint16_t err_cls,
+   uint16_t err_code,
+   const char ** err_cls_str,
+   const char ** err_code_str);
+
+/**
+ * Copy an IP address (as an integer) to a struct
+ * @param destination_struct  Out:   Destination
+ * @param ip                  In:    IP address
+ */
+void app_utils_copy_ip_to_struct (
+   pnet_cfg_ip_addr_t * destination_struct,
+   pnal_ipaddr_t ip);
+
+/**
+ * Return a string representation of
+ * the given dcontrol command.
+ * @param event            In:    control_command
+ * @return  A string representing the command
+ */
+const char * app_utils_dcontrol_cmd_to_string (
+   pnet_control_command_t control_command);
+
+/**
+ * Return a string representation of the given event.
+ * @param event            In:    event
+ * @return  A string representing the event
+ */
+const char * app_utils_event_to_string (pnet_event_values_t event);
+
+/**
+ * Get network configuration from a string
+ * defining a list of network interfaces examples:
+ * "eth0" or "br0,eth0,eth1"
+ * @param netif_list_str      In: list of network ifs
+ * @param if_list             Out: Array of network ifs
+ * @param number_of_ports     Out: Number of ports
+ * @param if_cfg              Out: P-Net network configuration
+ * @return 0 on success, -1 on error
+ */
+int app_utils_pnet_cfg_init_netifs (
+   const char * netif_list_str,
+   app_utils_netif_namelist_t * if_list,
+   uint16_t * number_of_ports,
+   pnet_if_cfg_t * if_cfg);
+
+/**
+ * Parse a comma separated list of network interfaces and check
+ * that the number of interfaces match the PNET_MAX_PHYSICAL_PORTS
+ * configuration.
+ *
+ * For a single Ethernet interface, the \a arg_str should consist of
+ * one name. For two Ethernet interfaces, the  \a arg_str should consist of
+ * three names, as we also need a bridge interface.
+ *
+ * Does only consider the number of comma separated names. No check of the
+ * names themselves are done.
+ *
+ * Examples:
+ * arg_str                 num_ports
+ * "eth0"                  1
+ * "eth0,eth1"             error (We need a bridge as well)
+ * "br0,eth0,eth1"         2
+ *
+ * @param arg_str      In:   Network interface list as comma separated,
+ *                           terminated string. For example "eth0" or
+ *                           "br0,eth0,eth1".
+ * @param max_port     In:   PNET_MAX_PHYSICAL_PORTS, passed as argument to
+ *                           allow test.
+ * @param p_if_list    Out:  List of network interfaces
+ * @param p_num_ports  Out:  Resulting number of physical ports
+ * @return  0  on success
+ *         -1  on error
+ */
+int app_utils_get_netif_namelist (
+   const char * arg_str,
+   uint16_t max_port,
+   app_utils_netif_namelist_t * p_if_list,
+   uint16_t * p_num_ports);
+
+/**
+ * Print network configuration using APP_LOG_INFO().
+ * @param if_cfg           In:   Network configuration
+ * @param number_of_ports  In:   Number of used ports
+ */
+void app_utils_print_network_config (
+   pnet_if_cfg_t * if_cfg,
+   uint16_t number_of_ports);
+
+/**
+ * Print iocs warning message if ioxs is changed.
+ * @param subslot          In: Subslot
+ * @param ioxs_str         In: String description Producer or Consumer
+ * @param ioxs_current     In: Current status
+ * @param ioxs_new         In: New status
+ */
+void app_utils_print_ioxs_change (
+   app_subslot_t * subslot,
+   const char * ioxs_str,
+   uint8_t ioxs_current,
+   uint8_t ioxs_new);
+
+/**
+ * Init the p-net configuration to default values.
+ * Some values hardcoded, some picked from app_gsdml.h
+ * Network configuration not initialized.
+ * This means'.if_cfg' must be set by application.
+ * Use this function to init P-Net configuration before
+ * before passing config to app_init().
+ * @param pnet_cfg     Out:   Configuration for use by p-net
+ * @return  0  if the operation succeeded.
+ *          -1 if an error occurred.
+ */
+int app_utils_pnet_cfg_init_default (pnet_cfg_t * pnet_cfg);
+
+/**
+ * Plug application module.
+ * @param p_api            InOut: API
+ * @param slot_nbr         In:    Slot number
+ * @param module_id        In:    Module identity
+ * @return 0 on success, -1 on error
+ */
+int app_utils_plug_module (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint32_t id,
+   const char * name);
+
+/**
+ * Pull any application module in given slot.
+ * @param p_api            InOut: API
+ * @param slot_nbr         In:    Slot number
+ * @return 0 on success, -1 on error
+ */
+int app_utils_pull_module (app_api_t * p_api, uint16_t slot_nbr);
+
+/**
+ * Plug application submodule.
+ * @param p_api            InOut: API
+ * @param slot_nbr         In:    Slot number
+ * @param subslot_nbr      In:    Subslot number
+ * @param submodule_id     In:    Submodule identity
+ * @param p_data_cfg       In:    Data configuration,
+ *                                direction, in and out sizes
+ * @param submodule_name   In:    Submodule name
+ * @param cyclic_callback  In:    Submodule data callback
+ * @param tag              In:    Tag passed in cyclic callback
+ *                                Typically application or
+ *                                submodule handle
+ *
+ * @return Reference to allocated subslot,
+ *         NULL if no free subslot is available. This should
+ *         never happen if application is aligned with p-net state.
+ */
+app_subslot_t * app_utils_plug_submodule (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   uint32_t submodule_id,
+   const pnet_data_cfg_t * p_data_cfg,
+   const char * submodule_name,
+   app_utils_cyclic_callback cyclic_callback,
+   void * tag);
+
+/**
+ * Unplug any application submodule from given subslot.
+ * @param p_api            InOut: API
+ * @param slot_nbr         In:    Slot number
+ * @param subslot_nbr      In:    Subslot number
+ * @return 0 on success, -1 on error.
+ */
+int app_utils_pull_submodule (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr);
+
+/**
+ * Generate data callback for all plugged
+ * submodules. The callback passed in
+ * app_utils_plug_submodule is used.
+ * @param p_api         In:   API
+ */
+void app_utils_cyclic_data_poll (app_api_t * p_api);
+
+/**
+ * Get subslot application information.
+ *
+ * @param p_appdata        InOut: Application state.
+ * @param slot_nbr         In:    Slot number.
+ * @param subslot_nbr      In:    Subslot number. Range 0 - 0x9FFF.
+ * @return Reference to application subslot,
+ *         NULL if subslot is not found/plugged.
+ */
+app_subslot_t * app_utils_subslot_get (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr);
+
+/**
+ * Return true if subslot is input.
+ * @param p_subslot        In:    Reference to subslot.
+ * @return true if subslot is input or input/output.
+ *         false if not.
+ */
+bool app_utils_subslot_is_input (const app_subslot_t * p_subslot);
+
+/**
+ * Return true if subslot is neither input or output.
+ * This is applies for DAP submodules/slots
+ * @param p_subslot     In: Reference to subslot.
+ * @return true if subslot is input or input/output.
+ *         false if not.
+ */
+bool app_utils_subslot_is_no_io (const app_subslot_t * p_subslot);
+
+/**
+ * Return true if subslot is output.
+ * @param p_subslot     In: Reference to subslot.
+ * @return true if subslot is output or input/output,
+ *         false if not.
+ */
+bool app_utils_subslot_is_output (const app_subslot_t * p_subslot);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_UTILS_H */

+ 1235 - 0
samples/pn_dev_lan9662/sampleapp_common.c

@@ -0,0 +1,1235 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#include "sampleapp_common.h"
+#include "app_utils.h"
+#include "app_gsdml.h"
+#include "app_data.h"
+#include "app_log.h"
+#include "osal.h"
+#include "pnal.h"
+#include <pnet_api.h>
+#include "pnet_lan9662_api.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Events handled by main task */
+#define APP_EVENT_READY_FOR_DATA BIT (0)
+#define APP_EVENT_TIMER          BIT (1)
+#define APP_EVENT_ALARM          BIT (2)
+#define APP_EVENT_ABORT          BIT (15)
+
+/* Defines used for alarm demo functionality */
+#define CHANNEL_ERRORTYPE_SHORT_CIRCUIT                       0x0001
+#define CHANNEL_ERRORTYPE_LINE_BREAK                          0x0006
+#define CHANNEL_ERRORTYPE_DATA_TRANSMISSION_IMPOSSIBLE        0x8000
+#define CHANNEL_ERRORTYPE_NETWORK_COMPONENT_FUNCTION_MISMATCH 0x8008
+#define EXTENDED_CHANNEL_ERRORTYPE_FRAME_DROPPED              0x8000
+#define EXTENDED_CHANNEL_ERRORTYPE_MAUTYPE_MISMATCH           0x8001
+#define EXTENDED_CHANNEL_ERRORTYPE_LINE_DELAY_MISMATCH        0x8002
+
+#define APP_ALARM_USI                       0x0010
+#define APP_DIAG_CHANNEL_NUMBER             4
+#define APP_DIAG_CHANNEL_DIRECTION          PNET_DIAG_CH_PROP_DIR_INPUT
+#define APP_DIAG_CHANNEL_NUMBER_OF_BITS     PNET_DIAG_CH_PROP_TYPE_1_BIT
+#define APP_DIAG_CHANNEL_SEVERITY           PNET_DIAG_CH_PROP_MAINT_FAULT
+#define APP_DIAG_CHANNEL_ERRORTYPE          CHANNEL_ERRORTYPE_SHORT_CIRCUIT
+#define APP_DIAG_CHANNEL_ADDVALUE_A         0
+#define APP_DIAG_CHANNEL_ADDVALUE_B         1234
+#define APP_DIAG_CHANNEL_EXTENDED_ERRORTYPE 0
+#define APP_DIAG_CHANNEL_QUAL_SEVERITY      0 /* Not used (Max one bit set) */
+
+typedef struct app_data_t
+{
+   pnet_t * net;
+
+   /* P-Net configuration passed in app_init(). */
+   pnet_cfg_t * pnet_cfg;
+
+   /* Application api for administration of plugged
+    *(sub)modules and connection state.
+    */
+   app_api_t main_api;
+
+   os_timer_t * main_timer;
+   os_event_t * main_events;
+
+   bool alarm_allowed;
+   pnet_alarm_argument_t alarm_arg;
+   uint8_t alarm_payload[APP_GSDML_ALARM_PAYLOAD_SIZE];
+
+   uint32_t arep_for_appl_ready;
+   uint32_t appl_ready_delay_count;
+   bool appl_ready_wait;
+
+   uint32_t process_data_tick_counter;
+
+   app_mode_t mode;
+
+} app_data_t;
+
+static void app_plug_dap (app_data_t * app, uint16_t number_of_ports);
+static int app_set_initial_data_and_ioxs (app_data_t * app);
+static void app_cyclic_data_callback (app_subslot_t * subslot, void * tag);
+
+app_data_t app_state;
+
+pnet_t * app_get_pnet_instance (app_data_t * app)
+{
+   if (app != NULL)
+   {
+      return app->net;
+   }
+   return NULL;
+}
+
+app_data_t * app_init (pnet_cfg_t * pnet_cfg, const app_args_t * app_args)
+{
+   app_data_t * app = &app_state;
+
+   app->alarm_allowed = true;
+   app->main_api.arep = UINT32_MAX;
+   app->pnet_cfg = pnet_cfg;
+
+#if PNET_OPTION_DRIVER_LAN9662
+   app->mode = app_args->mode;
+   switch (app->mode)
+   {
+   case MODE_HW_OFFLOAD_CPU:
+      APP_LOG_INFO ("Application RTE mode \"cpu\"\n");
+      pnet_cfg->driver_enable = true;
+      break;
+   case MODE_HW_OFFLOAD_FULL:
+      APP_LOG_INFO ("Application RTE mode \"full\"\n");
+      pnet_cfg->driver_enable = true;
+      break;
+   case MODE_HW_OFFLOAD_NONE:
+      APP_LOG_INFO ("Application RTE mode \"none\"\n");
+      pnet_cfg->driver_enable = false;
+      break;
+   default:
+      APP_LOG_ERROR ("Application mode undefined\n");
+      pnet_cfg->driver_enable = false;
+      break;
+   }
+#else
+   app->mode = MODE_HW_OFFLOAD_NONE;
+#endif
+
+   app_data_init (app->mode);
+
+   app->net = pnet_init (app->pnet_cfg);
+
+   if (app->net == NULL)
+   {
+      return NULL;
+   }
+
+   return app;
+}
+
+static void main_timer_tick (os_timer_t * timer, void * arg)
+{
+   app_data_t * app = (app_data_t *)arg;
+
+   os_event_set (app->main_events, APP_EVENT_TIMER);
+}
+
+int app_start (app_data_t * app, app_run_in_separate_task_t task_config)
+{
+   if (app == NULL)
+   {
+      return -1;
+   }
+
+   app->main_events = os_event_create();
+   if (app->main_events == NULL)
+   {
+      return -1;
+   }
+
+   app->main_timer = os_timer_create (
+      APP_TICK_INTERVAL_US,
+      main_timer_tick,
+      (void *)app,
+      false);
+
+   if (app->main_timer == NULL)
+   {
+      os_event_destroy (app->main_events);
+      return -1;
+   }
+
+   if (task_config == RUN_IN_SEPARATE_THREAD)
+   {
+      os_thread_create (
+         "p-net_sample_app",
+         APP_MAIN_THREAD_PRIORITY,
+         APP_MAIN_THREAD_STACKSIZE,
+         app_loop_forever,
+         (void *)app);
+   }
+
+   os_timer_start (app->main_timer);
+
+   return 0;
+}
+
+static void app_set_outputs_default_value (void)
+{
+   // APP_LOG_DEBUG ("Setting outputs to default values.\n");
+   app_data_set_default_outputs();
+}
+
+/*********************************** Callbacks ********************************/
+
+static int app_connect_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   pnet_result_t * p_result)
+{
+   APP_LOG_DEBUG ("PLC connect indication. AREP: %u\n", arep);
+   /*
+    *  Handle the request on an application level.
+    *  This is a very simple application which does not need to handle anything.
+    *  All the needed information is in the AR data structure.
+    */
+
+   return 0;
+}
+
+static int app_release_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   pnet_result_t * p_result)
+{
+   APP_LOG_DEBUG ("PLC release (disconnect) indication. AREP: %u\n", arep);
+
+   app_set_outputs_default_value();
+
+   return 0;
+}
+
+static int app_dcontrol_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   pnet_control_command_t control_command,
+   pnet_result_t * p_result)
+{
+   APP_LOG_DEBUG (
+      "PLC dcontrol message. AREP: %u  Command: %s\n",
+      arep,
+      app_utils_dcontrol_cmd_to_string (control_command));
+
+   return 0;
+}
+
+static int app_ccontrol_cnf (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   pnet_result_t * p_result)
+{
+   APP_LOG_DEBUG (
+      "PLC ccontrol message confirmation. AREP: %u  Status codes: %d "
+      "%d %d %d\n",
+      arep,
+      p_result->pnio_status.error_code,
+      p_result->pnio_status.error_decode,
+      p_result->pnio_status.error_code_1,
+      p_result->pnio_status.error_code_2);
+
+   return 0;
+}
+
+static int app_write_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   uint32_t api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   uint16_t idx,
+   uint16_t sequence_number,
+   uint16_t write_length,
+   const uint8_t * p_write_data,
+   pnet_result_t * p_result)
+{
+   APP_LOG_DEBUG (
+      "PLC write record indication.\n"
+      "  AREP: %u API: %u Slot: %2u Subslot: %u Index: %u Sequence: %2u "
+      "Length: %u\n",
+      arep,
+      api,
+      slot_nbr,
+      subslot_nbr,
+      (unsigned)idx,
+      sequence_number,
+      write_length);
+
+   return 0;
+}
+
+static int app_read_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   uint32_t api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   uint16_t idx,
+   uint16_t sequence_number,
+   uint8_t ** pp_read_data,
+   uint16_t * p_read_length,
+   pnet_result_t * p_result)
+{
+   APP_LOG_DEBUG (
+      "PLC read record indication.\n"
+      "  AREP: %u API: %u Slot: %2u Subslot: %u Index: %u Sequence: %2u Max "
+      "length: %u\n",
+      arep,
+      api,
+      slot_nbr,
+      subslot_nbr,
+      (unsigned)idx,
+      sequence_number,
+      (unsigned)*p_read_length);
+
+   return 0;
+}
+
+static int app_state_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   pnet_event_values_t state)
+{
+   uint16_t err_cls = 0;  /* Error code 1 */
+   uint16_t err_code = 0; /* Error code 2 */
+   const char * error_class_description = "";
+   const char * error_code_description = "";
+
+   app_data_t * app = (app_data_t *)arg;
+
+   APP_LOG_DEBUG (
+      "Event indication %s   AREP: %u\n",
+      app_utils_event_to_string (state),
+      arep);
+
+   if (state == PNET_EVENT_ABORT)
+   {
+      if (pnet_get_ar_error_codes (net, arep, &err_cls, &err_code) == 0)
+      {
+         app_utils_get_error_code_strings (
+            err_cls,
+            err_code,
+            &error_class_description,
+            &error_code_description);
+         APP_LOG_DEBUG (
+            "    Error class: 0x%02x %s \n"
+            "    Error code:  0x%02x %s \n",
+            (unsigned)err_cls,
+            error_class_description,
+            (unsigned)err_code,
+            error_code_description);
+      }
+      else
+      {
+         APP_LOG_DEBUG ("    No error status available\n");
+      }
+      /* Set output values */
+      app_set_outputs_default_value();
+
+      /* Only abort AR with correct session key */
+      os_event_set (app->main_events, APP_EVENT_ABORT);
+   }
+   else if (state == PNET_EVENT_PRMEND)
+   {
+      if (app->main_api.arep != UINT32_MAX)
+      {
+         APP_LOG_WARNING ("Warning - AREP out of sync\n");
+      }
+      app->main_api.arep = arep;
+
+      app_set_initial_data_and_ioxs (app);
+
+      (void)pnet_set_provider_state (net, true);
+
+      /* Send application ready at next tick
+         Do not call pnet_application_ready() here as it will affect
+         the internal stack states */
+      app->arep_for_appl_ready = arep;
+      os_event_set (app->main_events, APP_EVENT_READY_FOR_DATA);
+   }
+   else if (state == PNET_EVENT_DATA)
+   {
+      APP_LOG_DEBUG ("Cyclic data transmission started\n\n");
+   }
+
+   return 0;
+}
+
+static int app_reset_ind (
+   pnet_t * net,
+   void * arg,
+   bool should_reset_application,
+   uint16_t reset_mode)
+{
+   APP_LOG_DEBUG (
+      "PLC reset indication. Application reset mandatory: %u  Reset mode: %d\n",
+      should_reset_application,
+      reset_mode);
+
+   return 0;
+}
+
+static int app_signal_led_ind (pnet_t * net, void * arg, bool led_state)
+{
+   APP_LOG_INFO ("Profinet signal LED indication. New state: %u\n", led_state);
+
+   app_set_led (APP_PROFINET_SIGNAL_LED_ID, led_state);
+   return 0;
+}
+
+static int app_exp_module_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t api,
+   uint16_t slot,
+   uint32_t module_ident)
+{
+   int ret = -1;
+   int result = 0;
+   app_data_t * app = (app_data_t *)arg;
+   const char * module_name = "unknown";
+   const app_gsdml_module_t * module_config;
+
+   APP_LOG_DEBUG ("Module plug indication API %d\n");
+
+   if (slot >= PNET_MAX_SLOTS)
+   {
+      APP_LOG_ERROR (
+         "Unsupported slot number: %u  It should be less than %u\n",
+         slot,
+         PNET_MAX_SLOTS);
+      return -1;
+   }
+
+   module_config = app_gsdml_get_module_cfg (module_ident);
+   if (module_config == NULL)
+   {
+      APP_LOG_ERROR ("  Module ID %08x not found.\n", (unsigned)module_ident);
+      /*
+       * Needed to pass Behavior scenario 2
+       */
+      APP_LOG_DEBUG ("  Plug expected module anyway\n");
+   }
+   else
+   {
+      module_name = module_config->name;
+   }
+
+   APP_LOG_DEBUG ("  [%u] Pull old module\n", slot);
+   result = pnet_pull_module (net, api, slot);
+
+   if (result == 0)
+   {
+      app_utils_pull_module (&app->main_api, slot);
+   }
+
+   APP_LOG_DEBUG (
+      "  [%u] Plug module. Module ID: 0x%x \"%s\"\n",
+      slot,
+      (unsigned)module_ident,
+      module_name);
+
+   ret = pnet_plug_module (net, api, slot, module_ident);
+   if (ret == 0)
+   {
+      app_utils_plug_module (&app->main_api, slot, module_ident, module_name);
+   }
+   else
+   {
+      APP_LOG_ERROR (
+         "  [%u] Plug module failed. Ret: %u Module ID: 0x%x \" \"\n",
+         slot,
+         ret,
+         (unsigned)module_ident,
+         module_name);
+   }
+
+   return ret;
+}
+
+static int app_exp_submodule_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t api,
+   uint16_t slot,
+   uint16_t subslot,
+   uint32_t module_id,
+   uint32_t submodule_id,
+   const pnet_data_cfg_t * p_exp_data)
+{
+   int ret = -1;
+   int result = 0;
+   pnet_data_cfg_t data_cfg = {0};
+   app_data_t * app = (app_data_t *)arg;
+   const app_gsdml_submodule_t * submod_cfg;
+   const char * name = "Unsupported";
+   app_utils_cyclic_callback cyclic_data_callback = NULL;
+   app_subslot_t * p_subslot;
+
+   APP_LOG_DEBUG ("Submodule plug indication API %u\n", api);
+
+   submod_cfg = app_gsdml_get_submodule_cfg (submodule_id);
+   if (submod_cfg != NULL)
+   {
+      data_cfg.data_dir = submod_cfg->data_dir;
+      data_cfg.insize = submod_cfg->insize;
+      data_cfg.outsize = submod_cfg->outsize;
+      name = submod_cfg->name;
+
+      if (data_cfg.insize > 0 || data_cfg.outsize > 0)
+      {
+         cyclic_data_callback = app_cyclic_data_callback;
+      }
+   }
+   else
+   {
+      APP_LOG_WARNING (
+         "  [%u,%u] Submodule ID 0x%x in Module ID 0x%x not found \n",
+         slot,
+         subslot,
+         (unsigned)submodule_id,
+         (unsigned)module_id);
+
+      /*
+       * Needed for behavior scenario 2 to pass.
+       * Iops will be set to bad for this subslot
+       */
+      APP_LOG_WARNING (
+         "  [%u,%u] Plug expected submodule anyway\n",
+         slot,
+         subslot);
+
+      data_cfg.data_dir = p_exp_data->data_dir;
+      data_cfg.insize = p_exp_data->insize;
+      data_cfg.outsize = p_exp_data->outsize;
+   }
+
+   APP_LOG_DEBUG ("  [%u,%u] Pull old submodule.\n", slot, subslot);
+
+   result = pnet_pull_submodule (net, api, slot, subslot);
+   if (result == 0)
+   {
+      app_utils_pull_submodule (&app->main_api, slot, subslot);
+   }
+
+   APP_LOG_DEBUG (
+      "  [%u,%u] Plug submodule. Submodule ID: 0x%x Data Dir: %s In: %u Out: "
+      "%u \"%s\"\n",
+      slot,
+      subslot,
+      (unsigned)submodule_id,
+      app_utils_submod_dir_to_string (data_cfg.data_dir),
+      data_cfg.insize,
+      data_cfg.outsize,
+      name);
+
+   if (
+      data_cfg.data_dir != p_exp_data->data_dir ||
+      data_cfg.insize != p_exp_data->insize ||
+      data_cfg.outsize != p_exp_data->outsize)
+   {
+      APP_LOG_WARNING (
+         "  [%u,%u] Warning expected Data Dir: %s In: %u Out: %u\n",
+         slot,
+         subslot,
+         app_utils_submod_dir_to_string (p_exp_data->data_dir),
+         p_exp_data->insize,
+         p_exp_data->outsize);
+   }
+   ret = pnet_plug_submodule (
+      net,
+      api,
+      slot,
+      subslot,
+      module_id,
+      submodule_id,
+      data_cfg.data_dir,
+      data_cfg.insize,
+      data_cfg.outsize);
+
+   if (ret == 0)
+   {
+      p_subslot = app_utils_plug_submodule (
+         &app->main_api,
+         slot,
+         subslot,
+         submodule_id,
+         &data_cfg,
+         name,
+         cyclic_data_callback,
+         app);
+
+      /**
+       * Setup the RTE configuration for submodules data provided by the FPGA.
+       */
+
+      if (app->mode == MODE_HW_OFFLOAD_FULL)
+      {
+         const uint8_t * default_data;
+         uint16_t fpga_address;
+
+         if (
+            app_data_get_fpga_info_by_id (
+               submodule_id,
+               &fpga_address,
+               &default_data) == 0)
+         {
+            pnet_mera_rte_data_cfg_t rte_cfg = {
+               .type = PNET_MERA_DATA_TYPE_QSPI,
+               .address = fpga_address,
+               .default_data = default_data};
+
+            APP_LOG_INFO (
+               "  %-40s Set RTE QSPI address 0x%x\n",
+               app_utils_get_subslot_string (p_subslot),
+               rte_cfg.address);
+
+            if (data_cfg.insize > 0)
+            {
+               if (
+                  pnet_mera_input_set_rte_config (
+                     app->net,
+                     APP_GSDML_API,
+                     slot,
+                     subslot,
+                     &rte_cfg) != 0)
+               {
+                  APP_LOG_ERROR (
+                     "  %-40s RTE configuration failed\n",
+                     app_utils_get_subslot_string (p_subslot));
+               }
+            }
+            else
+            {
+               if (
+                  pnet_mera_output_set_rte_config (
+                     app->net,
+                     APP_GSDML_API,
+                     slot,
+                     subslot,
+                     &rte_cfg) != 0)
+               {
+                  APP_LOG_ERROR (
+                     "  %-40s RTE configuration failed\n",
+                     app_utils_get_subslot_string (p_subslot));
+               }
+            }
+         }
+      }
+   }
+   else
+   {
+      APP_LOG_ERROR (
+         "  [%u,%u] Plug submodule failed. Ret: %u\n",
+         slot,
+         subslot,
+         ret);
+   }
+
+   return ret;
+}
+
+static int app_new_data_status_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   uint32_t crep,
+   uint8_t changes,
+   uint8_t data_status)
+{
+   bool is_running = data_status & BIT (PNET_DATA_STATUS_BIT_PROVIDER_STATE);
+   bool is_valid = data_status & BIT (PNET_DATA_STATUS_BIT_DATA_VALID);
+
+   APP_LOG_DEBUG (
+      "Data status indication. AREP: %u  Data status changes: 0x%02x  "
+      "Data status: 0x%02x\n",
+      arep,
+      changes,
+      data_status);
+   APP_LOG_DEBUG (
+      "   %s, %s, %s, %s, %s\n",
+      is_running ? "Run" : "Stop",
+      is_valid ? "Valid" : "Invalid",
+      (data_status & BIT (PNET_DATA_STATUS_BIT_STATE)) ? "Primary" : "Backup",
+      (data_status & BIT (PNET_DATA_STATUS_BIT_STATION_PROBLEM_INDICATOR))
+         ? "Normal operation"
+         : "Problem",
+      (data_status & BIT (PNET_DATA_STATUS_BIT_IGNORE))
+         ? "Ignore data status"
+         : "Evaluate data status");
+
+   if (is_running == false || is_valid == false)
+   {
+      app_set_outputs_default_value();
+   }
+
+   return 0;
+}
+
+static int app_alarm_ind (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   const pnet_alarm_argument_t * p_alarm_arg,
+   uint16_t data_len,
+   uint16_t data_usi,
+   const uint8_t * p_data)
+{
+   app_data_t * app = (app_data_t *)arg;
+
+   APP_LOG_DEBUG (
+      "Alarm indication. AREP: %u API: %d Slot: %d Subslot: %d "
+      "Type: %d Seq: %d Length: %d USI: %d\n",
+      arep,
+      p_alarm_arg->api_id,
+      p_alarm_arg->slot_nbr,
+      p_alarm_arg->subslot_nbr,
+      p_alarm_arg->alarm_type,
+      p_alarm_arg->sequence_number,
+      data_len,
+      data_usi);
+
+   app->alarm_arg = *p_alarm_arg;
+   os_event_set (app->main_events, APP_EVENT_ALARM);
+
+   return 0;
+}
+
+static int app_alarm_cnf (
+   pnet_t * net,
+   void * arg,
+   uint32_t arep,
+   const pnet_pnio_status_t * p_pnio_status)
+{
+   app_data_t * app = (app_data_t *)arg;
+
+   APP_LOG_DEBUG (
+      "PLC alarm confirmation. AREP: %u  Status code %u, "
+      "%u, %u, %u\n",
+      arep,
+      p_pnio_status->error_code,
+      p_pnio_status->error_decode,
+      p_pnio_status->error_code_1,
+      p_pnio_status->error_code_2);
+
+   app->alarm_allowed = true;
+
+   return 0;
+}
+
+static int app_alarm_ack_cnf (pnet_t * net, void * arg, uint32_t arep, int res)
+{
+   APP_LOG_DEBUG (
+      "PLC alarm ACK confirmation. AREP: %u  Result: "
+      "%d\n",
+      arep,
+      res);
+
+   return 0;
+}
+
+/**
+ * Plug all DAP (sub)modules
+ * Use existing callback functions to plug the (sub-)modules
+ * @param app              InOut:   Application handle
+ * @param number_of_ports  In:      Number of active ports
+ */
+static void app_plug_dap (app_data_t * app, uint16_t number_of_ports)
+{
+   const pnet_data_cfg_t cfg_dap_data = {
+      .data_dir = PNET_DIR_NO_IO,
+      .insize = 0,
+      .outsize = 0,
+   };
+
+   APP_LOG_DEBUG ("Plug DAP module and its submodules\n");
+
+   app_exp_module_ind (
+      app->net,
+      app,
+      APP_GSDML_API,
+      PNET_SLOT_DAP_IDENT,
+      PNET_MOD_DAP_IDENT);
+
+   app_exp_submodule_ind (
+      app->net,
+      app,
+      APP_GSDML_API,
+      PNET_SLOT_DAP_IDENT,
+      PNET_SUBSLOT_DAP_IDENT,
+      PNET_MOD_DAP_IDENT,
+      PNET_SUBMOD_DAP_IDENT,
+      &cfg_dap_data);
+
+   app_exp_submodule_ind (
+      app->net,
+      app,
+      APP_GSDML_API,
+      PNET_SLOT_DAP_IDENT,
+      PNET_SUBSLOT_DAP_INTERFACE_1_IDENT,
+      PNET_MOD_DAP_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_IDENT,
+      &cfg_dap_data);
+
+   app_exp_submodule_ind (
+      app->net,
+      app,
+      APP_GSDML_API,
+      PNET_SLOT_DAP_IDENT,
+      PNET_SUBSLOT_DAP_INTERFACE_1_PORT_1_IDENT,
+      PNET_MOD_DAP_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_PORT_1_IDENT,
+      &cfg_dap_data);
+
+   if (number_of_ports >= 2)
+   {
+      app_exp_submodule_ind (
+         app->net,
+         app,
+         APP_GSDML_API,
+         PNET_SLOT_DAP_IDENT,
+         PNET_SUBSLOT_DAP_INTERFACE_1_PORT_2_IDENT,
+         PNET_MOD_DAP_IDENT,
+         PNET_SUBMOD_DAP_INTERFACE_1_PORT_2_IDENT,
+         &cfg_dap_data);
+   }
+
+   if (number_of_ports >= 3)
+   {
+      app_exp_submodule_ind (
+         app->net,
+         app,
+         APP_GSDML_API,
+         PNET_SLOT_DAP_IDENT,
+         PNET_SUBSLOT_DAP_INTERFACE_1_PORT_3_IDENT,
+         PNET_MOD_DAP_IDENT,
+         PNET_SUBMOD_DAP_INTERFACE_1_PORT_3_IDENT,
+         &cfg_dap_data);
+   }
+
+   if (number_of_ports >= 4)
+   {
+      app_exp_submodule_ind (
+         app->net,
+         app,
+         APP_GSDML_API,
+         PNET_SLOT_DAP_IDENT,
+         PNET_SUBSLOT_DAP_INTERFACE_1_PORT_4_IDENT,
+         PNET_MOD_DAP_IDENT,
+         PNET_SUBMOD_DAP_INTERFACE_1_PORT_4_IDENT,
+         &cfg_dap_data);
+   }
+}
+
+/**
+ * Send application ready to the PLC
+ * @param net              InOut: p-net stack instance
+ * @param arep             In:    Arep
+ */
+static void app_handle_send_application_ready (pnet_t * net, uint32_t arep)
+{
+   int ret = -1;
+
+   APP_LOG_DEBUG (
+      "Application will signal that it is ready for data, for "
+      "AREP %u.\n",
+      arep);
+
+   ret = pnet_application_ready (net, arep);
+   if (ret != 0)
+   {
+      APP_LOG_ERROR (
+         "Error returned when application telling that it is ready for "
+         "data. Have you set IOCS or IOPS for all subslots?\n");
+   }
+
+   /* When the PLC sends a confirmation to this message, the
+      pnet_ccontrol_cnf() callback will be triggered.  */
+}
+
+/**
+ * Send alarm ACK to the PLC
+ *
+ * @param net              InOut: p-net stack instance
+ * @param arep             In:    Arep
+ * @param p_alarm_arg      In:    Alarm argument (slot, subslot etc)
+ */
+static void app_handle_send_alarm_ack (
+   pnet_t * net,
+   uint32_t arep,
+   const pnet_alarm_argument_t * p_alarm_arg)
+{
+   pnet_pnio_status_t pnio_status = {0, 0, 0, 0};
+   int ret;
+
+   ret = pnet_alarm_send_ack (net, arep, p_alarm_arg, &pnio_status);
+   if (ret != 0)
+   {
+      APP_LOG_DEBUG ("Error when sending alarm ACK. Error: %d\n", ret);
+   }
+   else
+   {
+      APP_LOG_DEBUG ("Alarm ACK sent\n");
+   }
+}
+
+/**
+ * Handle cyclic data for all plugged submodules
+ * Data is read from / written to the app_data file
+ * which handles the data and update the physical
+ * input /outputs.
+ *
+ * For subslots with mapped shared memory inputs, the
+ * shared memory is read and the data is passed to the
+ * stack.
+ *
+ * For subslots with mapped shared memory outputs, the
+ * current output data is fetched from the stack and written to
+ * mapped shared memory.
+ *
+ * @param subslot    In:   Subslot reference
+ * @param tag        In:   Application handle
+ */
+static void app_cyclic_data_callback (app_subslot_t * subslot, void * tag)
+{
+   app_data_t * app = (app_data_t *)tag;
+   uint8_t iops = PNET_IOXS_BAD;
+   uint8_t iocs = PNET_IOXS_BAD;
+   uint8_t * indata;
+   uint16_t indata_size;
+   bool outdata_updated;
+   uint16_t outdata_length;
+   uint8_t outdata_iops;
+   uint8_t * outdata;
+
+   if (app == NULL)
+   {
+      APP_LOG_ERROR ("Application tag not set in subslot?\n");
+      return;
+   }
+
+   if (subslot->data_cfg.insize > 0)
+   {
+      indata =
+         app_data_get_input_data (subslot->submodule_id, &indata_size, &iops);
+
+      if (indata != NULL)
+      {
+         iops = PNET_IOXS_GOOD;
+      }
+
+      (void)pnet_input_set_data_and_iops (
+         app->net,
+         APP_GSDML_API,
+         subslot->slot_nbr,
+         subslot->subslot_nbr,
+         indata,
+         indata_size,
+         iops);
+
+      (void)pnet_input_get_iocs (
+         app->net,
+         APP_GSDML_API,
+         subslot->slot_nbr,
+         subslot->subslot_nbr,
+         &iocs);
+
+      app_utils_print_ioxs_change (
+         subslot,
+         "Consumer Status (IOCS)",
+         subslot->iocs,
+         iocs);
+      subslot->iocs = iocs;
+   }
+
+   if (subslot->data_cfg.outsize > 0)
+   {
+      outdata = app_data_get_output_data_buffer (
+         subslot->submodule_id,
+         &outdata_length);
+
+      pnet_output_get_data_and_iops (
+         app->net,
+         APP_GSDML_API,
+         subslot->slot_nbr,
+         subslot->subslot_nbr,
+         &outdata_updated,
+         outdata,
+         &outdata_length,
+         &outdata_iops);
+
+      app_utils_print_ioxs_change (
+         subslot,
+         "Provider Status (IOPS)",
+         subslot->iops,
+         outdata_iops);
+      subslot->iops = outdata_iops;
+
+      if (outdata_length != subslot->data_cfg.outsize)
+      {
+         APP_LOG_ERROR (
+            "  %-40s Wrong outputdata length %u (%u)\n",
+            app_utils_get_subslot_string (subslot),
+            outdata_length,
+            subslot->data_cfg.outsize);
+         app_set_outputs_default_value();
+      }
+      else if (outdata_iops == PNET_IOXS_GOOD)
+      {
+         /* Set output data for submodule */
+         app_data_set_output_data (
+            subslot->submodule_id,
+            outdata,
+            outdata_length);
+      }
+      else
+      {
+         app_set_outputs_default_value();
+      }
+   }
+}
+
+/**
+ * Set initial input data, provider and consumer status for a subslot.
+ *
+ * @param app              In:    Application handle
+ */
+static int app_set_initial_data_and_ioxs (app_data_t * app)
+{
+   int ret;
+   uint8_t iops;
+   uint16_t slot;
+   uint16_t subslot;
+   const app_subslot_t * p_subslot;
+   uint8_t * indata;
+   uint16_t indata_size;
+
+   for (slot = 0; slot < PNET_MAX_SLOTS; slot++)
+   {
+      for (subslot = 0; subslot < PNET_MAX_SUBSLOTS; subslot++)
+      {
+         p_subslot = &app->main_api.slots[slot].subslots[subslot];
+         if (p_subslot->plugged)
+         {
+            iops = PNET_IOXS_GOOD;
+            if (
+               p_subslot->data_cfg.insize > 0 ||
+               p_subslot->data_cfg.data_dir == PNET_DIR_NO_IO)
+            {
+               indata = NULL;
+               indata_size = p_subslot->data_cfg.insize;
+
+               if (p_subslot->data_cfg.insize > 0)
+               {
+                  indata = app_data_get_input_data (
+                     p_subslot->submodule_id,
+                     &indata_size,
+                     &iops);
+               }
+
+               ret = pnet_input_set_data_and_iops (
+                  app->net,
+                  APP_GSDML_API,
+                  p_subslot->slot_nbr,
+                  p_subslot->subslot_nbr,
+                  indata,
+                  indata_size,
+                  iops);
+
+               /*
+                * If a submodule is still plugged but not used in current
+                * AR setting the data and iops will fail. This is not a
+                * problem. Log message below will only be printed for
+                * active submodules.
+                */
+               if (ret == 0)
+               {
+                  APP_LOG_DEBUG (
+                     "  %-40s Set input data and IOPS. Size: %d "
+                     "IOPS: %s\n",
+                     app_utils_get_subslot_string (p_subslot),
+                     p_subslot->data_cfg.insize,
+                     app_utils_ioxs_to_string (iops));
+               }
+            }
+
+            if (p_subslot->data_cfg.outsize > 0)
+            {
+               ret = pnet_output_set_iocs (
+                  app->net,
+                  APP_GSDML_API,
+                  p_subslot->slot_nbr,
+                  p_subslot->subslot_nbr,
+                  PNET_IOXS_GOOD);
+
+               if (ret == 0)
+               {
+                  APP_LOG_DEBUG (
+                     "  %-40s Set output IOCS: %s\n",
+                     app_utils_get_subslot_string (p_subslot),
+                     app_utils_ioxs_to_string (PNET_IOXS_GOOD));
+               }
+            }
+         }
+      }
+   }
+   return 0;
+}
+
+/**
+ * Send and receive cyclic / process data
+ *
+ * Generate callback for all plugged submodules
+ * the sample application handles all submodules
+ * in the app_cyclic_data_callback() function.
+ * A complex device may register separate callbacks
+ * for each submodule.
+ *
+ * @param app        In:    Application handle
+ */
+static void app_handle_cyclic_data (app_data_t * app)
+{
+   /* For the sample application cyclic data is updated
+    * with a period defined by APP_TICKS_UPDATE_DATA
+    */
+   app->process_data_tick_counter++;
+   if (app->process_data_tick_counter < APP_TICKS_UPDATE_DATA)
+   {
+      return;
+   }
+   app->process_data_tick_counter = 0;
+
+   app_utils_cyclic_data_poll (&app->main_api);
+}
+
+void app_pnet_cfg_init_default (pnet_cfg_t * pnet_cfg)
+{
+   app_utils_pnet_cfg_init_default (pnet_cfg);
+
+   pnet_cfg->state_cb = app_state_ind;
+   pnet_cfg->connect_cb = app_connect_ind;
+   pnet_cfg->release_cb = app_release_ind;
+   pnet_cfg->dcontrol_cb = app_dcontrol_ind;
+   pnet_cfg->ccontrol_cb = app_ccontrol_cnf;
+   pnet_cfg->read_cb = app_read_ind;
+   pnet_cfg->write_cb = app_write_ind;
+   pnet_cfg->exp_module_cb = app_exp_module_ind;
+   pnet_cfg->exp_submodule_cb = app_exp_submodule_ind;
+   pnet_cfg->new_data_status_cb = app_new_data_status_ind;
+   pnet_cfg->alarm_ind_cb = app_alarm_ind;
+   pnet_cfg->alarm_cnf_cb = app_alarm_cnf;
+   pnet_cfg->alarm_ack_cnf_cb = app_alarm_ack_cnf;
+   pnet_cfg->reset_cb = app_reset_ind;
+   pnet_cfg->signal_led_cb = app_signal_led_ind;
+
+   pnet_cfg->cb_arg = (void *)&app_state;
+}
+
+void app_loop_forever (void * arg)
+{
+   app_data_t * app = (app_data_t *)arg;
+   uint32_t mask = APP_EVENT_READY_FOR_DATA | APP_EVENT_TIMER |
+                   APP_EVENT_ALARM | APP_EVENT_ABORT;
+   uint32_t flags = 0;
+
+   app->main_api.arep = UINT32_MAX;
+
+   app_set_led (APP_DATA_LED_ID, false);
+   app_plug_dap (app, app->pnet_cfg->num_physical_ports);
+   APP_LOG_INFO ("Waiting for PLC connect request\n\n");
+
+   /* Main event loop */
+   for (;;)
+   {
+      os_event_wait (app->main_events, mask, &flags, OS_WAIT_FOREVER);
+      if (flags & APP_EVENT_READY_FOR_DATA)
+      {
+         /* Delay the application ready event to
+          * allow the RTE to start sending frames.
+          */
+         app->appl_ready_delay_count = 256;
+         app->appl_ready_wait = true;
+         os_event_clr (app->main_events, APP_EVENT_READY_FOR_DATA);
+      }
+      else if (flags & APP_EVENT_ALARM)
+      {
+         os_event_clr (app->main_events, APP_EVENT_ALARM);
+
+         app_handle_send_alarm_ack (
+            app->net,
+            app->main_api.arep,
+            &app->alarm_arg);
+      }
+      else if (flags & APP_EVENT_TIMER)
+      {
+         os_event_clr (app->main_events, APP_EVENT_TIMER);
+
+         if (app->appl_ready_wait)
+         {
+            /* Delayed application ready event */
+            if (app->appl_ready_delay_count == 0)
+            {
+               app->appl_ready_wait = false;
+               app_handle_send_application_ready (
+                  app->net,
+                  app->arep_for_appl_ready);
+            }
+            else
+            {
+               app->appl_ready_delay_count--;
+            }
+         }
+
+         if (app->main_api.arep != UINT32_MAX)
+         {
+            /* Todo this operation should be split
+             * and inputs handled before pnet_handle_periodic is
+             * called and outputs after.*/
+            app_handle_cyclic_data (app);
+         }
+
+         /* Run p-net stack */
+         pnet_handle_periodic (app->net);
+      }
+      else if (flags & APP_EVENT_ABORT)
+      {
+         os_event_clr (app->main_events, APP_EVENT_ABORT);
+
+         app->main_api.arep = UINT32_MAX;
+         app->alarm_allowed = true;
+         APP_LOG_DEBUG ("Connection closed\n");
+         APP_LOG_DEBUG ("Waiting for PLC connect request\n\n");
+      }
+   }
+}

+ 148 - 0
samples/pn_dev_lan9662/sampleapp_common.h

@@ -0,0 +1,148 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef SAMPLEAPP_COMMON_H
+#define SAMPLEAPP_COMMON_H
+
+#include "osal.h"
+#include "pnal.h"
+#include <pnet_api.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define APP_TICK_INTERVAL_US 1000 /* 1 ms */
+
+/* Thread configuration for targets where sample
+ * event loop is run in a separate thread (not main).
+ * This applies for linux sample app implementation.
+ */
+#define APP_MAIN_THREAD_PRIORITY  15
+#define APP_MAIN_THREAD_STACKSIZE 4096 /* bytes */
+
+#define APP_DATA_LED_ID            1
+#define APP_PROFINET_SIGNAL_LED_ID 2
+
+#define APP_TICKS_UPDATE_DATA 100
+
+typedef enum
+{
+   MODE_HW_OFFLOAD_NONE = 0,
+   MODE_HW_OFFLOAD_CPU,
+   MODE_HW_OFFLOAD_FULL,
+} app_mode_t;
+
+typedef struct app_args
+{
+   char path_button1[PNET_MAX_FILE_FULLPATH_SIZE]; /** Terminated string */
+   char path_button2[PNET_MAX_FILE_FULLPATH_SIZE]; /** Terminated string */
+   char path_storage_directory[PNET_MAX_DIRECTORYPATH_SIZE]; /** Terminated */
+   char station_name[PNET_STATION_NAME_MAX_SIZE]; /** Terminated string */
+   char eth_interfaces
+      [PNET_INTERFACE_NAME_MAX_SIZE * (PNET_MAX_PHYSICAL_PORTS + 1) +
+       PNET_MAX_PHYSICAL_PORTS]; /** Terminated string */
+   int verbosity;
+   int show;
+   bool factory_reset;
+   bool remove_files;
+   app_mode_t mode;
+} app_args_t;
+
+typedef enum
+{
+   RUN_IN_SEPARATE_THREAD,
+   RUN_IN_MAIN_THREAD
+} app_run_in_separate_task_t;
+
+typedef struct app_data_t app_data_t;
+
+void app_pnet_cfg_init_default (pnet_cfg_t * pnet_cfg);
+
+/**
+ * Initialize application
+ *
+ * Initialize P-Net stack and application.
+ * The pnet_cfg argument shall have been initialized using
+ * app_pnet_cfg_init_default() before this function is
+ * called.
+ * @param pnet_cfg               In:    P-Net start configuration
+ * @return Application handle, NULL on error
+ */
+app_data_t * app_init (pnet_cfg_t * pnet_cfg, const app_args_t * app_args);
+
+/**
+ * Start application
+ *
+ * Application must have been initialized using app_init() before
+ * app_start() is called.
+ * If task_config parameters is set to RUN_IN_SEPARATE_THREAD a
+ * thread execution the app_loop_forever() function is started.
+ * If task_config is set to RUN_IN_MAIN_THREAD no such thread is
+ * started and the caller must call the app_loop_forever() after
+ * calling this function.
+ * RUN_IN_MAIN_THREAD is intended for rt-kernel targets.
+ * RUN_IN_SEPARATE_THREAD is intended for linux targets.
+ * @param app                 In:    Application handle
+ * @param task_config         In:    Defines if stack and application
+ *                                   is run in main or separate task.
+ *
+ * @return 0 on success, -1 on error
+ */
+int app_start (app_data_t * app, app_run_in_separate_task_t task_config);
+
+/**
+ * Application task definition. Handles events
+ * in eternal loop.
+ * @param arg                 In: Application handle
+ * return Should not
+ */
+void app_loop_forever (void * arg);
+
+/**
+ * Get P-Net instance from application
+ *
+ * @param app                 In:    Application handle
+ *
+ * @return P-Net instance
+ */
+pnet_t * app_get_pnet_instance (app_data_t * app);
+
+/**
+ * Set LED state
+ * Hardware specific. Implemented in sample app main file for
+ * each supported platform.
+ *
+ * @param id               In:    LED number, starting from 0.
+ * @param led_state        In:    LED state. Use true for on and false for off.
+ */
+void app_set_led (uint16_t id, bool led_state);
+
+/**
+ * Read button state
+ *
+ * Hardware specific. Implemented in sample app main file for
+ * each supported platform.
+ *
+ * @param id               In:    Button number, starting from 0.
+ * @return  true if button is pressed, false if not
+ */
+bool app_get_button (uint16_t id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SAMPLEAPP_COMMON_H */

+ 79 - 0
samples/pn_dev_lan9662/switchdev-profinet-example.sh

@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+# Start Profinet sample application
+# By default RTE mode is set to full / RTE-FGPA
+# The RTE mode can be selected by passing
+# "none", "cpu" or "full" to this script.
+#
+# Application RTE modes
+# none
+#  - LAN9662 RTE:      Disabled
+#  - I/O Data mapping: Shared Memory
+#  - P-Net data path:  P-Net default
+# cpu
+#  - LAN9662 RTE:      Enabled
+#  - I/O Data mapping: Shared Memory
+#  - P-Net data path:  RTE - SRAM
+# full
+#  - LAN9662 RTE:      Enabled
+#  - I/O Data mapping: iofpga
+#  - P-Net data path:  RTE - QSPI
+#
+# P-Net profinet requires a file system for
+# configuration storage. This example assumes
+# a rw file system mounted on /tmp/pn_data
+#
+
+echo "Starting switchdev-profinet-example"
+
+# Assume a valid partion on /dev/mmcblk0p2
+mkdir /tmp/pn_data
+mount -o sync /dev/mmcblk0p2 /tmp/pn_data
+
+# Configure bridge
+ip link add name br0 type bridge
+
+# Needed for bridge to work
+symreg ANA_PGID[61] 0x1ff
+
+ip link set br0 address 12:A9:2D:16:93:89
+ip link set br0 up
+sysctl -w net.ipv6.conf.br0.disable_ipv6=1
+
+ip link set eth0 up
+ip link set eth1 up
+
+ip link set eth0 master br0
+ip link set eth1 master br0
+
+# Enable forwarding to chip port 4 (RTE)
+symreg qsys_sw_port_mode[4] 0x45000
+
+# Start Profinet application
+if [[ $# -eq 0 ]] ; then
+    mode=full
+else
+    mode=$1
+fi
+
+echo "Starting LAN9662 Profinet sample application"
+echo "RTE mode: $mode"
+
+/usr/bin/pn_lan9662 -m "$mode" -vvvv -p /tmp/pn_data &
+
+# When enabling log level "DEBUG" for the P-Net stack
+# it may be useful to write log to file. The log file
+# can be uploaded from target using scp:
+# scp root@192.168.0.50:/tmp/pn_dev_log.txt pn_dev_log.txt
+
+#echo "Setup dropbear to support scp"
+#mkdir -p /var/run/dropbear/
+#dropbear -R -B
+
+# Enable to to write p-net log to file.
+#touch /tmp/pn_dev_log.txt
+#chmod a+w /tmp/pn_dev_log.txt
+#echo " Application log will be stored to /tmp/pn_dev_log.txt"
+#/usr/bin/pn_lan9662 -m "$mode" -vvvv -p /tmp/pn_data > /tmp/pn_dev_log.txt
+
+

+ 17 - 0
samples/pn_shm_tool/CMakeLists.txt

@@ -0,0 +1,17 @@
+#********************************************************************
+#        _       _         _
+#  _ __ | |_  _ | |  __ _ | |__   ___
+# | '__|| __|(_)| | / _` || '_ \ / __|
+# | |   | |_  _ | || (_| || |_) |\__ \
+# |_|    \__|(_)|_| \__,_||_.__/ |___/
+#
+# http://www.rt-labs.com
+# Copyright 2017 rt-labs AB, Sweden.
+# See LICENSE file in the project root for full license information.
+#*******************************************************************/
+
+target_include_directories(pn_shm_tool
+  PRIVATE
+  )
+
+install (TARGETS pn_shm_tool DESTINATION bin)

+ 509 - 0
samples/pn_shm_tool/pn_shm_tool.c

@@ -0,0 +1,509 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+/* Linux application intended to be used
+ * to read and write shared memory areas supported
+ * by the lan9662 sample application.
+ * See show_usage() for a functional description.
+ */
+
+#include <getopt.h>
+#include <semaphore.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAX_FILE_NAME    200
+#define MAX_STDIO_BUFFER 10000
+
+#define DEBUG_LOG (1)
+#if DEBUG_LOG == 1
+#define LOG_ERR(...)                                                           \
+   fprintf (stderr, "Line:%d ", __LINE__);                                     \
+   fprintf (stderr, __VA_ARGS__);
+#else
+#define LOG_ERR(...)
+#endif
+
+typedef struct app
+{
+   enum
+   {
+      UNDEFINED = 0,
+      READ,
+      WRITE,
+      CREATE,
+      READ_BIT
+   } mode;
+
+   char name[MAX_FILE_NAME]; /* Shared memory file name*/
+   int fd;                   /* Shared memory file descriptor */
+   caddr_t mem_address;      /* Address of mapped shared memory */
+   sem_t * sem;              /* Named semaphore */
+   size_t shm_size;          /* The size of an existing shared memory area */
+   size_t create_size;       /* Size argument used when creating a shared memory
+                                area*/
+   uint32_t bitnumber;       /* Bit number to read */
+   uint8_t buffer[MAX_STDIO_BUFFER];
+} app_t;
+
+static void show_usage (void)
+{
+   printf ("\nTool for accessing shared memory files\n");
+   printf ("Intended to be used in combination with the \n"
+           "pn_lan9662 sample application. \n");
+   printf ("Arguments:\n");
+   printf ("   -h             Show help and exit\n");
+   printf (
+      "   -r <shm_name>  Read shared memory and write content to stdout \n");
+   printf ("   -w <shm_name>  Write content on stdin to shared memory \n");
+   printf ("   -c <shm_name>  Create shared memory area\n");
+   printf ("   -s <size>      Size of shared memory, only used with create\n");
+   printf ("   -b <shm_name>  Read bit from shared memory. Write '1' or '0' "
+           "to stdout\n");
+   printf (
+      "   -n <bitnumber> Bit number, only used with read bit. Defaults to\n");
+   printf (
+      "                  bit number 0 (least significant bit in first byte)\n");
+   printf (
+      "Partial write is allowed. Offsets are not supported. If write data\n");
+   printf ("is larger than shared memory, data is truncated.\n");
+
+   printf ("\nExample usage:\n");
+   printf ("Read slot and passing data to hexdump:\n");
+   printf ("pn_shm_tool -r pnet-in-1-1-digital_input_1x8 | hexdump -C\n");
+   printf ("Write slot:\n");
+   printf ("echo -n -e \"\\x02\" | pn_shm_tool -w "
+           "pnet-in-1-1-digital_input_1x8\n");
+   printf ("Create 4 bytes shared memory area (for testing this tool):\n");
+   printf ("pn_shm_tool -c test_shm -s 4\n");
+}
+
+/**
+ * Open an existing shared memory area
+ * figure out its size and map it.
+ * @param   app      InOut: Application context
+ * @return 0 on success, -1 on error
+ */
+int open_shm (app_t * app)
+{
+   struct stat st;
+
+   if (app->mode == CREATE)
+   {
+      return -1;
+   }
+
+   app->fd = shm_open (app->name, O_RDWR, 0666);
+
+   if (app->fd < 0)
+   {
+      LOG_ERR ("\nFailed to open %s %s\n", app->name, strerror (errno));
+      return -1;
+   }
+
+   if (fstat (app->fd, &st))
+   {
+      LOG_ERR ("\nfstat error: [%s]\n", strerror (errno));
+      close (app->fd);
+      return -1;
+   }
+
+   app->shm_size = st.st_size;
+
+   if (ftruncate (app->fd, app->shm_size) != 0)
+   {
+      return -1;
+   }
+
+   app->mem_address =
+      mmap (NULL, app->shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, app->fd, 0);
+
+   if (app->mem_address == MAP_FAILED)
+   {
+      LOG_ERR ("\nmmap error: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   app->sem = sem_open (app->name, 0, 0644, 1);
+
+   if (app->sem == NULL)
+   {
+      LOG_ERR ("\nFailed to open semaphore: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * Create a new shared memory area
+ * @param   app      InOut: Application context
+ * @return 0 on success, -1 on error
+ */
+int create_shm (app_t * app)
+{
+   if (app->mode != CREATE)
+   {
+      return -1;
+   }
+
+   app->fd = shm_open (app->name, O_RDWR | O_CREAT, 0666);
+
+   if (app->fd < 0)
+   {
+      LOG_ERR ("\nFailed to create %s %s\n", app->name, strerror (errno));
+      return -1;
+   }
+
+   if (ftruncate (app->fd, app->create_size) != 0)
+   {
+      return -1;
+   }
+
+   app->mem_address = mmap (
+      NULL,
+      app->create_size,
+      PROT_READ | PROT_WRITE,
+      MAP_SHARED,
+      app->fd,
+      0);
+
+   if (app->mem_address == MAP_FAILED)
+   {
+      LOG_ERR ("\nmmap error: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   app->sem = sem_open (app->name, O_CREAT, 0644, 1);
+
+   if (app->sem == NULL)
+   {
+      LOG_ERR ("\nFailed to open semaphore: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * Close a shared memory area and its
+ * open resources.
+ * @param   app      InOut: Application context
+ * @return 0 always
+ */
+int close_shm (app_t * app)
+{
+   if (app->mem_address != 0)
+   {
+      munmap (app->mem_address, app->shm_size);
+      app->mem_address = 0;
+   }
+
+   if (app->fd >= 0)
+   {
+      close (app->fd);
+      app->fd = -1;
+   }
+
+   if (app->sem != NULL)
+   {
+      sem_close (app->sem);
+      app->sem = NULL;
+   }
+
+   return 0;
+}
+
+/**
+ * Read a shared memory area and print its content
+ * to STDOUT.
+ * @param   app      InOut: Application context
+ * @return 0 on success, -1 on error
+ */
+int read_shm (app_t * app)
+{
+   int write_len;
+
+   if (sem_wait (app->sem) != 0)
+   {
+      LOG_ERR ("\nFailed to take semaphore: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   memcpy (app->buffer, app->mem_address, app->shm_size);
+
+   if (sem_post (app->sem) != 0)
+   {
+      LOG_ERR ("\nFailed to release semaphore: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   write_len = write (STDOUT_FILENO, app->buffer, app->shm_size);
+   if (write_len != (int)app->shm_size)
+   {
+      LOG_ERR ("\nFailed to write stdout\n");
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * Read a bit from shared memory area and print "0" or "1" to STDOUT.
+ * @param   app      InOut: Application context
+ * @return 0 on success, -1 on error
+ */
+int read_bit_shm (app_t * app)
+{
+   int write_len = 0;
+   uint32_t byte_number = 0;
+   uint8_t bitnumber_in_byte = 0;
+   uint8_t byte_value = 0;
+   uint8_t bit_value = 0;
+
+   if (app->shm_size == 0)
+   {
+      LOG_ERR ("\nShared memory size is zero\n");
+      return -1;
+   }
+
+   if (app->bitnumber >= MAX_STDIO_BUFFER * 8)
+   {
+      LOG_ERR (
+         "\nToo large bitnumber. Given: %u (Buffer size %u bytes)\n",
+         app->bitnumber,
+         MAX_STDIO_BUFFER);
+      return -1;
+   }
+
+   if (app->bitnumber >= app->shm_size * 8)
+   {
+      LOG_ERR (
+         "\nToo large bitnumber. Given: %u (Memory size %zu bytes)\n",
+         app->bitnumber,
+         app->shm_size);
+      return -1;
+   }
+
+   if (sem_wait (app->sem) != 0)
+   {
+      LOG_ERR ("\nFailed to take semaphore: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   memcpy (app->buffer, app->mem_address, app->shm_size);
+
+   if (sem_post (app->sem) != 0)
+   {
+      LOG_ERR ("\nFailed to release semaphore: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   byte_number = app->bitnumber / 8; /* byte_number 0 is first */
+   byte_value = app->buffer[byte_number];
+   bitnumber_in_byte = app->bitnumber % 8;
+   bit_value = (byte_value >> bitnumber_in_byte) & 0x01;
+
+   write_len = write (STDOUT_FILENO, bit_value ? "1\n" : "0\n", 2);
+   if (write_len != 2)
+   {
+      LOG_ERR ("\nFailed to write stdout\n");
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * Read STDIN and copy the data to shared memory area.
+ * @param   app      InOut: Application context
+ * @return 0 on success, -1 on error
+ */
+int write_shm (app_t * app)
+{
+   int len = read (STDIN_FILENO, app->buffer, app->shm_size);
+   if (len > (int)app->shm_size)
+   {
+      /* Truncate len of data to match shared memory */
+      len = app->shm_size;
+   }
+
+   if (len <= 0)
+   {
+      LOG_ERR ("\nFailed to read stdin: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   if (sem_wait (app->sem) != 0)
+   {
+      LOG_ERR ("\nFailed to take semaphore: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   memcpy (app->mem_address, app->buffer, len);
+
+   if (sem_post (app->sem) != 0)
+   {
+      LOG_ERR ("\nFailed to release semaphore: [%s]\n", strerror (errno));
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * Execute a command given the configuration
+ * set in the app variable.
+ * Creation, read and write of shared memory regions
+ * are supported.
+ * @param   app      InOut: Application context
+ * @return 0 on success, -1 on error
+ */
+static int run (app_t * app)
+{
+   int error = 0;
+
+   if (app->mode == CREATE)
+   {
+      error = create_shm (app);
+      close_shm (app);
+      return error;
+   }
+
+   error = open_shm (app);
+   if (!error)
+   {
+      if (app->mode == READ)
+      {
+         error = read_shm (app);
+      }
+      else if (app->mode == READ_BIT)
+      {
+         error = read_bit_shm (app);
+      }
+      else if (app->mode == WRITE)
+      {
+         error = write_shm (app);
+      }
+      error |= close_shm (app);
+   }
+   return error;
+}
+
+/**
+ * Parse command line arguments and execute selected command.
+ * @param argc  In:  Number of args
+ * @param argv  In:  Arguments
+ * @return 0 on success, 1 on error
+ */
+int main (int argc, char * argv[])
+{
+   int error = 0;
+   int option = 0;
+   app_t app = {0};
+
+   if (argc < 2)
+   {
+      show_usage();
+      exit (EXIT_FAILURE);
+   }
+
+   while ((option = getopt (argc, argv, "hw:r:c:s:b:n:")) != -1)
+   {
+      switch (option)
+      {
+      case 'r':
+         if (app.mode != UNDEFINED)
+         {
+            show_usage();
+            printf ("\nOnly one operation allowed\n");
+            exit (EXIT_FAILURE);
+         }
+         strncpy (app.name, optarg, MAX_FILE_NAME - 1);
+         app.mode = READ;
+         break;
+      case 'b':
+         if (app.mode != UNDEFINED)
+         {
+            show_usage();
+            printf ("\nOnly one operation allowed\n");
+            exit (EXIT_FAILURE);
+         }
+         strncpy (app.name, optarg, MAX_FILE_NAME - 1);
+         app.mode = READ_BIT;
+         break;
+      case 'w':
+         if (app.mode != UNDEFINED)
+         {
+            show_usage();
+            printf ("\nOnly one operation allowed\n");
+            exit (EXIT_FAILURE);
+         }
+         strncpy (app.name, optarg, MAX_FILE_NAME - 1);
+         app.mode = WRITE;
+         break;
+      case 'c':
+         if (app.mode != UNDEFINED)
+         {
+            show_usage();
+            printf ("\nOnly one operation allowed\n");
+            exit (EXIT_FAILURE);
+         }
+         strncpy (app.name, optarg, MAX_FILE_NAME - 1);
+         app.mode = CREATE;
+         break;
+      case 's':
+         app.create_size = atoi (optarg);
+         break;
+      case 'n':
+         app.bitnumber = atoi (optarg);
+         break;
+      case 'h':
+         show_usage();
+         exit (EXIT_SUCCESS);
+      default:
+         show_usage();
+         exit (EXIT_FAILURE);
+      }
+   }
+   if (app.mode == CREATE)
+   {
+      if (app.create_size <= 0 || app.create_size > MAX_STDIO_BUFFER)
+      {
+         show_usage();
+         printf ("\nSize must be set when creating shared mem area\n");
+         printf ("Min size 1, Max size %d\n", MAX_STDIO_BUFFER);
+         exit (EXIT_FAILURE);
+      }
+   }
+
+   error = run (&app);
+   if (error != 0)
+   {
+      fprintf (stderr, "Operation failed.\n");
+      exit (EXIT_FAILURE);
+   }
+
+   exit (EXIT_SUCCESS);
+}

+ 31 - 0
samples/pn_shm_tool/shm_LED_handler.sh

@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Read shared memory and write to EVB-LAN9662 LED
+# Utility script using the pn_shm_tool. Intended to be
+# used for testing the pn_lan9662 application.
+#
+
+
+MEMORY_NAME="pnet-out-2-1-digital_output_1x8"
+BIT_NUMBER=7
+LED_NAME="twr1:yellow"
+
+echo "Starting LED handler for Profinet data LED"
+
+sleep 1
+
+while true
+do
+   sleep 0.1
+
+   led_state=$(pn_shm_tool -b "$MEMORY_NAME" -n "$BIT_NUMBER")
+   exitcode=$?
+
+   if [ $exitcode -ne 0 ]; then
+      echo "Failed to get LED state from shared memory"
+      exit 1
+   fi
+
+   echo ${led_state} > "/sys/class/leds/${LED_NAME}/brightness"
+
+done

+ 39 - 0
samples/pn_shm_tool/shm_button_handler.sh

@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# Read button on GPIO pin and write to shared memory
+# Utility script using the pn_shm_tool. Intended to be
+# used for testing the pn_lan9662 application.
+#
+
+MEMORY_NAME="pnet-in-1-1-digital_input_1x8"
+
+# Set up digital input on "Raspberry Pi"-type header
+# Button1, pin 7, use GND at pin 9
+GPIO_NUMBER=39
+
+echo "Starting button state monitor for GPIO $GPIO_NUMBER"
+
+echo "$GPIO_NUMBER" > /sys/class/gpio/export
+echo "1" > "/sys/class/gpio/gpio$GPIO_NUMBER/active_low"
+
+sleep 1
+
+while true
+do
+   sleep 0.1
+
+   button_state=$(cat "/sys/class/gpio/gpio$GPIO_NUMBER/value")
+   exitcode=$?
+
+   if [ $exitcode -ne 0 ]; then
+      echo "Failed to read button state"
+      exit 1
+   fi
+
+   if [ "$button_state" = "1" ]; then
+      echo -n -e '\x80' | pn_shm_tool -w "$MEMORY_NAME"
+   else
+      echo -n -e '\x00' | pn_shm_tool -w "$MEMORY_NAME"
+   fi
+
+done

+ 15 - 0
samples/pn_shm_tool/shm_echo_all.sh

@@ -0,0 +1,15 @@
+#!/bin/sh
+# Write shm outputs to inputs
+# For test running this command forever
+# may be handy:
+# while true; do /usr/bin/shm_echo_all.sh; done
+# Utility script using the pn_shm_tool. Intended to be
+# used for testing the pn_lan9662 application.
+#
+
+
+pn_shm_tool -r pnet-out-2-1-digital_output_1x8     | pn_shm_tool -w pnet-in-1-1-digital_input_1x8
+pn_shm_tool -r pnet-out-7-1-digital_out_1x64       | pn_shm_tool -w pnet-in-3-1-digital_input_1x64
+pn_shm_tool -r pnet-out-8-1-digital_output_2x32_a  | pn_shm_tool -w pnet-in-4-1-digital_input_2x32_a
+pn_shm_tool -r pnet-out-9-1-digital_output_2x32_b  | pn_shm_tool -w pnet-in-5-1-digital_input_2x32_b
+pn_shm_tool -r pnet-out-10-1-digital_output_1x8000 | pn_shm_tool -w pnet-in-6-1-digital_input_1x8000

+ 32 - 0
samples/pn_shm_tool/shm_read_all.sh

@@ -0,0 +1,32 @@
+#!/bin/sh
+# Utility script using the pn_shm_tool. Intended to be
+# used for testing the pn_lan9662 application.
+#
+# Dump the contents of all shared memory areas
+# supported by the lan9662 profinet sample application
+
+echo "Inputs"
+echo "------------------------------------------------------------------"
+echo "pnet-in-1-1-digital_input_1x8"
+pn_shm_tool -r pnet-in-1-1-digital_input_1x8 | hexdump -C
+echo "pnet-in-3-1-digital_input_1x64"
+pn_shm_tool -r pnet-in-3-1-digital_input_1x64 | hexdump -C
+echo "pnet-in-4-1-digital_input_2x32_a"
+pn_shm_tool -r pnet-in-4-1-digital_input_2x32_a | hexdump -C
+echo "pnet-in-5-1-digital_input_2x32_b"
+pn_shm_tool -r pnet-in-5-1-digital_input_2x32_b | hexdump -C
+echo "pnet-in-6-1-digital_input_1x8000"
+pn_shm_tool -r pnet-in-6-1-digital_input_1x8000 | hexdump -C
+
+echo "\nOutputs"
+echo "------------------------------------------------------------------"
+echo "pnet-out-2-1-digital_output_1x8"
+pn_shm_tool -r pnet-out-2-1-digital_output_1x8 | hexdump -C
+echo "pnet-out-7-1-digital_out_1x64"
+pn_shm_tool -r pnet-out-7-1-digital_out_1x64 | hexdump -C
+echo "pnet-out-8-1-digital_output_2x32_a"
+pn_shm_tool -r pnet-out-8-1-digital_output_2x32_a | hexdump -C
+echo "pnet-out-9-1-digital_output_2x32_b"
+pn_shm_tool -r pnet-out-9-1-digital_output_2x32_b | hexdump -C
+echo "pnet-out-10-1-digital_output_1x8000"
+pn_shm_tool -r pnet-out-10-1-digital_output_1x8000 | hexdump -C

+ 29 - 0
samples/pn_shm_tool/shm_write_all_inputs.sh

@@ -0,0 +1,29 @@
+#!/bin/sh
+# Utility script using the pn_shm_tool. Intended to be
+# used for testing the pn_lan9662 application.
+#
+# Write a character / byte to all shared memory inputs
+# supported by the lan9662 profinet sample application
+# Usage:
+#         shm_write_all_inputs.sh a
+#         shm_write_all_inputs.sh "\x02"
+
+echo -n -e "$1" | \
+pn_shm_tool -w pnet-in-1-1-digital_input_1x8
+
+echo -n -e "$1$1$1$1$1$1$1$1" | \
+pn_shm_tool -w pnet-in-3-1-digital_input_1x64
+
+echo -n -e "$1$1$1$1$1$1$1$1" | \
+pn_shm_tool -w pnet-in-4-1-digital_input_2x32_a
+
+echo -n -e "$1$1$1$1$1$1$1$1" | \
+pn_shm_tool -w pnet-in-5-1-digital_input_2x32_b
+
+data=''
+for i in `seq 1 100`;
+do
+data="${data}$1"
+done
+echo -n -e "$data" | \
+pn_shm_tool -w pnet-in-6-1-digital_input_1x8000

+ 122 - 0
samples/pn_shm_tool/test.sh

@@ -0,0 +1,122 @@
+#!/bin/sh
+echo "Sanity test of pn_shm_tool."
+
+# Create a shared memory area for test
+f="shm_test_2"
+echo "Creating /dev/shm/$f for test"
+
+$(./pn_shm_tool -c "$f" -s 4)
+
+
+# 1 Verify correct size of data
+
+data_in="abcd"
+echo "Test data: $data_in"
+
+$(echo -n -e "$data_in" | ./pn_shm_tool -w "$f")
+data_out=$(./pn_shm_tool -r "$f")
+
+echo "Data out:  $data_out"
+
+if [ "$data_out" != "$data_in" ]; then
+  echo "Error"
+  exit 1
+fi
+
+
+# 2 Verify correct too small input data
+
+data_in="ABC"
+echo "Test data: $data_in"
+
+$(echo -n -e "$data_in" | ./pn_shm_tool -w "$f")
+data_out=$(./pn_shm_tool -r "$f")
+
+echo "Data out:  $data_out"
+
+# First three bytes changed. Last byte keeps value
+if [ "$data_out" != "ABCd" ]; then
+  echo "Error"
+  exit 1
+fi
+
+
+# 3 Verify correct too large input data
+
+data_in="123456"
+echo "Test data: $data_in"
+
+$(echo -n -e "$data_in" | ./pn_shm_tool -w "$f")
+data_out=$(./pn_shm_tool -r "$f")
+
+echo "Data out:  $data_out"
+
+# All bytes changed. Size unchanged
+if [ "$data_out" != "1234" ]; then
+  echo "Error"
+  exit 1
+fi
+
+
+# 4 faulty parameter
+
+echo "Testing invalid parameter:"
+show_cmd=$(./pn_shm_tool -d "$f")
+ret_val=$?
+echo " "
+
+if [ $ret_val -ne 1 ]; then
+  echo "Error"
+  exit 1
+fi
+
+# 5 Verify reading bit
+
+data_in="1234"
+echo "Test data: $data_in"
+echo "The stored data as ASCII: 0x31 0x32 0x33 0x34"
+
+$(echo -n -e "$data_in" | ./pn_shm_tool -w "$f")
+
+data_out=$(./pn_shm_tool -b "$f" -n 0)
+ret_val=$?
+if [ $ret_val -ne 0 ]; then
+  echo "Error when reading 0: Wrong exit code"
+  exit 1
+fi
+if [ "$data_out" != "1" ]; then
+  echo "Error reading bit 0: Wrong value"
+  exit 1
+fi
+
+data_out=$(./pn_shm_tool -b "$f" -n 1)
+if [ "$data_out" != "0" ]; then
+  echo "Error reading bit 1"
+  exit 1
+fi
+
+data_out=$(./pn_shm_tool -b "$f" -n 2)
+if [ "$data_out" != "0" ]; then
+  echo "Error reading bit 2"
+  exit 1
+fi
+
+data_out=$(./pn_shm_tool -b "$f" -n 3)
+if [ "$data_out" != "0" ]; then
+  echo "Error reading bit 3"
+  exit 1
+fi
+
+
+# 6 faulty parameter to read bit
+echo "Testing invalid parameter:"
+data_out=$(./pn_shm_tool -b "$f" -n 32)
+ret_val=$?
+echo " "
+
+if [ $ret_val -ne 1 ]; then
+  echo "Error"
+  exit 1
+fi
+
+echo "OK, test passed"

+ 3 - 7
src/drivers/drivers.cmake

@@ -12,10 +12,6 @@
 # license. See the file LICENSE.md distributed with this software for
 # full license information.
 #*******************************************************************/
-
-# Include cmake-file for supported drivers here
-
-# TODO - Currently no driver is supported and no cmake is included 
-# include( src/drivers/${subdir}/driver.cmake )
-
-
+if (PNET_OPTION_DRIVER_ENABLE)
+  include( src/drivers/lan9662/driver.cmake )
+endif()

+ 0 - 24
src/drivers/drivers_options.cmake

@@ -1,24 +0,0 @@
-#********************************************************************
-#        _       _         _
-#  _ __ | |_  _ | |  __ _ | |__   ___
-# | '__|| __|(_)| | / _` || '_ \ / __|
-# | |   | |_  _ | || (_| || |_) |\__ \
-# |_|    \__|(_)|_| \__,_||_.__/ |___/
-#
-# www.rt-labs.com
-# Copyright 2018 rt-labs AB, Sweden.
-#
-# This software is dual-licensed under GPLv3 and a commercial
-# license. See the file LICENSE.md distributed with this software for
-# full license information.
-#*******************************************************************/
-message(STATUS "PNET_OPTION_DRIVER_ENABLE ${PNET_OPTION_DRIVER_ENABLE}")
-message(STATUS "Driver options configuration")
-
-# Include driver_options cmake-file for supported drivers here
-
-# TODO - Currently no driver is supported and no cmake included 
-# include( src/drivers/${subdir}/driver_options.cmake )
-
-
-

+ 41 - 0
src/drivers/lan9662/add_inbound_vcap_rule.sh

@@ -0,0 +1,41 @@
+#!/bin/sh
+#############################################
+# Add vcap rule for inbound data / outgoing cyclic frames
+#
+# Definitions:
+# VLAN_ID :== VLAN ID
+# DMAC :== xx:xx:xx:xx:xx:xx
+# RT_VLAN_IDX :== 0..7
+# PRIORITY :== 0..0xffff
+# VCAM_HANDLE :== 0..0xffffffff
+# RT_FRMID :== 0..0xffff
+# RTP_ID :== 0..0x1f
+# RTP_SUBID :== 0..1
+# RTE_INB_UPD :== 1 for inbound RTE processing, 0 otherwise
+
+#set -x
+
+PRIORITY=10
+VCAM_HANDLE=$1
+VLAN_ID=$2
+DMAC=$3
+RT_VLAN_IDX=$4
+
+RT_FRMID=$5
+RTP_ID=$6
+RTP_SUBID=0
+RTE_INB_UPD=1
+
+COMMAND="vcap add is1 $PRIORITY $VCAM_HANDLE \
+s1_rt first 0 l2_mac $DMAC ff:ff:ff:ff:ff:ff \
+rt_vlan_idx $RT_VLAN_IDX 0x7 rt_frmid $RT_FRMID 0xffff \
+s1_rt rtp_id $RTP_ID rtp_subid $RTP_SUBID \
+rte_inb_upd $RTE_INB_UPD fwd_ena 1 fwd_mask 0x10"
+
+echo "$COMMAND"
+if ! $COMMAND; then
+   echo "Failed adding vcap rule for inbound data"
+   exit 1
+fi
+
+exit 0

+ 42 - 0
src/drivers/lan9662/add_mera_lib.cmake

@@ -0,0 +1,42 @@
+#********************************************************************
+#        _       _         _
+#  _ __ | |_  _ | |  __ _ | |__   ___
+# | '__|| __|(_)| | / _` || '_ \ / __|
+# | |   | |_  _ | || (_| || |_) |\__ \
+# |_|    \__|(_)|_| \__,_||_.__/ |___/
+#
+# www.rt-labs.com
+# Copyright 2020 rt-labs AB, Sweden.
+#
+# This software is licensed under the terms of the BSD 3-clause
+# license. See the file LICENSE distributed with this software for
+# full license information.
+#*******************************************************************/
+
+
+cmake_minimum_required(VERSION 3.14)
+
+# Attempt to find externally built mera library
+find_package(mera QUIET)
+
+if (NOT mera_FOUND)
+  # Download and build mera locally as a static library
+  # Todo: this is a private repo. Switch to public repo before release
+  message(STATUS "Fetch mera from github")
+  include(FetchContent)
+  FetchContent_Declare(
+    mera
+    GIT_REPOSITORY      https://github.com/microchip-ung/rtlabs-mera
+    GIT_TAG             b9d43d5
+    )
+
+  FetchContent_GetProperties(mera)
+  if(NOT mera_POPULATED)
+    FetchContent_Populate(mera)
+    set(BUILD_SHARED_LIBS_OLD ${BUILD_SHARED_LIBS})
+    set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "" FORCE)
+    add_subdirectory(${mera_SOURCE_DIR} ${mera_BINARY_DIR} EXCLUDE_FROM_ALL)
+    set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_OLD} CACHE BOOL "" FORCE)
+  endif()
+
+endif()

+ 47 - 0
src/drivers/lan9662/add_outbound_vcap_rule.sh

@@ -0,0 +1,47 @@
+#!/bin/sh
+
+############################################
+# Add vcap rule for outbound data / incoming cyclic frames
+#
+# Definitions:
+# VLAN_ID :== VLAN ID
+# RT_VLAN_IDX :== 0..7
+# PRIORITY :== 0..0xffff
+# VCAM_HANDLE :== 0..0xffffffff
+# RT_FRMID :== 0..0xffff
+# RTP_ID :== 0..0x1f
+# RTP_SUBID :== 0..1
+# RTE_INB_UPD :== 1 for inbound RTE processing, 0 otherwise
+
+# set -x
+
+PRIORITY=10
+VCAM_HANDLE=$1
+VLAN_ID=$2
+RT_VLAN_IDX=$3
+
+RT_FRMID=$4
+RTP_ID=$5
+DMAC=$6
+
+# Enable untagged RTP processing
+symreg ana_rt_vlan_pcp[$RT_VLAN_IDX].pcp_mask 0xff     # Wildcard on pcp value
+symreg ana_rt_vlan_pcp[$RT_VLAN_IDX].vlan_id $VLAN_ID  # Actual vid
+symreg ana_rt_vlan_pcp[$RT_VLAN_IDX].vlan_pcp_ena 1    # Enable entry
+
+COMMAND="vcap add is1 $PRIORITY $VCAM_HANDLE \
+s1_rt first 0 \
+rt_vlan_idx $RT_VLAN_IDX 0x7 \
+l2_mac $DMAC ff:ff:ff:ff:ff:ff \
+rt_type 1 0x3 \
+rt_frmid $RT_FRMID 0xffff \
+s1_rt rtp_id $RTP_ID \
+fwd_ena 1 fwd_mask 0x10"
+
+echo "$COMMAND"
+if ! $COMMAND; then
+   echo "Failed adding vcam rule for inbound data"
+   exit 1
+fi
+
+exit 0

+ 20 - 0
src/drivers/lan9662/del_vcap_rule.sh

@@ -0,0 +1,20 @@
+#!/bin/sh
+
+############################################
+# Delete vcap rule
+#
+
+#set -x
+
+PRIORITY=10
+VCAM_HANDLE=$1
+
+COMMAND="vcap del is1 $PRIORITY $VCAM_HANDLE"
+
+echo "$COMMAND"
+if ! $COMMAND; then
+   echo "Failed to delete vcap rule"
+   exit 1
+fi
+
+exit 0

+ 154 - 0
src/drivers/lan9662/driver.cmake

@@ -0,0 +1,154 @@
+#********************************************************************
+#        _       _         _
+#  _ __ | |_  _ | |  __ _ | |__   ___
+# | '__|| __|(_)| | / _` || '_ \ / __|
+# | |   | |_  _ | || (_| || |_) |\__ \
+# |_|    \__|(_)|_| \__,_||_.__/ |___/
+#
+# www.rt-labs.com
+# Copyright 2018 rt-labs AB, Sweden.
+#
+# This software is dual-licensed under GPLv3 and a commercial
+# license. See the file LICENSE.md distributed with this software for
+# full license information.
+#*******************************************************************/
+
+cmake_dependent_option (
+  PNET_OPTION_DRIVER_LAN9662
+  "Enable LAN9662 HW Offload driver" OFF
+  "PNET_OPTION_DRIVER_ENABLE" OFF )
+
+if (PNET_OPTION_DRIVER_LAN9662)
+
+option(PNET_OPTION_LAN9662_SHOW_RTE_INFO "Show RTE config log message" OFF)
+set(PNET_LAN9662_MAX_FRAMES 2  CACHE STRING "Max active CPM and PPM instances")
+set(PNET_LAN9662_MAX_IDS    16 CACHE STRING "Max subslots for a frame")
+
+set(PNET_LAN9662_VCAM_BASE  2  CACHE STRING "LAN9662 VCAP base index")
+set(PNET_LAN9662_RTP_BASE   4  CACHE STRING "LAN9662 RTP base index")
+set(PNET_LAN9662_WAL_BASE   6  CACHE STRING "LAN9662 write action list base index")
+set(PNET_LAN9662_RAL_BASE   8  CACHE STRING "LAN9662 read action list base index")
+
+message(STATUS "LAN9662 pnet_driver_options.h configuration")
+configure_file (
+  src/drivers/lan9662/pnet_driver_options.h.in
+  include/pnet_driver_options.h
+)
+
+message(STATUS "Add LAN9662 targets and configurations")
+
+if(TARGET mera)
+  message(STATUS "LAN9662 mera lib found")
+  get_target_property(MERA_INCLUDES mera INCLUDE_DIRECTORIES)
+else()
+  message(STATUS "LAN9662 mera lib not found - fetch repo")
+  include(src/drivers/lan9662/add_mera_lib.cmake)
+  get_target_property(MERA_INCLUDES mera INCLUDE_DIRECTORIES)
+endif()
+
+add_executable(pn_lan9662 "")
+add_executable(pn_shm_tool "")
+
+set_target_properties (pn_lan9662 pn_shm_tool
+  PROPERTIES
+  C_STANDARD 99
+  )
+
+add_subdirectory (samples/pn_dev_lan9662)
+add_subdirectory (samples/pn_shm_tool)
+
+target_sources(profinet
+  PRIVATE
+  ${PROFINET_SOURCE_DIR}/src/drivers/lan9662/src/pf_cpm_driver_lan9662.c
+  ${PROFINET_SOURCE_DIR}/src/drivers/lan9662/src/pf_lan9662_mera.c
+  ${PROFINET_SOURCE_DIR}/src/drivers/lan9662/src/pf_mera_trace.c
+  ${PROFINET_SOURCE_DIR}/src/drivers/lan9662/src/pf_mera_trace.c
+  ${PROFINET_SOURCE_DIR}/src/drivers/lan9662/src/pf_rte_uio.c
+  ${PROFINET_SOURCE_DIR}/src/drivers/lan9662/src/pf_sram_uio.c
+  ${PROFINET_SOURCE_DIR}/src/drivers/lan9662/src/pf_ppm_driver_lan9662.c
+  )
+
+target_include_directories(profinet
+  PRIVATE
+  src/drivers/lan9662
+  src/drivers/lan9662/include
+  ${MERA_INCLUDES}
+  )
+
+target_link_libraries(profinet
+  PUBLIC
+  mera
+  )
+
+install (
+  TARGETS mera
+  EXPORT ProfinetConfig
+  DESTINATION lib
+  )
+
+target_include_directories(pn_dev
+  PRIVATE
+  src/drivers/lan9662
+  src/drivers/lan9662/include
+  )
+
+target_include_directories(pn_lan9662
+  PRIVATE
+  samples/pn_dev_lan9662
+  src/drivers/lan9662
+  src/drivers/lan9662/include
+  src/ports/linux
+  )
+
+target_sources(pn_lan9662
+  PRIVATE
+  samples/pn_dev_lan9662/sampleapp_common.c
+  samples/pn_dev_lan9662/app_utils.c
+  samples/pn_dev_lan9662/app_log.c
+  samples/pn_dev_lan9662/app_gsdml.c
+  samples/pn_dev_lan9662/app_data.c
+  samples/pn_dev_lan9662/app_shm.c
+  src/ports/linux/sampleapp_main.c
+  )
+
+target_compile_options(pn_lan9662
+  PRIVATE
+  ${APP_COMPILER_FLAGS}
+  )
+
+target_link_options(pn_lan9662
+   PRIVATE
+   -Wl,--gc-sections
+  )
+
+target_sources(pn_shm_tool
+  PRIVATE
+  samples/pn_shm_tool/pn_shm_tool.c
+  )
+
+target_compile_options(pn_shm_tool
+  PRIVATE
+  ${APP_COMPILER_FLAGS}
+  )
+
+target_link_options(pn_shm_tool
+   PRIVATE
+   -Wl,--gc-sections
+  )
+
+target_link_libraries (pn_shm_tool PUBLIC rt pthread)
+
+file(COPY
+  src/drivers/lan9662/include/driver_config.h
+  src/drivers/lan9662/include/pnet_lan9662_api.h
+  DESTINATION include
+  )
+
+file(COPY
+  src/ports/linux/set_network_parameters
+  src/drivers/lan9662/set_profinet_leds
+  DESTINATION
+  ${PROFINET_BINARY_DIR}/
+  )
+
+endif()

+ 49 - 0
src/drivers/lan9662/include/driver_config.h

@@ -0,0 +1,49 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2022 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+/**
+ * @file
+ * @brief LAN9662-specific configuration
+ *
+ * This file contains definitions of configuration settings for the
+ * LAN9662 driver.
+ */
+
+#ifndef DRIVER_CONFIG_H
+#define DRIVER_CONFIG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "pnet_driver_options.h"
+
+typedef struct pnet_driver_config
+{
+   struct
+   {
+      uint16_t vcam_base_id;
+      uint16_t rtp_base_id;
+      uint16_t wal_base_id;
+      uint16_t ral_base_id;
+   } mera;
+
+} pnet_driver_config_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRIVER_CONFIG_H */

+ 89 - 0
src/drivers/lan9662/include/pnet_lan9662_api.h

@@ -0,0 +1,89 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef PF_LAN9662_API_H
+#define PF_LAN9662_API_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pnet_api.h>
+
+typedef enum
+{
+   PNET_MERA_DATA_TYPE_SRAM,
+   PNET_MERA_DATA_TYPE_QSPI
+} pnet_mera_rte_data_type_t;
+
+/**
+ * Submodule RTE data configuration.
+ * Used for both inputs and outputs configuration.
+ * The default value is used if the IOXS is bad.
+ */
+typedef struct pnet_mera_rte_data_cfg
+{
+   pnet_mera_rte_data_type_t type;
+   uint16_t address;
+   const uint8_t * default_data;
+} pnet_mera_rte_data_cfg_t;
+
+/**
+ * Configure RTE input for a single subslot.
+ * This operation is used to configure a QSPI data source.
+ * Called during the AR establishment.
+ *
+ * @param net              InOut: The p-net stack instance
+ * @param api              In:    The API.
+ * @param slot             In:    The slot.
+ * @param subslot          In:    The sub-slot.
+ * @param rte_data_cfg     In:    RTE configuration
+ * @return  0  if the RTE is successfully configured.
+ *          -1 if an error occurred. For example if HW resources are not
+ *             available.
+ */
+PNET_EXPORT int pnet_mera_input_set_rte_config (
+   pnet_t * net,
+   uint32_t api,
+   uint16_t slot,
+   uint16_t subslot,
+   const pnet_mera_rte_data_cfg_t * rte_data_cfg);
+
+/**
+ * Configure RTE output for a single subslot.
+ * This operation is used to configure a QSPI data sink.
+ * Called during the AR establishment.
+ *
+ * @param net              InOut: The p-net stack instance
+ * @param api              In:    The API.
+ * @param slot             In:    The slot.
+ * @param subslot          In:    The sub-slot.
+ * @param rte_data_cfg     In:    RTE configuration
+ * @return  0  if the RTE is successfully configured.
+ *          -1 if an error occurred. For example if HW resources are not
+ *             available.
+ */
+PNET_EXPORT int pnet_mera_output_set_rte_config (
+   pnet_t * net,
+   uint32_t api,
+   uint16_t slot,
+   uint16_t subslot,
+   const pnet_mera_rte_data_cfg_t * rte_data_cfg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PF_LAN9662_API_H */

+ 52 - 0
src/drivers/lan9662/pnet_driver_options.h.in

@@ -0,0 +1,52 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2022 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef PNET_DRIVER_OPTIONS_H
+#define PNET_DRIVER_OPTIONS_H
+
+
+#if !defined (PNET_OPTION_DRIVER_LAN9662)
+#cmakedefine01 PNET_OPTION_DRIVER_LAN9662
+#endif
+
+#if !defined (PNET_OPTION_LAN9662_SHOW_RTE_INFO)
+#cmakedefine01 PNET_OPTION_LAN9662_SHOW_RTE_INFO
+#endif
+
+#if !defined (PNET_LAN9662_MAX_FRAMES)
+#define PNET_LAN9662_MAX_FRAMES @PNET_LAN9662_MAX_FRAMES@
+#endif
+
+#if !defined (PNET_LAN9662_MAX_IDS)
+#define PNET_LAN9662_MAX_IDS @PNET_LAN9662_MAX_IDS@
+#endif
+
+#if !defined (PNET_LAN9662_VCAM_BASE)
+#define PNET_LAN9662_VCAM_BASE @PNET_LAN9662_VCAM_BASE@
+#endif
+
+#if !defined (PNET_LAN9662_RTP_BASE)
+#define PNET_LAN9662_RTP_BASE @PNET_LAN9662_RTP_BASE@
+#endif
+
+#if !defined (PNET_LAN9662_WAL_BASE)
+#define PNET_LAN9662_WAL_BASE @PNET_LAN9662_WAL_BASE@
+#endif
+
+#if !defined (PNET_LAN9662_RAL_BASE)
+#define PNET_LAN9662_RAL_BASE @PNET_LAN9662_RAL_BASE@
+#endif
+
+#endif  /* PNET_DRIVER_OPTIONS_H */

+ 37 - 0
src/drivers/lan9662/set_profinet_leds

@@ -0,0 +1,37 @@
+#!/bin/sh
+
+#############################################
+# Script for setting Profinet LEDs on Linux #
+#############################################
+#
+# This writes the output to files instead of real LEDs.
+# It can be useful when running on for example Ubuntu or some other
+# desktop Linux variant.
+#
+# Modify this script to fit your setup, by for example changing output path.
+
+if [ $# -ne 2 ]; then
+   echo "Usage: ${0} led_number led_state"
+   echo "   where:"
+   echo "       led_number:      LED number. Number 1 for sample app data LED"
+   echo "                                    Number 2 for mandatory Profinet signal LED"
+   echo "       led_state:       1 for on, 0 for off"
+   echo "   Exit code:           0 on success, 1 on error"
+   exit 1
+fi
+
+LED_NUMBER=$1
+LED_STATE=$2
+
+# Uncomment next line for debugging
+#echo "Script for setting Profinet LED state in dummy file. LED number ${LED_NUMBER}, new state ${LED_STATE}"
+
+if ! [ ${LED_STATE} == "0" -o ${LED_STATE} == "1" ]; then
+   echo "Wrong LED state: ${LED_STATE}"
+   exit 1
+fi
+
+# Todo - map to GPIOs
+echo "LED ${LED_NUMBER} new state ${LED_STATE}"
+
+exit 0

+ 473 - 0
src/drivers/lan9662/src/pf_cpm_driver_lan9662.c

@@ -0,0 +1,473 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+/**
+ * @file
+ * @brief CPM driver for LAN9662
+ *
+ */
+
+#include "pf_includes.h"
+#include "pf_lan9662_mera.h"
+
+#include <inttypes.h>
+#include <string.h>
+
+/**
+ * @internal
+ * Control interval timeout handler
+ * This is a callback for the scheduler.
+ *
+ * @param net              InOut: The p-net stack instance
+ * @param arg              In:    The IOCR instance. pf_iocr_t
+ * @param current_time     In:    The current system time, in microseconds,
+ *                                when the scheduler is started to execute
+ *                                stored tasks.
+ */
+static void pf_cpm_drv_lan9662_ci_timeout (
+   pnet_t * net,
+   void * arg,
+   uint32_t current_time)
+{
+   pf_iocr_t * p_iocr = (pf_iocr_t *)arg;
+   uint32_t start = os_get_current_time_us();
+   uint32_t exec;
+   uint64_t rx_count = 0;
+   uint8_t data_status = 0;
+   uint8_t changes = 0;
+   pf_mera_event_t mera_event = {0};
+   bool reset_dht = false;
+
+   pf_scheduler_reset_handle (&p_iocr->cpm.ci_timeout);
+
+   if (p_iocr->cpm.ci_running == true) /* Timer running */
+   {
+      if (pf_mera_cpm_read_rx_count (p_iocr->cpm.frame, &rx_count) != 0)
+      {
+         LOG_ERROR (
+            PF_CPM_LOG,
+            "CPM_DRV_MERA(%d): Failed to read frame rx counter\n",
+            __LINE__);
+      }
+      p_iocr->cpm.recv_cnt += rx_count;
+
+      if (pf_mera_cpm_read_data_status (p_iocr->cpm.frame, &data_status) != 0)
+      {
+         LOG_ERROR (
+            PF_CPM_LOG,
+            "CPM_DRV_MERA(%d): Failed to read data status\n",
+            __LINE__);
+      }
+
+      changes = p_iocr->cpm.data_status ^ data_status;
+      p_iocr->cpm.data_status = data_status;
+      if (changes != 0)
+      {
+         LOG_INFO (
+            PF_CPM_LOG,
+            "CPM_DRV_MERA(%d): Data Status changed. New value 0x%x\n",
+            __LINE__,
+            data_status);
+      }
+
+      switch (p_iocr->cpm.state)
+      {
+      case PF_CPM_STATE_W_START:
+      case PF_CPM_STATE_FRUN:
+         if (
+            (rx_count > 0) &&
+            (data_status & BIT (PNET_DATA_STATUS_BIT_PROVIDER_STATE)))
+         {
+            LOG_DEBUG (
+               PF_CPM_LOG,
+               "CPM_DRV_MERA(%d): Data received and status ok\n",
+               __LINE__);
+
+            pf_cpm_state_ind (net, p_iocr->p_ar, p_iocr->crep, true);
+            pf_cpm_set_state (&p_iocr->cpm, PF_CPM_STATE_RUN);
+
+            /* Reconfigure RTE to activate DHT */
+            pf_mera_cpm_activate_rte_dht (p_iocr->cpm.frame);
+         }
+
+         /* Always reset DHT while in the inital states. */
+
+         reset_dht = true;
+         break;
+
+      case PF_CPM_STATE_RUN:
+
+         if (changes != 0)
+         {
+            pf_fspm_data_status_changed (
+               net,
+               p_iocr->p_ar,
+               p_iocr,
+               changes,
+               data_status);
+         }
+
+         if (rx_count > 0)
+         {
+            (void)pf_cmio_cpm_new_data_ind (p_iocr->p_ar, p_iocr->crep, true);
+         }
+         break;
+
+      default:
+         LOG_ERROR (
+            PF_CPM_LOG,
+            "CPM_DRV_MERA(%d): Unsupported CPM state\n",
+            __LINE__);
+         break;
+      }
+
+      if (pf_mera_poll_cpm_events (net, p_iocr->cpm.frame, &mera_event) != 0)
+      {
+         LOG_ERROR (
+            PF_CPM_LOG,
+            "CPM_DRV_MERA(%d): Failed to poll rtp events\n",
+            __LINE__);
+      }
+
+      if (rx_count > 0 && !mera_event.data_status_mismatch)
+      {
+         reset_dht = true;
+      }
+      else
+      {
+         if ((BIT (PNET_DATA_STATUS_BIT_PROVIDER_STATE) & data_status) == 0)
+         {
+            reset_dht = true;
+         }
+         if (
+            (BIT (PNET_DATA_STATUS_BIT_STATION_PROBLEM_INDICATOR) &
+             data_status) == 0)
+         {
+            reset_dht = true;
+         }
+      }
+
+      if (reset_dht)
+      {
+         p_iocr->cpm.dht = 0;
+      }
+      else
+      {
+         p_iocr->cpm.dht++;
+      }
+
+      if (p_iocr->cpm.dht >= p_iocr->cpm.data_hold_factor)
+      {
+         if (p_iocr->cpm.dht == p_iocr->cpm.data_hold_factor)
+         {
+            LOG_WARNING (
+               PF_CPM_LOG,
+               "CPM_DRV_MERA(%d): Data hold timer (DHT) expired\n",
+               __LINE__);
+
+            if (!mera_event.stopped)
+            {
+               /* Workaround for RTC testcase. */
+               LOG_INFO (
+                  PF_CPM_LOG,
+                  "CPM_DRV_MERA(%d): Generate rtp stopped event\n",
+                  __LINE__);
+
+               mera_event.stopped = true;
+            }
+         }
+      }
+
+      if (mera_event.stopped)
+      {
+         p_iocr->p_ar->err_cls = PNET_ERROR_CODE_1_RTA_ERR_CLS_PROTOCOL;
+         p_iocr->p_ar->err_code =
+            PNET_ERROR_CODE_2_ABORT_AR_CONSUMER_DHT_EXPIRED;
+
+         p_iocr->cpm.dht = 0;
+         p_iocr->cpm.ci_running = false; /* Stop timer */
+         pf_cpm_state_ind (net, p_iocr->p_ar, p_iocr->crep, false);
+
+         pf_cpm_set_state (&p_iocr->cpm, PF_CPM_STATE_W_START);
+      }
+
+      if (p_iocr->cpm.ci_running == true)
+      {
+         /* Timer auto-reload */
+         if (
+            pf_scheduler_add (
+               net,
+               p_iocr->cpm.control_interval,
+               pf_cpm_drv_lan9662_ci_timeout,
+               arg,
+               &p_iocr->cpm.ci_timeout) != 0)
+         {
+            LOG_ERROR (
+               PF_CPM_LOG,
+               "CPM_DRV_MERA(%d): Timeout not started\n",
+               __LINE__);
+            p_iocr->p_ar->err_cls = PNET_ERROR_CODE_1_CPM;
+            p_iocr->p_ar->err_code = PNET_ERROR_CODE_2_CPM_INVALID;
+            pf_cmsu_cpm_error_ind (
+               net,
+               p_iocr->p_ar,
+               p_iocr->p_ar->err_cls,
+               p_iocr->p_ar->err_code);
+         }
+      }
+   }
+
+   exec = os_get_current_time_us() - start;
+   if (exec > p_iocr->cpm.max_exec)
+   {
+      p_iocr->cpm.max_exec = exec;
+   }
+}
+
+static int pf_cpm_drv_lan9662_create (
+   pnet_t * net,
+   pf_ar_t * p_ar,
+   uint32_t crep)
+{
+   pf_cpm_t * p_cpm = &p_ar->iocrs[crep].cpm;
+
+   pf_mera_frame_cfg_t mera_cpm_cfg = {0};
+
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "CPM_DRV_MERA(%d): Allocate RTE frame (outbound data / rx frames) for "
+      "AREP %u "
+      "CREP "
+      "%" PRIu32 "\n",
+      __LINE__,
+      p_ar->arep,
+      crep);
+
+   mera_cpm_cfg.p_iocr = &p_ar->iocrs[crep];
+   mera_cpm_cfg.port = 0;
+
+   pf_mera_get_port_from_remote_mac (
+      &p_ar->ar_param.cm_initiator_mac_add,
+      &mera_cpm_cfg.port);
+
+   memcpy (
+      &mera_cpm_cfg.main_mac_addr,
+      &net->pf_interface.main_port.mac_address,
+      sizeof (pnet_ethaddr_t));
+
+   p_cpm->frame = pf_mera_cpm_alloc (net, &mera_cpm_cfg);
+
+   if (p_cpm->frame == NULL)
+   {
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * @internal
+ * Close RTE resources used in CPM frame.
+ * @param net              InOut: The p-net stack instance
+ * @param arg              In:    CPM frame
+ * @param current_time     In:    The current system time, in microseconds,
+ *                                when the scheduler is started to execute
+ *                                stored tasks.
+ */
+static void pf_cpm_drv_lan9662_delayed_close (
+   pnet_t * net,
+   void * arg,
+   uint32_t current_time)
+{
+   pf_drv_frame_t * frame = (pf_drv_frame_t *)arg;
+   pf_mera_cpm_stop (net, frame);
+   pf_mera_cpm_free (net, frame);
+}
+
+static int pf_cpm_drv_lan9662_close_req (
+   pnet_t * net,
+   pf_ar_t * p_ar,
+   uint32_t crep)
+{
+   pf_cpm_t * p_cpm = &p_ar->iocrs[crep].cpm;
+
+   p_cpm->ci_running = false;
+   pf_scheduler_reset_handle (&p_cpm->ci_timeout);
+
+   /* Delay close of pf_mera resources
+    * Workaround for delayed alarm transmission.
+    * Close RTE resources after alarm is sent.
+    */
+   pf_scheduler_init_handle (&p_cpm->ci_timeout, "cpm_close");
+   pf_scheduler_add (
+      net,
+      2,
+      pf_cpm_drv_lan9662_delayed_close,
+      p_cpm->frame,
+      &p_cpm->ci_timeout);
+
+   return 0;
+}
+
+static int pf_cpm_drv_lan9662_activate_req (
+   pnet_t * net,
+   pf_ar_t * p_ar,
+   uint32_t crep)
+{
+   int ret = -1;
+   pf_cpm_t * p_cpm;
+   pf_iocr_t * p_iocr;
+
+   p_iocr = &p_ar->iocrs[crep];
+   p_cpm = &p_iocr->cpm;
+
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "CPM_DRV_MERA(%d): Activate RTE (outbound data / rx frames) for AREP %u "
+      "CREP "
+      "%" PRIu32 "\n",
+      __LINE__,
+      p_ar->arep,
+      crep);
+
+   ret = pf_mera_cpm_start (net, p_cpm->frame);
+   if (ret == 0)
+   {
+      pf_scheduler_init_handle (&p_cpm->ci_timeout, "cpm");
+      ret = pf_scheduler_add (
+         net,
+         p_cpm->control_interval,
+         pf_cpm_drv_lan9662_ci_timeout,
+         p_iocr,
+         &p_cpm->ci_timeout);
+
+      if (ret == 0)
+      {
+         p_cpm->ci_running = true;
+      }
+      else
+      {
+         LOG_ERROR (
+            PF_CPM_LOG,
+            "CPM_DRV_MERA(%d): Timeout not started\n",
+            __LINE__);
+      }
+   }
+   else
+   {
+      LOG_ERROR (
+         PF_CPM_LOG,
+         "CPM_DRV_MERA(%d): Failed to start RTE\n",
+         __LINE__);
+   }
+
+   return ret;
+}
+
+/**
+ * Read data and iops of an iodata object in the
+ * active RTE frame.
+ * TODO - behaviour for iodata mapped to QSPI is tbd
+ */
+static int pf_cpm_drv_lan9662_get_data_and_iops (
+   pnet_t * net,
+   pf_iocr_t * p_iocr,
+   const pf_iodata_object_t * p_iodata,
+   bool * p_new_flag,
+   uint8_t * p_data,
+   uint16_t data_len,
+   uint8_t * p_iops,
+   uint8_t iops_len)
+{
+   int ret = -1;
+
+   ret = pf_mera_cpm_read_data_and_iops (
+      net,
+      p_iocr->cpm.frame,
+      p_iodata->slot_nbr,
+      p_iodata->subslot_nbr,
+      p_new_flag,
+      p_data,
+      data_len,
+      p_iops,
+      iops_len);
+
+   return ret;
+}
+
+/**
+ * Read the consumer status for an iodata object
+ * in the active RTE frame.
+ * @return 0 on success, -1 on error
+ */
+static int pf_cpm_drv_lan9662_get_iocs (
+   pnet_t * net,
+   pf_iocr_t * p_iocr,
+   const pf_iodata_object_t * p_iodata,
+   uint8_t * p_iocs,
+   uint8_t iocs_len)
+{
+   return pf_mera_cpm_read_iocs (
+      net,
+      p_iocr->cpm.frame,
+      p_iodata->slot_nbr,
+      p_iodata->subslot_nbr,
+      p_iocs,
+      iocs_len);
+}
+
+/**
+ * Read data_status for active CPM frame.
+ * @param p_cpm            In: CPM instance
+ * @param p_data_status    InOt: Ref to data_status value
+ * @return 0 on success, -1 on error
+ */
+
+static int pf_cpm_drv_lan9662_get_data_status (
+   const pf_cpm_t * p_cpm,
+   uint8_t * p_data_status)
+{
+   return pf_mera_cpm_read_data_status (p_cpm->frame, p_data_status);
+}
+
+/**
+ * Show the current state of the driver
+ * @param net     In: The p-net stack instance
+ * @param p_ppm   In: CPM instance
+ */
+static void pf_cpm_drv_lan9662_show (const pnet_t * net, const pf_cpm_t * p_cpm)
+{
+   printf ("Not implemented:  pf_cpm_drv_mera_show()\n");
+}
+
+int pf_driver_cpm_init (pnet_t * net)
+{
+   static const pf_cpm_driver_t drv = {
+      .create = pf_cpm_drv_lan9662_create,
+      .activate_req = pf_cpm_drv_lan9662_activate_req,
+      .close_req = pf_cpm_drv_lan9662_close_req,
+      .get_data_and_iops = pf_cpm_drv_lan9662_get_data_and_iops,
+      .get_iocs = pf_cpm_drv_lan9662_get_iocs,
+      .get_data_status = pf_cpm_drv_lan9662_get_data_status,
+      .show = pf_cpm_drv_lan9662_show};
+
+   net->cpm_drv = &drv;
+
+   LOG_INFO (
+      PF_CPM_LOG,
+      "CPM_DRV(%d): LAN9662 CPM driver installed\n",
+      __LINE__);
+   return 0;
+}

+ 2836 - 0
src/drivers/lan9662/src/pf_lan9662_mera.c

@@ -0,0 +1,2836 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#include "microchip/ethernet/rte/api.h"
+
+#include "pf_includes.h"
+#include "pnet_driver_options.h"
+#include "pnet_lan9662_api.h"
+#include "pf_lan9662_mera.h"
+#include "pf_rte_uio.h"
+#include "pf_sram_uio.h"
+#include "pf_mera_trace.h"
+#include "pnal_filetools.h"
+
+#include <string.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+#define PF_MERA_GROUP_ID_NONE 0
+#define PF_MERA_GROUP_ID_PPM  1
+#define PF_MERA_GROUP_ID_CPM  2
+
+#define PF_MERA_INVALID_ID            0xFFFF
+#define PF_MERA_DATA_STATUS_MASK      0x35
+#define PF_MERA_SCRIPT_ARG_LEN        24
+#define PF_MERA_RTE_INFO_PERIOD_IN_US 30000000
+#define PF_MERA_POLL_INTERVAL_IN_US   1000000
+#define PF_MERA_PPM_IODATA_OFFSET     6   /* frame ID + vlan ID */
+#define PF_MERA_CPM_IODATA_OFFSET     2   /* frame ID + vlan ID */
+#define PF_MERA_RAL_TIME_OFFSET       100 /* Time in ns */
+#define PF_MERA_OB_RTP_TIME_OFFSET    300 /* Time in ns */
+
+/* Default behaviour is that pf_mera assigns SRAM address for iodata/subslot
+ * Enabling this define allows the application to set the SRAM address using
+ * rte configuration functions in P-Net API.
+ */
+#define PF_MERA_ALLOW_APPLICATION_SRAM 0
+
+/* Log enable / disable of features shared between PPM and CPM side */
+#define PF_MERA_LOG (PF_PPM_LOG | PF_CPM_LOG)
+
+typedef struct pf_mera_rte_cfg
+{
+   bool valid;
+   pnet_mera_rte_data_cfg_t cfg;
+} pf_mera_rte_cfg_t;
+
+/**
+ * RTE mapping of an iodata object.
+ * Same type is used for both inbound and outbound
+ * data.
+ */
+typedef struct pf_rte_object
+{
+   bool in_use;
+   pf_iodata_object_t * iodata;
+   mera_io_intf_t io_interface_type;
+   uint16_t sram_address;
+   uint16_t qspi_address;
+   const uint8_t * default_data;
+   mera_ob_dg_id_t ob_dg_id;
+   mera_ib_ra_id_t ib_ra_id;
+   mera_ib_ral_id_t ib_ral_id;
+   mera_buf_t ib_ral_buf;
+} pf_rte_object_t;
+
+/**
+ * RTE configuration for a complete frame.
+ * The frame can be either a PPM or a CPM frame.
+ * Includes information of the RTE configuration as
+ * well as an array with the configuration of each
+ * iodata object / subslot and their RTE configurations.
+ */
+typedef struct pf_mera_frame
+{
+   pf_drv_frame_t drv_frame;
+   bool used;   /* true when allocated */
+   bool active; /* true when RTE is started */
+   bool is_ppm; /* true if ppm / inbound RTE frame */
+   bool is_cpm; /* true if cpm / outbound RTE frame */
+
+   /* Configuration passed on allocation */
+   pf_mera_frame_cfg_t config;
+
+   /* General RTE configuration */
+   uint16_t port;
+   uint32_t interval; /* Frame read/write interval [ns] */
+   uint16_t vcam_id;
+   uint16_t vlan_idx;
+   mera_rtp_id_t rtp_id;
+
+   uint16_t sram_frame_address; /* Start address of SRAM frame */
+   uint16_t rte_data_offset;    /* RTE io-data offset. Different for inbound and
+                                   outbound configurations */
+
+   /* RTE configuration for individual iodata objects */
+   pf_rte_object_t rte_object[PNET_MAX_SLOTS][PNET_MAX_SUBSLOTS];
+
+   /* Inbound configuration */
+   uint16_t ib_ral_id_count;
+   uint16_t ib_ra_id_count;
+   /* Internal transfer QSPI -> SRAM */
+   mera_ob_wal_id_t internal_wal_id;
+
+   /* Outbound configuration */
+   mera_ob_wal_id_t ob_wal_id;
+   uint16_t ob_wa_count;
+   uint16_t ob_dg_count;
+
+   uint64_t rx_count;
+
+   /* A ref to the mera lib should not be part
+    * of the frame. But it is added to avoid updating
+    * a call chain including the block writer.
+    * Todo: Fix by adding net ref in CPM interface function
+    * get_data_status()
+    */
+   struct mera_inst * mera_lib;
+
+} pf_mera_frame_t;
+
+typedef struct pf_mera
+{
+   pf_drv_t hwo_drv;
+
+   struct mera_inst * mera_lib; /* MERA library ref */
+   pf_mera_frame_t frame[PNET_LAN9662_MAX_FRAMES];
+   pf_scheduler_handle_t sched_handle; /* Debug info timeout handle */
+   pf_scheduler_handle_t poll_handle;  /* Mera lib poll timeout handle */
+
+   /* Submodule RTE configuration set by calls to
+    * pnet_mera_input_set_rte_config and pnet_mera_out_set_rte_config.
+    * Should be part of frame, but application has to set configuration
+    * before cpm frame is created and config is stored until ppm/cpm
+    * are activated.
+    */
+   pf_mera_rte_cfg_t rte_data_cfg[PNET_MAX_SLOTS][PNET_MAX_SUBSLOTS];
+
+   /* RTE resource management */
+   struct
+   {
+      uint16_t base_id;
+      bool used[PNET_LAN9662_MAX_IDS];
+   } vcam_id;
+
+   struct
+   {
+      uint16_t base_id;
+      bool used[PNET_LAN9662_MAX_IDS];
+   } vlan_index;
+
+   struct
+   {
+      uint16_t base_id;
+      bool used[PNET_LAN9662_MAX_IDS];
+   } rtp;
+
+   struct
+   {
+      uint16_t base_id;
+      bool used[PNET_LAN9662_MAX_IDS];
+   } ral;
+
+   struct
+   {
+      uint16_t base_id;
+      bool used[PNET_LAN9662_MAX_IDS];
+   } wal;
+
+} pf_mera_t;
+
+/**
+ * Get string representation of RTE interface type
+ * @param t             In: RTE interface type
+ * @return String representation
+ */
+static const char * pf_mera_rte_data_type_to_str (pnet_mera_rte_data_type_t t)
+{
+   switch (t)
+   {
+   case PNET_MERA_DATA_TYPE_SRAM:
+      return "SRAM";
+   case PNET_MERA_DATA_TYPE_QSPI:
+      return "QSPI";
+   default:
+      return "UNSUPPORTED";
+   }
+}
+
+static const char * pf_mera_interface_type_to_str (mera_io_intf_t if_type)
+{
+   switch (if_type)
+   {
+   case MERA_IO_INTF_QSPI:
+      return "QSPI";
+   case MERA_IO_INTF_PI:
+      return "PI";
+   case MERA_IO_INTF_SRAM:
+      return "SRAM";
+   case MERA_IO_INTF_PCIE:
+      return "PCIE";
+   default:
+      return "UNKNOWN";
+   }
+}
+
+/**
+ * Get the subslot index
+ * The subslots numbers starts at 1 and DAP subslots for
+ * identity and port information at 0x8000. This
+ * function converts the subslot number to corresponding
+ * index in the management data structures.
+ *
+ * subslot   number      id
+ * slot 0
+ *   subslot 1           0
+ *   subslot 0x8000      1
+ *   subslot 0x8001      2
+ *   subslot 0x8002      3
+ * slot 1
+ *   subslot 1           0
+ *   subslot 2           1
+ * @param subslot       In: (Real) Subslot number
+ * @return Subslot index
+ */
+static uint16_t get_subslot_index (uint16_t subslot)
+{
+   uint16_t subslot_index;
+   if (subslot < PNET_SUBSLOT_DAP_INTERFACE_1_IDENT)
+   {
+      subslot_index = subslot - 1;
+   }
+   else
+   {
+      subslot_index = subslot - PNET_SUBSLOT_DAP_INTERFACE_1_IDENT + 1;
+   }
+
+   CC_ASSERT (subslot_index < PNET_MAX_SUBSLOTS);
+
+   return subslot_index;
+}
+
+pf_rte_object_t * get_rte_object (
+   pf_mera_frame_t * frame,
+   uint16_t slot,
+   uint16_t subslot)
+{
+   uint16_t subslot_index = get_subslot_index (subslot);
+
+   CC_ASSERT (frame != NULL);
+   CC_ASSERT (subslot < PNET_MAX_SLOTS);
+
+   return &frame->rte_object[slot][subslot_index];
+}
+
+/**
+ * Callback for mera library debug info
+ * Prints debug information using vprintf()
+ * @param fmt     In: Format string
+ * @param ...     In: argumet list
+ * @return Always returns 0
+ */
+static int pf_mera_printf_cb (const char * fmt, ...)
+{
+   va_list args;
+   va_start (args, fmt);
+   vprintf (fmt, args);
+   va_end (args);
+   return 0;
+}
+
+#if PNET_OPTION_LAN9662_SHOW_RTE_INFO
+/**
+ * Display RTE status using printf
+ * @param mera    In: mera lib reference
+ */
+static void pf_mera_rte_show (struct mera_inst * mera)
+{
+   mera_debug_info_t info;
+
+   info.group = MERA_DEBUG_GROUP_ALL;
+   info.rtp_id = 0;   /* All RTP IDs */
+   info.full = false; /* Only active IDs */
+   info.clear = true;
+
+   printf ("RTE Status:\n");
+   mera_debug_info_print (mera, pf_mera_printf_cb, &info);
+}
+#endif
+
+/**
+ * @internal
+ * Show current RTE configuration
+ * This is a callback for the scheduler.
+ * @param net              InOut: The p-net stack instance
+ * @param arg              In:    Handle to the LAN9662 driver.
+ * @param current_time     In:    The current system time, in microseconds,
+ *                                when the scheduler is started to execute
+ *                                stored tasks.
+ */
+#if PNET_OPTION_LAN9662_SHOW_RTE_INFO
+static void pf_mera_rte_show_periodic (
+   pnet_t * net,
+   void * arg,
+   uint32_t current_time)
+{
+
+   pf_mera_t * handle = (pf_mera_t *)arg;
+   pf_mera_rte_show (handle->mera_lib);
+
+   pf_scheduler_add (
+      net,
+      PF_MERA_RTE_INFO_PERIOD_IN_US,
+      pf_mera_rte_show_periodic,
+      handle,
+      &handle->sched_handle);
+}
+#endif
+
+/**
+ * @internal
+ * Poll mera lib / RTE
+ * This is a callback for the scheduler.
+ * @param net              InOut: The p-net stack instance
+ * @param arg              In:    Handle to the LAN9662 driver.
+ * @param current_time     In:    The current system time, in microseconds,
+ *                                when the scheduler is started to execute
+ *                                stored tasks.
+ */
+static void pf_mera_poll (pnet_t * net, void * arg, uint32_t current_time)
+{
+   pf_mera_t * handle = (pf_mera_t *)arg;
+
+   mera_poll (handle->mera_lib);
+
+   pf_scheduler_add (
+      net,
+      PF_MERA_POLL_INTERVAL_IN_US,
+      pf_mera_poll,
+      handle,
+      &handle->poll_handle);
+}
+
+/**
+ * Add vcam rule for an inbound RTE connection
+ * Calls the add_inbound_vcap_rule.sh script
+ *
+ * @param vcam_handle      In: VCAM handle
+ * @param vlan_id          In: VLAN ID
+ * @param mac_addr         In: MAC of management port
+ * @param rt_vlan_idx      In: RTE VLAN index
+ * @param frame_id         In: Profinet frame ID
+ * @param rtp_id           In: RTP ID
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ib_add_vcam_rule (
+   int vcam_handle,
+   uint16_t vlan_id,
+   pnet_ethaddr_t * mac_addr,
+   int rt_vlan_idx,
+   int frame_id,
+   int rtp_id)
+{
+   const char * argv[8];
+   char vlan_id_str[PF_MERA_SCRIPT_ARG_LEN];
+   char mac_addr_str[PF_MERA_SCRIPT_ARG_LEN];
+   char rt_vlan_idx_str[PF_MERA_SCRIPT_ARG_LEN];
+   char vcam_handle_str[PF_MERA_SCRIPT_ARG_LEN];
+   char frame_id_str[PF_MERA_SCRIPT_ARG_LEN];
+   char rtp_id_str[PF_MERA_SCRIPT_ARG_LEN];
+
+   snprintf (vlan_id_str, sizeof (vlan_id_str), "0x%x", vlan_id);
+   snprintf (
+      mac_addr_str,
+      sizeof (mac_addr_str),
+      "%02X:%02X:%02X:%02X:%02X:%02X",
+      mac_addr->addr[0],
+      mac_addr->addr[1],
+      mac_addr->addr[2],
+      mac_addr->addr[3],
+      mac_addr->addr[4],
+      mac_addr->addr[5]);
+
+   snprintf (rt_vlan_idx_str, sizeof (rt_vlan_idx_str), "%d", rt_vlan_idx);
+   snprintf (vcam_handle_str, sizeof (vcam_handle_str), "%d", vcam_handle);
+   snprintf (frame_id_str, sizeof (frame_id_str), "%d", frame_id);
+   snprintf (rtp_id_str, sizeof (rtp_id_str), "%d", rtp_id);
+
+   argv[0] = "add_inbound_vcap_rule.sh";
+   argv[1] = vcam_handle_str;
+   argv[2] = vlan_id_str;
+   argv[3] = mac_addr_str;
+   argv[4] = rt_vlan_idx_str;
+   argv[5] = frame_id_str;
+   argv[6] = rtp_id_str;
+   argv[7] = NULL;
+
+   return pnal_execute_script (argv);
+}
+
+/**
+ * Add vcam rule for an outbound RTE connection
+ * Calls the add_outbound_vcap_rule.sh script
+ *
+ * @param vcam_handle      In: VCAM handle
+ * @param vlan_id          In: VLAN ID
+ * @param mac_addr         In: MAC of management port
+ * @param rt_vlan_idx      In: RTE VLAN index
+ * @param frame_id         In: Profinet frame ID
+ * @param rtp_id           In: RTP ID
+ * @return 0 on success, -1 on error
+ */
+int ob_add_vcam_rule (
+   int vcam_handle,
+   uint16_t vlan_id,
+   pnet_ethaddr_t * mac_addr,
+   int rt_vlan_idx,
+   int frame_id,
+   int rtp_id)
+{
+   const char * argv[7];
+   char vlan_id_str[PF_MERA_SCRIPT_ARG_LEN];
+   char mac_addr_str[PF_MERA_SCRIPT_ARG_LEN];
+   char rt_vlan_idx_str[PF_MERA_SCRIPT_ARG_LEN];
+   char vcam_handle_str[PF_MERA_SCRIPT_ARG_LEN];
+   char frame_id_str[PF_MERA_SCRIPT_ARG_LEN];
+   char rtp_id_str[PF_MERA_SCRIPT_ARG_LEN];
+
+   snprintf (vlan_id_str, sizeof (vlan_id_str), "0x%x", vlan_id);
+   snprintf (
+      mac_addr_str,
+      sizeof (mac_addr_str),
+      "%02X:%02X:%02X:%02X:%02X:%02X",
+      mac_addr->addr[0],
+      mac_addr->addr[1],
+      mac_addr->addr[2],
+      mac_addr->addr[3],
+      mac_addr->addr[4],
+      mac_addr->addr[5]);
+   snprintf (rt_vlan_idx_str, sizeof (rt_vlan_idx_str), "%d", rt_vlan_idx);
+   snprintf (vcam_handle_str, sizeof (vcam_handle_str), "%d", vcam_handle);
+   snprintf (frame_id_str, sizeof (frame_id_str), "%d", frame_id);
+   snprintf (rtp_id_str, sizeof (rtp_id_str), "%d", rtp_id);
+
+   argv[0] = "add_outbound_vcap_rule.sh";
+   argv[1] = vcam_handle_str;
+   argv[2] = vlan_id_str;
+   argv[3] = rt_vlan_idx_str;
+   argv[4] = frame_id_str;
+   argv[5] = rtp_id_str;
+   argv[6] = mac_addr_str;
+   argv[7] = NULL;
+
+   return pnal_execute_script (argv);
+}
+
+/**
+ * Delete vcam rule
+ * Calls the delete_vcap_rule.sh script
+ * @param vcam_handle      In: VCAM handle
+ * @param vlan_id          In: VLAN ID
+ * @param rt_vlan_idx      In: RTE VLAN index
+ * @param frame_id         In: Profinet frame ID
+ * @param rtp_id           In: RTP ID
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_delete_vcam_rule (int vcam_handle)
+{
+   const char * argv[3];
+   char vcam_handle_str[16];
+
+   snprintf (vcam_handle_str, sizeof (vcam_handle_str), "%d", vcam_handle);
+
+   argv[0] = "del_vcap_rule.sh";
+   argv[1] = vcam_handle_str;
+   argv[2] = NULL;
+
+   return pnal_execute_script (argv);
+}
+
+/**
+ * Allocate a vcam ID for a RTE configuration
+ * @param handle     InOut: Ref to mera instance
+ * @return vcam ID, PF_MERA_INVALID_ID on error
+ */
+static uint16_t pf_mera_vcam_id_alloc (pf_mera_t * handle)
+{
+   uint16_t i;
+   CC_ASSERT (handle != NULL);
+   for (i = 0; i < NELEMENTS (handle->vcam_id.used); i++)
+   {
+      if (handle->vcam_id.used[i] == false)
+      {
+         handle->vcam_id.used[i] = true;
+         return handle->vcam_id.base_id + i;
+      }
+   }
+   LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed to alloc VCAM ID \n", __LINE__);
+   return PF_MERA_INVALID_ID;
+}
+
+/**
+ * Free a vcam ID
+ * @param handle     InOut: Ref to mera instance
+ * @param id         In: vcam ID
+ */
+static void pf_mera_vcam_id_free (pf_mera_t * handle, uint16_t id)
+{
+   CC_ASSERT (handle != NULL);
+   if (id != PF_MERA_INVALID_ID)
+   {
+      handle->vcam_id.used[id - handle->vcam_id.base_id] = false;
+   }
+}
+
+/**
+ * Allocate a vlan index for a RTE configuration
+ * @param handle     InOut: Ref to mera instance
+ * @return vlan index, PF_MERA_INVALID_ID on error
+ */
+static uint16_t pf_mera_vlan_index_alloc (pf_mera_t * handle)
+{
+   uint16_t i;
+   CC_ASSERT (handle != NULL);
+   for (i = 0; i < NELEMENTS (handle->vlan_index.used); i++)
+   {
+      if (handle->vlan_index.used[i] == false)
+      {
+         handle->vlan_index.used[i] = true;
+         return handle->vlan_index.base_id + i;
+      }
+   }
+   LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed to alloc vlan index\n", __LINE__);
+   return PF_MERA_INVALID_ID;
+}
+
+/**
+ * Free a vlan index
+ * @param handle     InOut: Ref to mera instance
+ * @param id         In: vlan index
+ */
+static void pf_mera_vlan_index_free (pf_mera_t * handle, uint16_t id)
+{
+   CC_ASSERT (handle != NULL);
+   if (id != PF_MERA_INVALID_ID)
+   {
+      handle->vlan_index.used[id - handle->vlan_index.base_id] = false;
+   }
+}
+
+/**
+ * Allocate a RTE ID
+ * @param handle     InOut: Ref to mera instance
+ * @return RTP ID, PF_MERA_INVALID_ID on error
+ */
+static uint16_t pf_mera_rtp_id_alloc (pf_mera_t * handle)
+{
+   uint16_t i;
+   CC_ASSERT (handle != NULL);
+
+   for (i = 0; i < NELEMENTS (handle->rtp.used); i++)
+   {
+      if (handle->rtp.used[i] == false)
+      {
+         handle->rtp.used[i] = true;
+         return handle->rtp.base_id + i;
+      }
+   }
+   LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed to allocate RTP ID\n", __LINE__);
+   return PF_MERA_INVALID_ID;
+}
+
+/**
+ * Free a RTP ID
+ * @param handle     InOut: Ref to mera instance
+ * @param id         In: RTP ID
+ */
+static void pf_mera_rtp_id_free (pf_mera_t * handle, uint16_t id)
+{
+   CC_ASSERT (handle != NULL);
+   if (id != PF_MERA_INVALID_ID)
+   {
+      handle->rtp.used[id - handle->rtp.base_id] = false;
+   }
+}
+
+/**
+ * Allocate a Read Action List (RAL) ID
+ * @param handle     InOut: Ref to mera instance
+ * @return ral ID, PF_MERA_INVALID_ID on error
+ */
+static uint16_t pf_mera_ral_id_alloc (pf_mera_t * handle)
+{
+   uint16_t i;
+   CC_ASSERT (handle != NULL);
+
+   for (i = 0; i < NELEMENTS (handle->ral.used); i++)
+   {
+      if (handle->ral.used[i] == false)
+      {
+         handle->ral.used[i] = true;
+         return handle->ral.base_id + i;
+      }
+   }
+   LOG_ERROR (PF_PPM_LOG, "MERA(%d): Failed to alloc RAL ID \n", __LINE__);
+   return PF_MERA_INVALID_ID;
+}
+
+/**
+ * Free a Read Action List (RAL) ID
+ * @param handle     InOut: Ref to mera instance
+ * @param id         In: ral ID
+ */
+static void pf_mera_ral_id_free (pf_mera_t * handle, uint16_t id)
+{
+   CC_ASSERT (handle != NULL);
+   if (id != PF_MERA_INVALID_ID)
+   {
+      handle->ral.used[id - handle->ral.base_id] = false;
+   }
+}
+
+/**
+ * Allocate a Write Action List (WAL) ID
+ * @param handle     InOut: Ref to mera instance
+ * @return wal ID, PF_MERA_INVALID_ID on error
+ */
+static uint16_t pf_mera_wal_id_alloc (pf_mera_t * handle)
+{
+   uint16_t i;
+   CC_ASSERT (handle != NULL);
+
+   for (i = 0; i < NELEMENTS (handle->wal.used); i++)
+   {
+      if (handle->wal.used[i] == false)
+      {
+         handle->wal.used[i] = true;
+         return handle->wal.base_id + i;
+      }
+   }
+   LOG_ERROR (PF_PPM_LOG, "MERA(%d): Failed to alloc WAL ID \n", __LINE__);
+   return PF_MERA_INVALID_ID;
+}
+
+/**
+ * Free a Write Action List (WAL) ID
+ * @param handle     InOut: Ref to mera instance
+ * @param id         In: wal ID
+ */
+static void pf_mera_wal_id_free (pf_mera_t * handle, uint16_t id)
+{
+   CC_ASSERT (handle != NULL);
+   if (id != PF_MERA_INVALID_ID)
+   {
+      handle->wal.used[id - handle->wal.base_id] = false;
+   }
+}
+
+/**
+ * Get the wal id from a CPM frame using the iocr ref.
+ * @param handle           In: Ref to mera instance
+ * @param iocr             In: IOCR reference
+ * @param wal_id           Out: Write Action List Id
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_get_cpm_wal_id (
+   pf_mera_t * handle,
+   pf_iocr_t * iocr,
+   mera_ob_wal_id_t * wal_id)
+{
+   uint16_t i;
+   for (i = 0; i < PNET_LAN9662_MAX_FRAMES; i++)
+   {
+      if (
+         handle->frame[i].used == true &&
+         handle->frame[i].config.p_iocr->p_ar == iocr->p_ar &&
+         handle->frame[i].is_cpm)
+      {
+         *wal_id = handle->frame[i].ob_wal_id;
+         return 0;
+      }
+   }
+   return -1;
+}
+/**
+ * Add RTE data group for an iodata object.
+ * Maps the data source to frame/pdu offset.
+ * @param mera_lib   In: Ref to mera lib instance
+ * @param frame      In: Ref to frame
+ * @param entry      In: Iodata object configuration
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ib_add_dg (
+   struct mera_inst * mera_lib,
+   pf_mera_frame_t * frame,
+   pf_rte_object_t * entry)
+{
+   int err;
+   mera_ib_dg_conf_t ib_dg_cfg;
+
+   err = mera_ib_dg_init (&ib_dg_cfg);
+   if (err)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to init data group (dg)\n",
+         __LINE__);
+      return -1;
+   }
+
+   ib_dg_cfg.rtp_id = frame->rtp_id;
+   ib_dg_cfg.pdu_offset = entry->iodata->data_offset + frame->rte_data_offset;
+   ib_dg_cfg.valid_offset = entry->iodata->iops_offset + frame->rte_data_offset;
+   if (entry->io_interface_type == MERA_IO_INTF_SRAM)
+   {
+      /* In SRAM mode the IOPS is handled by SW */
+      ib_dg_cfg.valid_update = false;
+   }
+   else
+   {
+      ib_dg_cfg.valid_update = true;
+   }
+
+   ib_dg_cfg.opc_seq_update = false;
+   ib_dg_cfg.opc_code_update = false;
+
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "MERA(%d): mera_ib_dg_add "
+      "ral_id:%d "
+      "ra_id:%d "
+      "cfg.rtp_id:%d "
+      "cfg.pdu_offset:%d "
+      "cfg.valid_offset:%d\n",
+      __LINE__,
+      entry->ib_ral_id,
+      entry->ib_ra_id,
+      ib_dg_cfg.rtp_id,
+      ib_dg_cfg.pdu_offset,
+      ib_dg_cfg.valid_offset);
+
+   err =
+      mera_ib_dg_add (mera_lib, entry->ib_ral_id, entry->ib_ra_id, &ib_dg_cfg);
+   if (err)
+   {
+      LOG_ERROR (
+         PF_PPM_LOG,
+         "MERA(%d): Failed to add inbound data group\n",
+         __LINE__);
+      return err;
+   }
+
+   return err;
+}
+/**
+ * Convert P-Net interface data type to mera lib type.
+ * @param pnet_if_type     In: P-Net interface type
+ * @return mera lib interface type
+ */
+static mera_io_intf_t pf_mera_interface_type (
+   pnet_mera_rte_data_type_t pnet_if_type)
+{
+   switch (pnet_if_type)
+   {
+   case PNET_MERA_DATA_TYPE_SRAM:
+      return MERA_IO_INTF_SRAM;
+   case PNET_MERA_DATA_TYPE_QSPI:
+      return MERA_IO_INTF_QSPI;
+   default:
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Unsupported interface type\n",
+         __LINE__);
+      exit (EXIT_FAILURE);
+   }
+}
+
+/**
+ * Add RTE read action for an iodata object.
+ * Configures the data source
+ * @param mera_lib   In: Ref to mera lib instance
+ * @param entry      In: Iodata object configuration
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ib_add_ra (
+   struct mera_inst * mera_lib,
+   pf_rte_object_t * entry)
+{
+   mera_ib_ra_conf_t ib_ra_cfg;
+   mera_ib_ra_ctrl_t ctrl;
+
+   if (mera_ib_ra_init (&ib_ra_cfg) != 0)
+   {
+      LOG_ERROR (PF_PPM_LOG, "MERA(%d): Failed to init read action\n", __LINE__);
+      return -1;
+   }
+
+   ib_ra_cfg.ra_id = entry->ib_ra_id;
+   ib_ra_cfg.rd_addr.intf = entry->io_interface_type;
+   ib_ra_cfg.rd_addr.addr = entry->sram_address;
+   if (entry->io_interface_type == MERA_IO_INTF_QSPI)
+   {
+      ib_ra_cfg.rd_addr.addr = entry->qspi_address;
+   }
+   ib_ra_cfg.length = entry->iodata->data_length;
+
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "MERA(%d): mera_ib_ra_add "
+      "ral_id:%d "
+      "cfg.ra_id:%d "
+      "cfg.rd_addr.intf:%d-%s "
+      "cfg.rd_addr.addr: 0x%x "
+      "cfg.length:%d\n",
+      __LINE__,
+      entry->ib_ral_id,
+      entry->ib_ra_id,
+      ib_ra_cfg.rd_addr.intf,
+      pf_mera_interface_type_to_str (ib_ra_cfg.rd_addr.intf),
+      ib_ra_cfg.rd_addr.addr,
+      ib_ra_cfg.length);
+
+   if (mera_ib_ra_add (mera_lib, entry->ib_ral_id, &ib_ra_cfg) != 0)
+   {
+      LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed to add read action\n", __LINE__);
+      return -1;
+   }
+
+   if (ib_ra_cfg.rd_addr.intf == MERA_IO_INTF_QSPI)
+   {
+      ctrl.enable = true;
+      if (
+         mera_ib_ra_ctrl_set (
+            mera_lib,
+            entry->ib_ral_id,
+            entry->ib_ra_id,
+            &ctrl) != 0)
+      {
+         LOG_ERROR (
+            PF_PPM_LOG,
+            "MERA(%d): mera_ib_ra_ctrl_set() error\n",
+            __LINE__);
+         return -1;
+      }
+   }
+
+   return 0;
+}
+
+/**
+ * Add RTE read action list for an iodata object.
+ * Configures when data source shall be read.
+ * @param mera_lib       In: Ref to mera lib instance
+ * @param entry          In: Iodata object configuration
+ * @param interval_in_ns In: Read interval in nanoseconds
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ib_add_ral (
+   struct mera_inst * mera_lib,
+   pf_rte_object_t * entry,
+   uint32_t interval_in_ns)
+{
+   mera_ib_ral_conf_t ral_cfg;
+
+   ral_cfg.time.offset = PF_MERA_RAL_TIME_OFFSET * entry->ib_ral_id;
+   ral_cfg.time.interval = interval_in_ns;
+
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "MERA(%d): mera_ib_ral_conf_set "
+      "ral_id:%d "
+      "cfg.time.offset:%d(ns) "
+      "cfg.time.interval:%d(ns)\n",
+      __LINE__,
+      entry->ib_ral_id,
+      ral_cfg.time.offset,
+      ral_cfg.time.interval);
+
+   if (mera_ib_ral_conf_set (mera_lib, entry->ib_ral_id, &ral_cfg) != 0)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to set read action configuration\n",
+         __LINE__);
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * Add RTE configuration for an inbound iodata object.
+ * @param handle     In: Ref to mera instance
+ * @param frame      In: Ref to frame
+ * @param entry      In: Iodata object configuration
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_add_ib_entry (
+   pf_mera_t * handle,
+   pf_mera_frame_t * frame,
+   pf_rte_object_t * entry)
+{
+   mera_ob_wa_conf_t wa_cfg;
+   entry->in_use = true;
+
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "MERA(%d): [%u,%u] Configure inbound data entry\n",
+      __LINE__,
+      entry->iodata->slot_nbr,
+      entry->iodata->subslot_nbr);
+
+   entry->ib_ral_id = pf_mera_ral_id_alloc (handle);
+
+   if (pf_mera_ib_add_ra (handle->mera_lib, entry) != 0)
+   {
+      LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed ta add read action\n", __LINE__);
+      return -1;
+   }
+
+   if (pf_mera_ib_add_dg (handle->mera_lib, frame, entry) != 0)
+   {
+      LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed ta add data group\n", __LINE__);
+      return -1;
+   }
+
+   if (pf_mera_ib_add_ral (handle->mera_lib, entry, frame->interval) != 0)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed ta add read action list\n",
+         __LINE__);
+      return -1;
+   }
+
+   if (entry->io_interface_type == MERA_IO_INTF_QSPI)
+   {
+      LOG_DEBUG (
+         PF_MERA_LOG,
+         "MERA(%d): [%u,%u] Add internal transfer QSPI 0x%x -> SRAM 0x%x\n",
+         __LINE__,
+         entry->iodata->slot_nbr,
+         entry->iodata->subslot_nbr,
+         entry->qspi_address,
+         entry->sram_address);
+
+      if (mera_ob_wa_init (&wa_cfg) != 0)
+      {
+         LOG_ERROR (
+            PF_MERA_LOG,
+            "MERA(%d): Failed init internal write action\n",
+            __LINE__);
+         return 0;
+      }
+
+      wa_cfg.internal = true;
+      /* wa_cfg.rtp_id not used for internal transfers */
+      /* wa_cfg.grp_id not used for internal transfers */
+      /* wa_cfg.dg_id  not used for internal transfers */
+
+      /* Read address */
+      wa_cfg.rd_addr.intf = MERA_IO_INTF_QSPI;
+      wa_cfg.rd_addr.addr = entry->qspi_address;
+
+      /* Write address */
+      wa_cfg.wr_addr.intf = MERA_IO_INTF_SRAM;
+      wa_cfg.wr_addr.addr = entry->sram_address;
+
+      wa_cfg.length = entry->iodata->data_length;
+
+      if (mera_ob_wa_add (handle->mera_lib, frame->internal_wal_id, &wa_cfg) != 0)
+      {
+         LOG_ERROR (
+            PF_MERA_LOG,
+            "MERA(%d): Failed to add internal write action\n",
+            __LINE__);
+         return -1;
+      }
+   }
+
+   return 0;
+}
+
+/**
+ * Set the general RTE configuration for a PPM frame.
+ * @param handle     In: Ref to mera instance
+ * @param frame      In: Ref to frame
+ * @param entry      In: Iodata object configuration
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ib_rtp_config (pf_mera_t * handle, pf_mera_frame_t * frame)
+{
+   int err = 0;
+   mera_ib_rtp_conf_t ib_rtp_config = {0};
+   pf_ppm_t * p_ppm = &frame->config.p_iocr->ppm;
+
+   ib_rtp_config.type = MERA_RTP_TYPE_PN;
+   ib_rtp_config.grp_id = PF_MERA_GROUP_ID_NONE;
+   ib_rtp_config.mode = MERA_RTP_IB_MODE_INJ;
+   ib_rtp_config.time.offset = MERA_TIME_OFFSET_NONE;
+   ib_rtp_config.time.interval = frame->interval;
+   ib_rtp_config.port = frame->port;
+   ib_rtp_config.length = ((pnal_buf_t *)p_ppm->p_send_buffer)->len;
+
+   memcpy (
+      ib_rtp_config.data,
+      ((pnal_buf_t *)p_ppm->p_send_buffer)->payload,
+      ((pnal_buf_t *)p_ppm->p_send_buffer)->len);
+
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "MERA(%d): mera_ib_rtp_conf_set "
+      "rtp_id:%d "
+      "type: %d "
+      "grp_id:%d "
+      "mode:%d "
+      "time.offset:%d "
+      "time.interval:%d "
+      "port:%d "
+      "length:%d\n",
+      __LINE__,
+      frame->rtp_id,
+      ib_rtp_config.type,
+      ib_rtp_config.grp_id,
+      ib_rtp_config.mode,
+      ib_rtp_config.time.offset,
+      ib_rtp_config.time.interval,
+      ib_rtp_config.port,
+      ib_rtp_config.length);
+
+   err = mera_ib_rtp_conf_set (handle->mera_lib, frame->rtp_id, &ib_rtp_config);
+   if (err)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to set inbound RTP configuration\n",
+         __LINE__);
+   }
+   return err;
+}
+
+/**
+ * Configure and start a RTE configuration for a PPM frame.
+ * Sets the general RTE configuration and the configuration
+ * for each iodata object in the communication relationship.
+ * @param handle     In: Ref to mera instance
+ * @param frame      In: Ref to frame
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ppm_start_rte (pf_mera_t * handle, pf_mera_frame_t * frame)
+{
+   int err = 0;
+   pf_iocr_t * p_iocr = frame->config.p_iocr;
+   uint16_t slot, subslot;
+
+   err = pf_mera_ib_add_vcam_rule (
+      frame->vcam_id,
+      p_iocr->param.iocr_tag_header.vlan_id,
+      &frame->config.main_mac_addr, /* MAC of sending interface */
+      frame->vlan_idx,
+      p_iocr->param.frame_id,
+      frame->rtp_id);
+
+   if (err != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to add inbound vcam rule\n",
+         __LINE__);
+      return err;
+   }
+
+   err = pf_mera_ib_rtp_config (handle, frame);
+   if (err != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to set inbound RTP configuration\n",
+         __LINE__);
+      return err;
+   }
+
+   err = pf_mera_get_cpm_wal_id (
+      handle,
+      frame->config.p_iocr,
+      &frame->internal_wal_id);
+   if (err != 0)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to get PPM internal WAL id from CPM\n",
+         __LINE__);
+      return err;
+   }
+
+   for (slot = 0; slot < PNET_MAX_SLOTS; slot++)
+   {
+      for (subslot = 0; subslot < PNET_MAX_SUBSLOTS; subslot++)
+      {
+         if (
+            frame->rte_object[slot][subslot].iodata != NULL &&
+            frame->rte_object[slot][subslot].iodata->data_length > 0)
+         {
+            err = pf_mera_add_ib_entry (
+               handle,
+               frame,
+               &frame->rte_object[slot][subslot]);
+
+            if (err != 0)
+            {
+               return err;
+            }
+         }
+      }
+   }
+
+   frame->active = true;
+
+   return err;
+}
+
+int pf_driver_init (pnet_t * net)
+{
+   pf_mera_t * handle;
+   mera_cb_t mera_lib_cb = {
+      .reg_rd = pf_rte_uio_reg_read,
+      .reg_wr = pf_rte_uio_reg_write,
+      .lock = NULL,
+      .unlock = NULL,
+      .trace_printf = pf_mera_callout_trace_printf,
+      .trace_hex_dump = NULL};
+
+   LOG_DEBUG (PF_MERA_LOG, "MERA(%d): Init RTE UIO driver\n", __LINE__);
+
+   if (pf_rte_uio_init() != 0)
+   {
+      LOG_FATAL (
+         PF_PPM_LOG,
+         "MERA(%d): RTE UIO driver initialization error\n",
+         __LINE__);
+      exit (EXIT_FAILURE);
+   }
+
+   LOG_DEBUG (PF_MERA_LOG, "MERA(%d): Init SRAM UIO driver\n", __LINE__);
+   if (pf_sram_init() != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): SRAM UIO driver initialization error\n",
+         __LINE__);
+      exit (EXIT_FAILURE);
+   }
+
+   handle = (pf_mera_t *)malloc (sizeof (pf_mera_t));
+   CC_ASSERT (handle != NULL);
+   memset (handle, 0, sizeof (pf_mera_t));
+
+   handle->vcam_id.base_id =
+      net->p_fspm_default_cfg->driver_config.mera.vcam_base_id;
+   handle->rtp.base_id =
+      net->p_fspm_default_cfg->driver_config.mera.rtp_base_id;
+   handle->ral.base_id =
+      net->p_fspm_default_cfg->driver_config.mera.ral_base_id;
+   handle->wal.base_id =
+      net->p_fspm_default_cfg->driver_config.mera.wal_base_id;
+
+   handle->mera_lib = mera_create (&mera_lib_cb);
+   if (handle->mera_lib == NULL)
+   {
+      LOG_FATAL (PF_MERA_LOG, "MERA(%d): Failed to init MERA lib\n", __LINE__);
+      exit (EXIT_FAILURE);
+   }
+
+   /* Start mera poll timeout */
+   pf_scheduler_init_handle (&handle->poll_handle, "mera_poll");
+   if (
+      pf_scheduler_add (
+         net,
+         PF_MERA_POLL_INTERVAL_IN_US,
+         pf_mera_poll,
+         handle,
+         &handle->poll_handle) != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to start poll timer\n",
+         __LINE__);
+      exit (EXIT_FAILURE);
+   }
+
+   handle->hwo_drv.initialized = true;
+   net->hwo_drv = (pf_drv_t *)handle;
+
+   return 0;
+};
+
+/**
+ * Allocate a mera frame
+ * The frame contains all required RTE configurations for a PPM or CPM.
+ * Iodata interface type is initialized to SRAM.
+ * @param handle     In: Ref to mera instance
+ * @return Allocated frame, NULL on error
+ */
+static pf_mera_frame_t * pf_mera_alloc_frame (pf_mera_t * handle)
+{
+   uint16_t i, j, k;
+   pf_mera_frame_t * frame = NULL;
+
+   for (i = 0; i < PNET_LAN9662_MAX_FRAMES; i++)
+   {
+      if (handle->frame[i].used == false)
+      {
+         frame = &handle->frame[i];
+         memset (frame, 0, sizeof (*frame));
+         frame->drv_frame.initialized = true;
+         frame->used = true;
+         frame->active = false;
+         frame->ob_dg_count = 1;
+
+         for (j = 0; j < PNET_MAX_SLOTS; j++)
+         {
+            for (k = 0; k < PNET_MAX_SUBSLOTS; k++)
+            {
+               frame->rte_object[j][k].ib_ral_id = PF_MERA_INVALID_ID;
+               frame->rte_object[j][k].io_interface_type = MERA_IO_INTF_SRAM;
+               frame->rte_object[j][k].sram_address = PF_SRAM_INVALID_ADDRESS;
+               frame->mera_lib = handle->mera_lib;
+            }
+         }
+         LOG_DEBUG (
+            PF_MERA_LOG,
+            "MERA(%d): Frame allocated (index=%d)\n",
+            __LINE__,
+            i);
+         return frame;
+      }
+   }
+
+   LOG_FATAL (PF_MERA_LOG, "MERA(%d): Failed to allocate frame\n", __LINE__);
+   exit (EXIT_FAILURE);
+   return NULL;
+}
+
+bool pf_mera_is_active_frame (pf_drv_frame_t * frame)
+{
+   return ((pf_mera_frame_t *)frame)->active;
+}
+
+/**
+ * Free a mera frame
+ * @param handle     InOut: Ref to mera instance
+ * @param frame      In: The frame to free
+ */
+static void pf_mera_free_frame (pf_mera_t * handle, pf_mera_frame_t * frame)
+{
+   if (frame != NULL)
+   {
+      frame->used = false;
+      return;
+   }
+   LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed to free frame\n", __LINE__);
+}
+
+pf_drv_frame_t * pf_mera_ppm_alloc (
+   pnet_t * net,
+   const pf_mera_frame_cfg_t * cfg)
+{
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   pf_mera_frame_t * frame = NULL;
+   pf_iodata_object_t * p_iodata;
+   pf_rte_object_t * entry;
+   uint16_t i;
+   pf_ppm_t * ppm;
+
+   if (cfg == NULL || cfg->p_iocr == NULL || handle == NULL)
+   {
+      return NULL;
+   }
+
+   frame = pf_mera_alloc_frame (handle);
+   if (frame == NULL)
+   {
+      return NULL;
+   }
+
+   frame->is_ppm = true;
+
+   frame->sram_frame_address = pf_sram_frame_alloc();
+   if (frame->sram_frame_address == 0)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to allocate SRAM frame\n",
+         __LINE__);
+      pf_mera_free_frame (handle, frame);
+      return NULL;
+   }
+
+   LOG_DEBUG (
+      PF_MERA_LOG,
+      "MERA(%d): Allocated PPM SRAM frame at 0x%x\n",
+      __LINE__,
+      frame->sram_frame_address);
+
+   frame->config = *cfg;
+   frame->port = cfg->port;
+
+   ppm = &frame->config.p_iocr->ppm;
+   frame->interval =
+      (1000000 * ppm->send_clock_factor * ppm->reduction_ratio) / 32;
+
+   for (i = 0; i < frame->config.p_iocr->nbr_data_desc; i++)
+   {
+      p_iodata = &frame->config.p_iocr->data_desc[i];
+      if (p_iodata->data_length > 0)
+      {
+         entry =
+            get_rte_object (frame, p_iodata->slot_nbr, p_iodata->subslot_nbr);
+         entry->iodata = p_iodata;
+      }
+   }
+
+   frame->vcam_id = pf_mera_vcam_id_alloc (handle);
+   frame->rtp_id = pf_mera_rtp_id_alloc (handle);
+   /* frame->internal_wal_id assigned later */
+   frame->vlan_idx = pf_mera_vlan_index_alloc (handle);
+   frame->rte_data_offset = PF_MERA_PPM_IODATA_OFFSET;
+
+   LOG_DEBUG (PF_MERA_LOG, "MERA(%d): PPM frame allocated\n", __LINE__);
+   return (pf_drv_frame_t *)frame;
+}
+
+int pf_mera_ppm_free (pnet_t * net, pf_drv_frame_t * drv_frame)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   LOG_DEBUG (PF_PPM_LOG, "MERA(%d): Free PPM frame\n", __LINE__);
+   if (frame != NULL)
+   {
+      pf_mera_vcam_id_free (handle, frame->vcam_id);
+      pf_mera_vlan_index_free (handle, frame->vlan_idx);
+      pf_mera_rtp_id_free (handle, frame->rtp_id);
+      pf_sram_frame_free (frame->sram_frame_address);
+      pf_mera_free_frame (handle, frame);
+      return 0;
+   }
+   LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed to free PPM frame\n", __LINE__);
+   return -1;
+}
+
+/**
+ * Generate RTE configurations for all iodata objects in a PPM type
+ * of communication relation.
+ * @param handle     InOut: Ref to mera instance
+ * @param frame      InOut: The PPM frame
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ppm_config (pf_mera_t * handle, pf_mera_frame_t * frame)
+{
+   int ret = 0;
+   uint16_t slot, subslot;
+   pf_rte_object_t * entry;
+   const pf_mera_rte_cfg_t * entry_rte_data_cfg; /* qspi address */
+
+   LOG_DEBUG (PF_PPM_LOG, "MERA(%d): Apply RTE configuration (PPM)\n", __LINE__);
+
+   for (slot = 0; slot < PNET_MAX_SLOTS; slot++)
+   {
+      for (subslot = 0; subslot < PNET_MAX_SUBSLOTS; subslot++)
+      {
+         if (
+            frame->rte_object[slot][subslot].iodata != NULL &&
+            frame->rte_object[slot][subslot].iodata->data_length > 0)
+         {
+            entry = &frame->rte_object[slot][subslot];
+            entry_rte_data_cfg = &handle->rte_data_cfg[slot][subslot];
+
+            entry->ib_ra_id = frame->ib_ra_id_count++;
+
+            if (entry_rte_data_cfg->valid)
+            {
+               entry->io_interface_type =
+                  pf_mera_interface_type (entry_rte_data_cfg->cfg.type);
+
+#if PF_MERA_ALLOW_APPLICATION_SRAM
+               if (entry_rte_data_cfg->cfg.type == PNET_MERA_DATA_TYPE_SRAM)
+               {
+                  entry->sram_address = entry_rte_data_cfg->cfg.address;
+               }
+#endif
+               entry->qspi_address = entry_rte_data_cfg->cfg.address;
+               entry->default_data = entry_rte_data_cfg->cfg.default_data;
+            }
+
+            if (entry->sram_address == PF_SRAM_INVALID_ADDRESS)
+            {
+               entry->sram_address = frame->sram_frame_address +
+                                     frame->rte_data_offset +
+                                     entry->iodata->data_offset;
+            }
+
+            LOG_DEBUG (
+               PF_PPM_LOG,
+               "MERA(%d): [%u,%u] Interface: %s Start address: 0x%x Size: %d\n",
+               __LINE__,
+               entry->iodata->slot_nbr,
+               entry->iodata->subslot_nbr,
+               pf_mera_interface_type_to_str (entry->io_interface_type),
+               entry->io_interface_type == MERA_IO_INTF_QSPI
+                  ? entry->qspi_address
+                  : entry->sram_address,
+               entry->iodata->data_length);
+         }
+      }
+   }
+
+   return ret;
+}
+
+int pf_mera_ppm_start (pnet_t * net, pf_drv_frame_t * drv_frame)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   if (handle == NULL || frame == NULL || frame->config.p_iocr == NULL)
+   {
+      LOG_ERROR (
+         PF_PPM_LOG,
+         "MERA(%d): Failed to start RTE - parameter error\n",
+         __LINE__);
+      return -1;
+   }
+
+   if (pf_mera_ppm_config (handle, frame) != 0)
+   {
+      LOG_ERROR (PF_PPM_LOG, "MERA(%d): Failed to configure RTE\n", __LINE__);
+      return -1;
+   }
+
+   if (pf_mera_ppm_start_rte (handle, frame) != 0)
+   {
+      LOG_ERROR (PF_PPM_LOG, "MERA(%d): Failed to start RTE\n", __LINE__);
+      return -1;
+   }
+
+#if PNET_OPTION_LAN9662_SHOW_RTE_INFO
+   pf_scheduler_init_handle (&handle->sched_handle, "mera_show");
+   if (
+      pf_scheduler_add (
+         net,
+         2000000,
+         pf_mera_rte_show_periodic,
+         handle,
+         &handle->sched_handle) != 0)
+   {
+      LOG_FATAL (PF_MERA_LOG, "MERA(%d): Failed to add timer\n", __LINE__);
+      exit (EXIT_FAILURE);
+   }
+#endif
+
+   return 0;
+}
+
+int pf_mera_ppm_write (
+   pnet_t * net,
+   pf_drv_frame_t * drv_frame,
+   uint32_t offset,
+   const uint8_t * data,
+   uint32_t len)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   uint16_t i;
+   mera_ib_rtp_data_t write_req;
+   int ret;
+
+   if (frame->active)
+   {
+      for (i = 0; i < len; i++)
+      {
+         write_req.value = data[i];
+         write_req.offset = offset + i;
+         ret =
+            mera_ib_rtp_data_set (handle->mera_lib, frame->rtp_id, &write_req);
+
+         if (ret != 0)
+         {
+            LOG_ERROR (
+               PF_MERA_LOG,
+               "MERA(%d): Failed to write frame RTP frame buffer\n",
+               __LINE__);
+            return -1;
+         }
+      }
+   }
+
+   return 0;
+}
+
+int pf_mera_ppm_write_input (
+   pnet_t * net,
+   pf_drv_frame_t * drv_frame,
+   const pf_iodata_object_t * p_iodata,
+   const uint8_t * data,
+   uint32_t len)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   int ret = -1;
+   pf_rte_object_t * entry;
+
+   if (!frame->active)
+   {
+      return -1;
+   }
+
+   entry = get_rte_object (frame, p_iodata->slot_nbr, p_iodata->subslot_nbr);
+
+   switch (entry->io_interface_type)
+   {
+   case MERA_IO_INTF_SRAM:
+      if (
+         mera_ib_ral_req (
+            handle->mera_lib,
+            entry->ib_ral_id,
+            &entry->ib_ral_buf) == 0)
+      {
+         ret = pf_sram_write (
+            entry->sram_address + entry->ib_ral_buf.addr,
+            data,
+            len);
+
+         if (mera_ib_ral_rel (handle->mera_lib, entry->ib_ral_id) != 0)
+         {
+            ret = -1;
+            LOG_ERROR (
+               PF_MERA_LOG,
+               "MERA(%d): Releasing RAL buffer failed\n",
+               __LINE__);
+         }
+      }
+      else
+      {
+         ret = -1;
+         LOG_ERROR (
+            PF_MERA_LOG,
+            "MERA(%d): Request for RAL buffer failed\n",
+            __LINE__);
+      }
+
+      break;
+   case PNET_MERA_DATA_TYPE_QSPI:
+   default:
+      /* Write not supported */
+      return 0;
+   }
+   return ret;
+}
+
+int pf_mera_ppm_read_data_and_iops (
+   pnet_t * net,
+   pf_drv_frame_t * drv_frame,
+   const pf_iodata_object_t * p_iodata,
+   uint8_t * p_data,
+   uint16_t data_len,
+   uint8_t * p_iops,
+   uint8_t iops_len)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   int err = 0;
+   pf_rte_object_t * entry;
+   mera_buf_t internal_wal_buf;
+
+   *p_iops = PNET_IOXS_GOOD;
+
+   if (!frame->active)
+   {
+      return -1;
+   }
+
+   if (p_iodata->slot_nbr == PNET_SLOT_DAP_IDENT)
+   {
+      return 0;
+   }
+
+   if (data_len == 0 || iops_len == 0)
+   {
+      return 0;
+   }
+
+   entry = get_rte_object (frame, p_iodata->slot_nbr, p_iodata->subslot_nbr);
+
+   switch (entry->io_interface_type)
+   {
+   case MERA_IO_INTF_SRAM:
+      if (pf_sram_read (entry->sram_address, p_data, data_len) != 0)
+      {
+         LOG_ERROR (
+            PF_MERA_LOG,
+            "MERA(%d): [%u,%u] Failed to read SRAM (input) \n",
+            __LINE__,
+            p_iodata->slot_nbr,
+            p_iodata->subslot_nbr);
+
+         return -1;
+      }
+      break;
+
+   case MERA_IO_INTF_QSPI:
+      err = mera_ob_wal_req (
+         handle->mera_lib,
+         frame->internal_wal_id,
+         &internal_wal_buf);
+      if (err != 0)
+      {
+         LOG_ERROR (
+            PF_MERA_LOG,
+            "MERA(%d): Failed to req wal buffer for internal transfer\n",
+            __LINE__);
+         return err;
+      }
+      if (
+         pf_sram_read (
+            entry->sram_address + internal_wal_buf.addr,
+            p_data,
+            data_len) != 0)
+      {
+         mera_ob_wal_rel (handle->mera_lib, frame->internal_wal_id);
+         LOG_ERROR (
+            PF_MERA_LOG,
+            "MERA(%d): [%u,%u] Failed to read SRAM (input) \n",
+            __LINE__,
+            p_iodata->slot_nbr,
+            p_iodata->subslot_nbr);
+
+         return -1;
+      }
+      mera_ob_wal_rel (handle->mera_lib, frame->internal_wal_id);
+      break;
+
+   default:
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): [%u,%u] unsupported interface type\n",
+         __LINE__,
+         p_iodata->slot_nbr,
+         p_iodata->subslot_nbr);
+      return -1;
+   }
+   return 0;
+}
+
+int pf_mera_ppm_write_iops (
+   pnet_t * net,
+   pf_drv_frame_t * drv_frame,
+   const pf_iodata_object_t * p_iodata,
+   const uint8_t * iops,
+   uint8_t len)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_rte_object_t * entry;
+
+   if (p_iodata->slot_nbr == PNET_SLOT_DAP_IDENT)
+   {
+      /* Setting only supported on data submodules
+       * DAP iops set during initial RTE configuration
+       * and never changed.
+       */
+      return -1;
+   }
+
+   if (!frame->active)
+   {
+      return -1;
+   }
+
+   entry = get_rte_object (frame, p_iodata->slot_nbr, p_iodata->subslot_nbr);
+
+   switch (entry->io_interface_type)
+   {
+   case MERA_IO_INTF_SRAM:
+      return pf_mera_ppm_write (
+         net,
+         (pf_drv_frame_t *)frame,
+         frame->config.p_iocr->ppm.buffer_pos + p_iodata->iops_offset,
+         iops,
+         len);
+
+   case MERA_IO_INTF_QSPI:
+   default:
+      /* Writing IOPS is not supported
+       * Automatically updated by RTE
+       */
+      return -1;
+   }
+   return 0;
+}
+
+int pf_mera_ppm_write_iocs (
+   pnet_t * net,
+   pf_drv_frame_t * drv_frame,
+   const pf_iodata_object_t * p_iodata,
+   const uint8_t * iops,
+   uint8_t len)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+
+   if (!frame->active)
+   {
+      return -1;
+   }
+
+   return pf_mera_ppm_write (
+      net,
+      drv_frame,
+      frame->config.p_iocr->ppm.buffer_pos + p_iodata->iocs_offset,
+      iops,
+      len);
+}
+
+int pf_mera_ppm_write_data_status (
+   pnet_t * net,
+   pf_drv_frame_t * drv_frame,
+   uint8_t data_status)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   if (frame == NULL || !frame->active)
+   {
+      /* Ignore writes during ppm initialization */
+      return 0;
+   }
+
+   return pf_mera_ppm_write (
+      net,
+      drv_frame,
+      frame->config.p_iocr->ppm.data_status_offset,
+      &data_status,
+      1);
+}
+
+int pf_mera_ppm_stop (pnet_t * net, pf_drv_frame_t * drv_frame)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   uint16_t i, j;
+
+   LOG_DEBUG (PF_PPM_LOG, "MERA(%d): Stop PPM\n", __LINE__);
+   if (frame->active)
+   {
+      mera_ib_flush (handle->mera_lib);
+      pf_mera_delete_vcam_rule (frame->vcam_id);
+
+      for (i = 0; i < PNET_MAX_SLOTS; i++)
+      {
+         for (j = 0; j < PNET_MAX_SUBSLOTS; j++)
+         {
+            pf_mera_ral_id_free (handle, frame->rte_object[i][j].ib_ral_id);
+         }
+      }
+   }
+   else
+   {
+      LOG_DEBUG (
+         PF_PPM_LOG,
+         "MERA(%d): No action - PPM is not started\n",
+         __LINE__);
+   }
+
+   return 0;
+}
+
+/**
+ * Set the general RTE configuration for a CPM frame.
+ * @param mera_lib   In: Ref to mera lib instance
+ * @param frame      In: Ref to frame
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ob_rtp_config (
+   struct mera_inst * mera_lib,
+   pf_mera_frame_t * frame)
+{
+   int err = 0;
+
+   mera_ob_rtp_conf_t ob_rtp_config = {0};
+
+   ob_rtp_config.type = MERA_RTP_TYPE_PN;
+   ob_rtp_config.grp_id = PF_MERA_GROUP_ID_NONE;
+   ob_rtp_config.length = sizeof (uint16_t) + /* frame_id */
+                          frame->config.p_iocr->param.c_sdu_length + /* Profinet
+                                                                         data
+                                                                         length
+                                                                       */
+                          sizeof (uint16_t) + /* cycle counter */
+                          1 +                 /* data status */
+                          1;                  /* transfer status */
+
+   ob_rtp_config.pn_ds = PF_MERA_DATA_STATUS_MASK;
+   ob_rtp_config.pn_discard = false;
+   ob_rtp_config.opc_grp_ver = 0;
+   ob_rtp_config.wal_enable = true;
+   ob_rtp_config.wal_id = frame->ob_wal_id;
+
+   ob_rtp_config.time.offset = PF_MERA_OB_RTP_TIME_OFFSET;
+   ob_rtp_config.time.interval = 0;
+   ob_rtp_config.time_cnt = 0;
+
+   LOG_DEBUG (
+      PF_CPM_LOG,
+      "MERA(%d): mera_ob_rtp_conf_set "
+      "type:%d "
+      "grp_id:%d "
+      "length:%d "
+      "pn_ds:%d "
+      "pn_discard:%d "
+      "opc_grp_ver:%d "
+      "wal_enable:%d "
+      "wal_id:%d "
+      "time.offset:%d "
+      "time.interval:%d "
+      "time_cnt:%d\n",
+      __LINE__,
+      ob_rtp_config.type,
+      ob_rtp_config.grp_id,
+      ob_rtp_config.length,
+      ob_rtp_config.pn_ds,
+      ob_rtp_config.pn_discard,
+      ob_rtp_config.opc_grp_ver,
+      ob_rtp_config.wal_enable,
+      ob_rtp_config.wal_id,
+      ob_rtp_config.time.offset,
+      ob_rtp_config.time.interval,
+      ob_rtp_config.time_cnt);
+
+   err = mera_ob_rtp_conf_set (mera_lib, frame->rtp_id, &ob_rtp_config);
+   if (err)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to set RTP configuration\n",
+         __LINE__);
+   }
+   return err;
+}
+
+/**
+ * Set the data group (DG) configuration for an outbound iodata object
+ * part of a CPM frame.
+ * Sets the frame data offset and length.
+ * @param mera_lib   In: Ref to mera lib instance
+ * @param frame      In: Ref to frame
+ * @param entry      In: Iodata object configuration
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ob_add_dg (
+   struct mera_inst * mera_lib,
+   pf_mera_frame_t * frame,
+   pf_rte_object_t * entry)
+{
+   mera_ob_dg_conf_t dg_cfg;
+   mera_ob_dg_ctrl_t dg_ctrl;
+
+   if (mera_ob_dg_init (&dg_cfg) != 0)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to init outbound data group\n",
+         __LINE__);
+      return -1;
+   }
+
+   dg_cfg.dg_id = entry->ob_dg_id;
+   dg_cfg.pdu_offset = entry->iodata->data_offset + frame->rte_data_offset;
+   dg_cfg.length = entry->iodata->data_length;
+   dg_cfg.valid_offset = entry->iodata->iops_offset + frame->rte_data_offset;
+
+   dg_cfg.valid_chk = true;
+   dg_cfg.opc_seq_chk = false;
+   dg_cfg.opc_code_chk = false;
+   dg_cfg.invalid_default = true;
+   if (entry->default_data != NULL)
+   {
+      memcpy (dg_cfg.data, entry->default_data, entry->iodata->data_length);
+   }
+   else
+   {
+      memset (dg_cfg.data, 0, entry->iodata->data_length);
+   }
+
+   LOG_DEBUG (
+      PF_CPM_LOG,
+      "MERA(%d): mera_ob_dg_add "
+      "rtp_id:%d "
+      "cfg.dg_id:%d "
+      "cfg.pdu_offset:%d "
+      "cfg.length:%d "
+      "cfg.valid_offset:%d "
+      "cfg.valid_chk:%d "
+      "cfg.opc_seq_chk:%d "
+      "cfg.opc_code_chk:%d "
+      "cfg.invalid_default:%d\n",
+      __LINE__,
+      frame->rtp_id,
+      dg_cfg.dg_id,
+      dg_cfg.pdu_offset,
+      dg_cfg.length,
+      dg_cfg.valid_offset,
+      dg_cfg.valid_chk,
+      dg_cfg.opc_seq_chk,
+      dg_cfg.opc_code_chk,
+      dg_cfg.invalid_default);
+
+   if (mera_ob_dg_add (mera_lib, frame->rtp_id, &dg_cfg) != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to add outbound data group\n",
+         __LINE__);
+      return -1;
+   }
+
+   dg_ctrl.enable = true;
+
+   if (mera_ob_dg_ctrl_set (mera_lib, frame->rtp_id, entry->ob_dg_id, &dg_ctrl) != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to enable outbound data group\n",
+         __LINE__);
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * Set the Write Action (WA) for an outbound iodata object
+ * part of a CPM frame.
+ * Sets the data sink (SRAM/QSPI address).
+ * @param mera_lib   In: Ref to mera instance
+ * @param frame      In: Ref to frame
+ * @param entry      In: Iodata object configuration
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_ob_add_wa (
+   struct mera_inst * mera_lib,
+   pf_mera_frame_t * frame,
+   pf_rte_object_t * entry)
+{
+   mera_ob_wa_conf_t wa_cfg;
+   if (mera_ob_wa_init (&wa_cfg) != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed init outbound write action\n",
+         __LINE__);
+      return 0;
+   }
+
+   wa_cfg.internal = false;
+   wa_cfg.rtp_id = frame->rtp_id;
+   wa_cfg.grp_id = PF_MERA_GROUP_ID_NONE;
+   wa_cfg.dg_id = entry->ob_dg_id;
+   wa_cfg.wr_addr.intf = MERA_IO_INTF_SRAM;
+   wa_cfg.wr_addr.addr = entry->sram_address;
+
+   wa_cfg.length = entry->iodata->data_length;
+
+   LOG_DEBUG (
+      PF_CPM_LOG,
+      "MERA(%d): mera_ob_wa_add "
+      "wal_id:%d "
+      "cfg.internal:%d "
+      "cfg.rtp_id:%d "
+      "cfg.grp_id:%d "
+      "cfg.dg_id:%d "
+      "cfg.length:%d "
+      "cfg.wr_addr.intf:%d-%s "
+      "cfg.wr_addr.addr:0x%x\n",
+      __LINE__,
+      frame->ob_wal_id,
+      wa_cfg.internal,
+      wa_cfg.rtp_id,
+      wa_cfg.grp_id,
+      wa_cfg.dg_id,
+      wa_cfg.length,
+      wa_cfg.wr_addr.intf,
+      pf_mera_interface_type_to_str (wa_cfg.wr_addr.intf),
+      wa_cfg.wr_addr.addr);
+
+   if (mera_ob_wa_add (mera_lib, frame->ob_wal_id, &wa_cfg) != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to add outbound read action\n",
+         __LINE__);
+      return -1;
+   }
+
+   if (entry->io_interface_type == MERA_IO_INTF_QSPI)
+   {
+      wa_cfg.wr_addr.addr = entry->qspi_address;
+      wa_cfg.wr_addr.intf = MERA_IO_INTF_QSPI;
+
+      LOG_DEBUG (
+         PF_CPM_LOG,
+         "MERA(%d): mera_ob_wa_add "
+         "wal_id:%d "
+         "cfg.internal:%d "
+         "cfg.rtp_id:%d "
+         "cfg.grp_id:%d "
+         "cfg.dg_id:%d "
+         "cfg.length:%d "
+         "cfg.wr_addr.intf:%d-%s "
+         "cfg.wr_addr.addr:0x%x\n",
+         __LINE__,
+         frame->ob_wal_id,
+         wa_cfg.internal,
+         wa_cfg.rtp_id,
+         wa_cfg.grp_id,
+         wa_cfg.dg_id,
+         wa_cfg.length,
+         wa_cfg.wr_addr.intf,
+         pf_mera_interface_type_to_str (wa_cfg.wr_addr.intf),
+         wa_cfg.wr_addr.addr);
+
+      if (mera_ob_wa_add (mera_lib, frame->ob_wal_id, &wa_cfg) != 0)
+      {
+         LOG_FATAL (
+            PF_MERA_LOG,
+            "MERA(%d): Failed to add outbound read action\n",
+            __LINE__);
+         return -1;
+      }
+   }
+
+   return 0;
+}
+
+/**
+ * Set the RTE configuration for an outbound iodata object
+ * part of a CPM frame. Sets datagroup and write action.
+ * @param mera_lib   In: Ref to mera lib instance
+ * @param frame      In: Ref to frame
+ * @param entry      In: Iodata object configuration
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_add_ob_entry (
+   struct mera_inst * mera_lib,
+   pf_mera_frame_t * frame,
+   pf_rte_object_t * entry)
+{
+   LOG_DEBUG (
+      PF_CPM_LOG,
+      "MERA(%d): [%u,%u] Configure outbound data entry\n",
+      __LINE__,
+      entry->iodata->slot_nbr,
+      entry->iodata->subslot_nbr);
+
+   entry->in_use = true;
+
+   if (pf_mera_ob_add_dg (mera_lib, frame, entry) != 0)
+   {
+      return -1;
+   }
+
+   if (pf_mera_ob_add_wa (mera_lib, frame, entry) != 0)
+   {
+      return -1;
+   }
+
+   return 0;
+}
+
+/**
+ * Generate RTE configurations for all iodata objects in a CPM type
+ * of communication relation.
+ * @param handle     InOut: Ref to mera instance
+ * @param frame      InOut: The PPM frame
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_cpm_config (pf_mera_t * handle, pf_mera_frame_t * frame)
+{
+   int ret = 0;
+   uint16_t slot, subslot;
+   pf_rte_object_t * entry;
+   const pf_mera_rte_cfg_t * entry_rte_data_cfg; /* qspi address */
+
+   LOG_DEBUG (PF_CPM_LOG, "MERA(%d): Apply RTE configuration (CMP)\n", __LINE__);
+
+   for (slot = 0; slot < PNET_MAX_SLOTS; slot++)
+   {
+      for (subslot = 0; subslot < PNET_MAX_SUBSLOTS; subslot++)
+      {
+         if (
+            frame->rte_object[slot][subslot].iodata != NULL &&
+            frame->rte_object[slot][subslot].iodata->data_length > 0)
+         {
+            entry = &frame->rte_object[slot][subslot];
+            entry_rte_data_cfg = &handle->rte_data_cfg[slot][subslot];
+            entry->ob_dg_id = frame->ob_dg_count++;
+
+            if (entry_rte_data_cfg->valid)
+            {
+               entry->io_interface_type =
+                  pf_mera_interface_type (entry_rte_data_cfg->cfg.type);
+
+#if PF_MERA_ALLOW_APPLICATION_SRAM
+               if (entry_rte_data_cfg->cfg.type == PNET_MERA_DATA_TYPE_SRAM)
+               {
+                  entry->sram_address = entry_rte_data_cfg->cfg.address;
+               }
+#endif
+               entry->qspi_address = entry_rte_data_cfg->cfg.address;
+               entry->default_data = entry_rte_data_cfg->cfg.default_data;
+            }
+
+            if (entry->sram_address == PF_SRAM_INVALID_ADDRESS)
+            {
+               /* SRAM address has not been set by application
+                * use address from SRAM frame
+                */
+               entry->sram_address = frame->sram_frame_address +
+                                     frame->rte_data_offset +
+                                     entry->iodata->data_offset;
+            }
+
+            LOG_DEBUG (
+               PF_PPM_LOG,
+               "MERA(%d): [%u,%u]: Interface: %s Start address: 0x%x Size: "
+               "%d\n",
+               __LINE__,
+               entry->iodata->slot_nbr,
+               entry->iodata->subslot_nbr,
+               pf_mera_interface_type_to_str (entry->io_interface_type),
+               entry->io_interface_type == MERA_IO_INTF_QSPI
+                  ? entry->qspi_address
+                  : entry->sram_address,
+               entry->iodata->data_length);
+         }
+      }
+   }
+
+   return ret;
+}
+
+/**
+ * Configure and start a RTE configuration for a CPM frame.
+ * Sets the general RTE configuration and the configuration
+ * for each iodata object in the communication relationship.
+ * @param handle     In: Ref to mera instance
+ * @param frame      In: Ref to frame
+ * @return 0 on success, -1 on error
+ */
+static int pf_mera_cpm_start_rte (pf_mera_t * handle, pf_mera_frame_t * frame)
+{
+   int err = 0;
+   pf_iocr_t * p_iocr = frame->config.p_iocr;
+   uint16_t slot, subslot;
+   mera_ob_wal_conf_t wal_conf;
+   mera_ob_rtp_state_t state;
+
+   err = ob_add_vcam_rule (
+      frame->vcam_id,
+      p_iocr->param.iocr_tag_header.vlan_id,
+      &frame->config.main_mac_addr,
+      frame->vlan_idx,
+      p_iocr->param.frame_id,
+      frame->rtp_id);
+
+   if (err != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to add outbound vcam rule\n",
+         __LINE__);
+      return err;
+   }
+
+   err = pf_mera_ob_rtp_config (handle->mera_lib, frame);
+   if (err != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to set outbound RTP configuration\n",
+         __LINE__);
+      return err;
+   }
+
+   for (slot = 0; slot < PNET_MAX_SLOTS; slot++)
+   {
+      for (subslot = 0; subslot < PNET_MAX_SUBSLOTS; subslot++)
+      {
+         if (
+            frame->rte_object[slot][subslot].iodata != NULL &&
+            frame->rte_object[slot][subslot].iodata->data_length > 0)
+         {
+            if (
+               pf_mera_add_ob_entry (
+                  handle->mera_lib,
+                  frame,
+                  &frame->rte_object[slot][subslot]) != 0)
+            {
+               return -1;
+            }
+         }
+      }
+   }
+
+   /* Add WAL to trigger reset to default value on DHT watchdog timeout. */
+   mera_ob_wal_conf_get (handle->mera_lib, frame->ob_wal_id, &wal_conf);
+   wal_conf.time.offset = 10000;
+   wal_conf.time.interval = frame->config.p_iocr->cpm.control_interval * 1000;
+
+   if (mera_ob_wal_conf_set (handle->mera_lib, frame->ob_wal_id, &wal_conf) != 0)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to set outbound RTP WAL \n",
+         __LINE__);
+   }
+
+   /* Activate rtp id. Needed if DHT has expired in previous connection. */
+   state.active = true;
+   if (mera_ob_rtp_state_set (handle->mera_lib, frame->rtp_id, &state) != 0)
+   {
+      LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed to set RTP state \n", __LINE__);
+   }
+
+   frame->active = true;
+
+   if (
+      mera_event_enable (
+         handle->mera_lib,
+         (MERA_EVENT_RTP_STATE_STOPPED | MERA_EVENT_PN_DS_MISMATCH |
+          MERA_EVENT_DG_INVALID),
+         true) != 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to enable outbound events\n",
+         __LINE__);
+      return -1;
+   }
+
+   return 0;
+}
+
+pf_drv_frame_t * pf_mera_cpm_alloc (
+   pnet_t * net,
+   const pf_mera_frame_cfg_t * cfg)
+{
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   pf_mera_frame_t * frame = NULL;
+   pf_iodata_object_t * p_iodata;
+   pf_rte_object_t * entry;
+   uint16_t i;
+
+   if (cfg == NULL || cfg->p_iocr == NULL || handle == NULL)
+   {
+      return NULL;
+   }
+
+   frame = pf_mera_alloc_frame (handle);
+   if (frame == NULL)
+   {
+      return NULL;
+   }
+
+   frame->is_cpm = true;
+
+   frame->sram_frame_address = pf_sram_frame_alloc();
+   if (frame->sram_frame_address == 0)
+   {
+      LOG_FATAL (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to alloc SRAM for CPM frame\n",
+         __LINE__);
+      pf_mera_free_frame (handle, frame);
+      return NULL;
+   }
+
+   LOG_DEBUG (
+      PF_MERA_LOG,
+      "MERA(%d): Allocated CPM SRAM frame at 0x%x\n",
+      __LINE__,
+      frame->sram_frame_address);
+
+   frame->config = *cfg;
+   frame->port = cfg->port;
+
+   for (i = 0; i < frame->config.p_iocr->nbr_data_desc; i++)
+   {
+      p_iodata = &frame->config.p_iocr->data_desc[i];
+      if (p_iodata->data_length > 0)
+      {
+         entry =
+            get_rte_object (frame, p_iodata->slot_nbr, p_iodata->subslot_nbr);
+         entry->iodata = p_iodata;
+      }
+   }
+
+   frame->vcam_id = pf_mera_vcam_id_alloc (handle);
+   frame->vlan_idx = pf_mera_vlan_index_alloc (handle);
+   frame->rtp_id = pf_mera_rtp_id_alloc (handle);
+   frame->ob_wal_id = pf_mera_wal_id_alloc (handle);
+   frame->rte_data_offset = PF_MERA_CPM_IODATA_OFFSET;
+
+   LOG_DEBUG (PF_CPM_LOG, "MERA(%d): CPM frame allocated\n", __LINE__);
+
+   return (pf_drv_frame_t *)frame;
+}
+
+int pf_mera_cpm_start (pnet_t * net, pf_drv_frame_t * drv_frame)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+
+   if (handle == NULL || frame == NULL || frame->config.p_iocr == NULL)
+   {
+      LOG_DEBUG (
+         PF_CPM_LOG,
+         "MERA(%d): Failed to start RTE - parameter error\n",
+         __LINE__);
+      return -1;
+   }
+
+   if (pf_mera_cpm_config (handle, frame) != 0)
+   {
+      LOG_DEBUG (
+         PF_CPM_LOG,
+         "MERA(%d): Failed to start RTE - data config error\n",
+         __LINE__);
+      return -1;
+   }
+
+   return pf_mera_cpm_start_rte (handle, frame);
+}
+
+int pf_mera_cpm_activate_rte_dht (pf_drv_frame_t * drv_frame)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   mera_ob_rtp_conf_t ob_rtp_config;
+
+   if (mera_ob_rtp_conf_get (frame->mera_lib, frame->rtp_id, &ob_rtp_config) != 0)
+   {
+      LOG_ERROR (PF_CPM_LOG, "MERA(%d): Failed get CPM RTE config\n", __LINE__);
+      return -1;
+   }
+
+   /* Configure CPM data hold timeout.
+    * RTP and group will stop when configured timeout expires.
+    * Default values will be applied to all DGs on
+    * writes triggered when group is stopped.
+    */
+
+   ob_rtp_config.time.offset = MERA_TIME_OFFSET_NONE;
+   ob_rtp_config.time.interval =
+      frame->config.p_iocr->cpm.control_interval * 1000;
+   ob_rtp_config.time_cnt = frame->config.p_iocr->cpm.data_hold_factor;
+
+   LOG_DEBUG (
+      PF_CPM_LOG,
+      "MERA(%d): Reconfigure ob rtp dht timing =%uns cnt=%d\n",
+      __LINE__,
+      ob_rtp_config.time.interval,
+      ob_rtp_config.time_cnt);
+
+   if (mera_ob_rtp_conf_set (frame->mera_lib, frame->rtp_id, &ob_rtp_config) != 0)
+   {
+      LOG_ERROR (PF_CPM_LOG, "MERA(%d): Failed set CPM RTE config\n", __LINE__);
+      return -1;
+   }
+
+   return 0;
+}
+
+int pf_mera_cpm_free (pnet_t * net, pf_drv_frame_t * drv_frame)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   LOG_DEBUG (PF_MERA_LOG, "MERA(%d): Free CPM frame\n", __LINE__);
+   if (frame != NULL)
+   {
+      pf_mera_vcam_id_free (handle, frame->vcam_id);
+      pf_mera_vlan_index_free (handle, frame->vlan_idx);
+      pf_mera_rtp_id_free (handle, frame->rtp_id);
+      pf_mera_wal_id_free (handle, frame->ob_wal_id);
+      pf_sram_frame_free (frame->sram_frame_address);
+      pf_mera_free_frame (handle, frame);
+      return 0;
+   }
+   LOG_ERROR (PF_MERA_LOG, "MERA(%d): Failed to free CPM frame\n", __LINE__);
+   return -1;
+}
+
+int pf_mera_cpm_stop (pnet_t * net, pf_drv_frame_t * drv_frame)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+
+   LOG_DEBUG (PF_PPM_LOG, "MERA(%d): Stop CPM\n", __LINE__);
+
+   mera_ob_wal_rel (handle->mera_lib, frame->ob_wal_id);
+
+   if (mera_ob_flush (handle->mera_lib) != 0)
+   {
+      LOG_ERROR (PF_CPM_LOG, "MERA(%d): Failed to flush rtp\n", __LINE__);
+   }
+   if (pf_mera_delete_vcam_rule (frame->vcam_id) != 0)
+   {
+      LOG_ERROR (PF_CPM_LOG, "MERA(%d): Failed to delete VCAM rule\n", __LINE__);
+   }
+
+   /* Stop periodic debug info */
+   pf_scheduler_remove_if_running (net, &handle->sched_handle);
+   return 0;
+}
+
+/**
+ * Disable RTE write operations for an iodata object.
+ * Intended to be used while reading SRAM data groups.
+ * @param mera    InOut: Ref to mera library instance
+ * @param frame   In: CPM frame
+ * @param entry   In: Iodata object configuration
+ */
+static void pf_mera_disable_ob_entry (
+   struct mera_inst * mera,
+   pf_mera_frame_t * frame,
+   pf_rte_object_t * entry)
+{
+   mera_ob_dg_ctrl_t dg_ctrl;
+
+   dg_ctrl.enable = false;
+
+   if (mera_ob_dg_ctrl_set (mera, frame->rtp_id, entry->ob_dg_id, &dg_ctrl) != 0)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to disable outbound data group\n",
+         __LINE__);
+   }
+}
+
+/**
+ * Enable RTE write operations for an iodata object.
+ * @param mera    InOut: Ref to mera library instance
+ * @param frame   In: CPM frame
+ * @param entry   In: Iodata object configuration
+ */
+static void pf_mera_enable_ob_entry (
+   struct mera_inst * mera,
+   pf_mera_frame_t * frame,
+   pf_rte_object_t * entry)
+{
+   mera_ob_dg_ctrl_t dg_ctrl;
+
+   dg_ctrl.enable = true;
+
+   if (mera_ob_dg_ctrl_set (mera, frame->rtp_id, entry->ob_dg_id, &dg_ctrl) != 0)
+   {
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): Failed to enable outbound data group\n",
+         __LINE__);
+   }
+}
+
+int pf_mera_cpm_read_data_and_iops (
+   pnet_t * net,
+   pf_drv_frame_t * drv_frame,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   bool * p_new_flag,
+   uint8_t * p_data,
+   uint16_t data_len,
+   uint8_t * p_iops,
+   uint8_t iops_len)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   mera_ob_dg_status_t status;
+   pf_rte_object_t * entry;
+
+   *p_iops = PNET_IOXS_BAD;
+
+   if (!frame->active)
+   {
+      return -1;
+   }
+
+   if (slot_nbr == PNET_SLOT_DAP_IDENT)
+   {
+      return 0;
+   }
+
+   if (data_len == 0 || iops_len == 0)
+   {
+      return 0;
+   }
+
+   entry = get_rte_object (frame, slot_nbr, subslot_nbr);
+
+   switch (entry->io_interface_type)
+   {
+   case MERA_IO_INTF_SRAM:
+   case MERA_IO_INTF_QSPI:
+      if (
+         mera_ob_dg_status_get (
+            handle->mera_lib,
+            frame->rtp_id,
+            entry->ob_dg_id,
+            &status) != 0)
+      {
+         LOG_ERROR (
+            PF_MERA_LOG,
+            "MERA(%d): [%u,%u] Failed to read data group status "
+            "(output)\n",
+            __LINE__,
+            slot_nbr,
+            subslot_nbr);
+         return -1;
+      }
+
+      if (status.valid_chk)
+      {
+         LOG_DEBUG (
+            PF_MERA_LOG,
+            "MERA(%d): [%u,%u] IOPS BAD, Invalid output data\n",
+            __LINE__,
+            slot_nbr,
+            subslot_nbr);
+      }
+      else
+      {
+         *p_iops = PNET_IOXS_GOOD;
+      }
+
+      pf_mera_disable_ob_entry (handle->mera_lib, frame, entry);
+
+      if (pf_sram_read (entry->sram_address, p_data, data_len) != 0)
+      {
+         pf_mera_enable_ob_entry (handle->mera_lib, frame, entry);
+
+         LOG_ERROR (
+            PF_MERA_LOG,
+            "MERA(%d): [%u,%u] Failed to read SRAM (output) \n",
+            __LINE__,
+            slot_nbr,
+            subslot_nbr);
+         return -1;
+      }
+
+      pf_mera_enable_ob_entry (handle->mera_lib, frame, entry);
+
+      return 0;
+      break;
+
+   default:
+      LOG_ERROR (
+         PF_MERA_LOG,
+         "MERA(%d): [%u,%u] unsupported interface type\n",
+         __LINE__,
+         slot_nbr,
+         subslot_nbr);
+      return -1;
+   }
+   return 0;
+}
+
+int pf_mera_cpm_read_iocs (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   uint8_t * p_iocs,
+   uint8_t iocs_len)
+{
+   /* TODO - Figure out how this can be read from frame memory */
+   /* this is implemented in pf_mera_cpm_read_data_and_iops.
+    review this and figure out best implementation */
+   if (iocs_len > 0)
+   {
+      *p_iocs = PNET_IOXS_GOOD;
+   }
+   return 0;
+}
+
+int pf_mera_poll_cpm_events (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   pf_mera_event_t * evt)
+{
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   mera_event_t mera_event;
+
+   if (net == NULL || frame == NULL || evt == NULL)
+   {
+      return 0;
+   }
+
+   memset (evt, 0, sizeof (pf_mera_event_t));
+
+   if (mera_event_poll (handle->mera_lib, (mera_event_t *)&mera_event) == 0)
+   {
+      if ((MERA_EVENT_RTP_STATE_STOPPED & mera_event) != 0)
+      {
+         LOG_INFO (PF_MERA_LOG, "MERA(%d): CPM RTP stopped event\n", __LINE__);
+         evt->stopped = true;
+      }
+      if ((MERA_EVENT_PN_DS_MISMATCH & mera_event) != 0)
+      {
+         evt->data_status_mismatch = true;
+      }
+      if ((MERA_EVENT_DG_INVALID & mera_event) != 0)
+      {
+         evt->invalid_dg = true;
+      }
+      return 0;
+   }
+   return -1;
+}
+
+int pf_mera_cpm_read_data_status (
+   pf_drv_frame_t * drv_frame,
+   uint8_t * p_data_status)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   mera_ob_rtp_status_t status;
+
+   if (mera_ob_rtp_status_get (frame->mera_lib, frame->rtp_id, &status) == 0)
+   {
+      if (status.pn_ds_chk == 1)
+      {
+         /* RTE has a detected a data status mismatch.
+            Report RTE data status
+          */
+         *p_data_status = status.pn_ds;
+      }
+      else
+      {
+         /* Data status ok - report checked data status */
+         *p_data_status = PF_MERA_DATA_STATUS_MASK;
+      }
+
+      return 0;
+   }
+
+   return -1;
+}
+
+int pf_mera_cpm_read_rx_count (pf_drv_frame_t * drv_frame, uint64_t * rx_count)
+{
+   pf_mera_frame_t * frame = (pf_mera_frame_t *)drv_frame;
+   mera_ob_rtp_counters_t counters;
+
+   if (mera_ob_rtp_counters_get (frame->mera_lib, frame->rtp_id, &counters) == 0)
+   {
+      if (counters.rx_0 >= frame->rx_count)
+      {
+         *rx_count = counters.rx_0 - frame->rx_count;
+      }
+      else
+      {
+         /* Wrap around */
+         *rx_count = counters.rx_0 + (UINT64_MAX - counters.rx_0);
+      }
+      frame->rx_count = counters.rx_0;
+      return 0;
+   }
+
+   *rx_count = 0;
+   return -1;
+}
+
+void pf_mera_show (pnet_t * net, bool cpm, bool ppm)
+{
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   mera_debug_info_t info;
+
+   info.group = MERA_DEBUG_GROUP_GEN;
+   info.rtp_id = 0;   /* All RTP IDs */
+   info.full = false; /* Only active IDs */
+   info.clear = true;
+
+   if (cpm && ppm)
+   {
+      info.group = MERA_DEBUG_GROUP_ALL;
+   }
+   else if (cpm)
+   {
+      info.group = MERA_DEBUG_GROUP_OB;
+   }
+   else if (ppm)
+   {
+      info.group = MERA_DEBUG_GROUP_IB;
+   }
+
+   printf ("RTE Status:\n");
+   mera_debug_info_print (handle->mera_lib, pf_mera_printf_cb, &info);
+}
+
+int pf_mera_get_port_from_remote_mac (
+   pnet_ethaddr_t * mac_addr,
+   uint16_t * p_port)
+{
+   int ret = -1;
+   char cmd[128] = {0};
+   char result[100] = {0};
+   FILE * fd = NULL;
+   char mac_addr_str[PF_MERA_SCRIPT_ARG_LEN] = {0};
+
+   *p_port = 0;
+
+   snprintf (
+      mac_addr_str,
+      sizeof (mac_addr_str),
+      "%02x:%02x:%02x:%02x:%02x:%02x",
+      mac_addr->addr[0],
+      mac_addr->addr[1],
+      mac_addr->addr[2],
+      mac_addr->addr[3],
+      mac_addr->addr[4],
+      mac_addr->addr[5]);
+
+   snprintf (
+      cmd,
+      sizeof (cmd),
+      "brctl showmacs br0 | grep %s | xargs | cut -c1-1",
+      mac_addr_str);
+
+   LOG_DEBUG (
+      PF_CPM_LOG,
+      "MERA(%d): Use brctl command to get active port: \"%s\"\n",
+      __LINE__,
+      cmd);
+
+   fd = popen (cmd, "r");
+   if (fd != NULL)
+   {
+      if (fgets (result, sizeof (result), fd) != NULL)
+      {
+         *p_port = atoi (result);
+         *p_port = *p_port - 1;
+
+         LOG_DEBUG (
+            PF_CPM_LOG,
+            "MERA(%d): Found mac on bridge interface %u, using port index "
+            "%u\n",
+            __LINE__,
+            *p_port + 1,
+            *p_port);
+         ret = 0;
+      }
+      else
+      {
+         LOG_FATAL (
+            PF_CPM_LOG,
+            "MERA(%d): Failed to find mac on bridge, using port index %u\n",
+            __LINE__,
+            *p_port);
+      }
+      pclose (fd);
+   }
+
+   return ret;
+}
+
+/* Public function in P-Net API
+ * For iodata objects not mapped to QSPI this operation
+ * is not used. Instead the iodata is automatically mapped
+ * to SRAM by the P-Net stack (this file).
+ *
+ * This config should be part of frame, but there is
+ * no suitable application event at which the configuration
+ * can be passed when CPM/PPM are created but not yet started.
+ */
+int pnet_mera_input_set_rte_config (
+   pnet_t * net,
+   uint32_t api,
+   uint16_t slot,
+   uint16_t subslot,
+   const pnet_mera_rte_data_cfg_t * rte_data_cfg)
+{
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "MERA(%d): [%u,%u] Adding RTE configuration "
+      "%s at address 0x%x (inbound / iodata input)\n",
+      __LINE__,
+      slot,
+      subslot,
+      pf_mera_rte_data_type_to_str (rte_data_cfg->type),
+      rte_data_cfg->address);
+
+   handle->rte_data_cfg[slot][get_subslot_index (subslot)].valid = true;
+   handle->rte_data_cfg[slot][get_subslot_index (subslot)].cfg = *rte_data_cfg;
+
+   return 0;
+}
+
+int pnet_mera_output_set_rte_config (
+   pnet_t * net,
+   uint32_t api,
+   uint16_t slot,
+   uint16_t subslot,
+   const pnet_mera_rte_data_cfg_t * rte_data_cfg)
+{
+   pf_mera_t * handle = (pf_mera_t *)net->hwo_drv;
+
+   LOG_DEBUG (
+      PF_CPM_LOG,
+      "MERA(%d): [%u,%u] Adding RTE configuration "
+      "%s at address 0x%x (outbound / iodata output)\n",
+      __LINE__,
+      slot,
+      subslot,
+      pf_mera_rte_data_type_to_str (rte_data_cfg->type),
+      rte_data_cfg->address);
+
+   handle->rte_data_cfg[slot][get_subslot_index (subslot)].valid = true;
+   handle->rte_data_cfg[slot][get_subslot_index (subslot)].cfg = *rte_data_cfg;
+
+   return 0;
+}

+ 335 - 0
src/drivers/lan9662/src/pf_lan9662_mera.h

@@ -0,0 +1,335 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef PF_MERA_H
+#define PF_MERA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* PPM/CPM frame configuration.
+ * The LAN9662 RTE configuration is generated from this.
+ */
+typedef struct pf_mera_frame_cfg
+{
+   pf_iocr_t * p_iocr;           /* IO-data configuration. From active AR. */
+   uint16_t port;                /* RTE Interface mapping */
+   pnet_ethaddr_t main_mac_addr; /* MAC address for management port */
+} pf_mera_frame_cfg_t;
+
+typedef struct pf_mera_event
+{
+   bool stopped;
+   bool data_status_mismatch;
+   bool invalid_dg;
+} pf_mera_event_t;
+
+/**
+ * Allocate a mera PPM frame and its RTE resources.
+ * @param net              InOut: The p-net stack instance
+ * @param cfg              In:    PPM configuration
+ * @return Reference to mera frame, NULL on error.
+ */
+pf_drv_frame_t * pf_mera_ppm_alloc (
+   pnet_t * net,
+   const pf_mera_frame_cfg_t * cfg);
+
+/**
+ * Free a mera PPM frame and its RTE resources.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera frame
+ * @return 0 on success. -1 on error.
+ */
+int pf_mera_ppm_free (pnet_t * net, pf_drv_frame_t * frame);
+
+/**
+ * Start cyclic data sender / provider.
+ * Calling this operation loads and starts the RTE
+ * configuration defined by the frame.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera frame
+ * @return 0 on success. -1 on error.
+ */
+int pf_mera_ppm_start (pnet_t * net, pf_drv_frame_t * frame);
+
+/**
+ * Start cyclic data sender / provider.
+ * Calling this operation stops the RTE
+ * configuration defined by the frame.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera frame
+ * @return 0 on success. -1 on error.
+ */
+int pf_mera_ppm_stop (pnet_t * net, pf_drv_frame_t * frame);
+
+/**
+ * Update value of an IO object.
+ * Only allowed for iodata objects mapped to SRAM.
+ *
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera PPM frame
+ * @param p_iodata         In:    Iodata object
+ * @param data             In:    Input data
+ * @param len              In:    Input data length
+ * @return 0 on success, -1 on error;
+ */
+int pf_mera_ppm_write_input (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   const pf_iodata_object_t * p_iodata,
+   const uint8_t * data,
+   uint32_t len);
+
+/**
+ * Update provider status of an IO object.
+ * Only allowed for iodata objects mapped to SRAM.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera PPM frame
+ * @param p_iodata         In:    Iodata object
+ * @param iops             In:    Provider status
+ * @param len              In:    Provider status length
+ * @return 0 on success, -1 on error;
+ */
+int pf_mera_ppm_write_iops (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   const pf_iodata_object_t * p_iodata,
+   const uint8_t * iops,
+   uint8_t len);
+
+/**
+ * Update consumer status of an IO object.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera PPM frame
+ * @param p_iodata         In:    Iodata object
+ * @param iocs             In:    Consumer status
+ * @param len              In:    Consumer status length
+ * @return 0 on success, -1 on error;
+ */
+int pf_mera_ppm_write_iocs (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   const pf_iodata_object_t * p_iodata,
+   const uint8_t * iops,
+   uint8_t len);
+
+/**
+ * Update PPM frame data status.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera PPM frame
+ * @param data_status      In:    Data status
+ * @return 0 on success, -1 on error;
+ */
+int pf_mera_ppm_write_data_status (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   uint8_t data_status);
+
+/**
+ * Retrieve the data and IOPS for a sub-module.
+ * Note that this is not from the PLC, but previously set by the application.
+ * @param net              InOut: The p-net stack instance
+ * @param api_id           In:    The API id.
+ * @param p_iodata         In:    Iodata object
+ * @param p_data           Out:   The application data.
+ * @param p_data_len       In:    The length of the p_data buffer.
+ * @param p_iops           Out:   The IOPS of the application data.
+ * @param p_iops_len       In:    Size of buffer at p_iops.
+ * @return  0  if the input data and IOPS could e retrieved.
+ *          -1 if an error occurred.
+ */
+int pf_mera_ppm_read_data_and_iops (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   const pf_iodata_object_t * p_iodata,
+   uint8_t * p_data,
+   uint16_t data_len,
+   uint8_t * p_iops,
+   uint8_t iops_len);
+
+/**
+ * Allocate a mera PPM frame and its RTE resources.
+ * @param net              InOut: The p-net stack instance
+ * @param cfg              In:    CPM configuration
+ * @return Reference to mera frame, NULL on error.
+ */
+pf_drv_frame_t * pf_mera_cpm_alloc (
+   pnet_t * net,
+   const pf_mera_frame_cfg_t * cfg);
+
+/**
+ * Free a mera CMP frame and its RTE resources.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera frame
+ * @return 0 on success. -1 on error.
+ */
+int pf_mera_cpm_free (pnet_t * net, pf_drv_frame_t * frame);
+
+/**
+ * Start cyclic data receiver / consumer.
+ * Calling this operation loads and starts the RTE
+ * configuration defined by the frame.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera frame
+ * @return 0 on success. -1 on error.
+ */
+int pf_mera_cpm_start (pnet_t * net, pf_drv_frame_t * frame);
+
+/**
+ * Start cyclic data receiver / consumer.
+ * Calling this operation stops the RTE
+ * configuration defined by the frame.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera frame
+ * @return 0 on success. -1 on error.
+ */
+int pf_mera_cpm_stop (pnet_t * net, pf_drv_frame_t * frame);
+
+/**
+ * Retrieve latest sub-slot data and IOPS received from the controller.
+ * This function may be called to retrieve the latest data and IOPS values
+ * of a sub-slot sent from the controller.
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera frame
+ * @param slot             In:    The slot.
+ * @param subslot          In:    The sub-slot.
+ * @param p_new_flag       InOut: Not updated by this function
+ * @param p_data           Out:   The received data.
+ * @param data_len         In:    Size of data.
+ * @param p_iops           Out:   The controller provider status (IOPS).
+ *                                See pnet_ioxs_values_t
+ * @param iops_len         In:    Size of IOPS
+ * @return  0  if a sub-module data and IOPS is retrieved.
+ *          -1 if an error occurred.
+ */
+int pf_mera_cpm_read_data_and_iops (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   uint16_t slot,
+   uint16_t subslot,
+   bool * p_new_flag,
+   uint8_t * p_data,
+   uint16_t data_len,
+   uint8_t * p_iops,
+   uint8_t iops_len);
+
+/**
+ * Fetch the controller consumer status of one sub-slot.
+ *
+ * This function may be called to retrieve the IOCS value of a sub-slot
+ * sent from the controller.
+ *
+ * TODO - Always returns hardcoded GOOD
+ *
+ * @param net              InOut: The p-net stack instance
+ * @param frame            InOut: Mera frame
+ * @param slot_nbr         In:    The slot.
+ * @param subslot_nbr      In:    The sub-slot.
+ * @param p_iocs           Out:   The controller consumer status.
+ *                                See pnet_ioxs_values_t
+ * @param iocs_len         In:    The IOCS length
+ * @return  0  if a sub-module IOCS was set.
+ *          -1 if an error occurred.
+ */
+int pf_mera_cpm_read_iocs (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   uint8_t * p_iocs,
+   uint8_t iocs_len);
+
+/**
+ * Get mera CPM events
+ * @param net               InOut: The p-net stack instance
+ * @param frame             InOut: Mera frame
+ * @param p_data_status     Out:   CPM Events.
+ * @return  0  if events was successfully retrieved
+ *          -1 if an error occurred.
+ */
+int pf_mera_poll_cpm_events (
+   pnet_t * net,
+   pf_drv_frame_t * frame,
+   pf_mera_event_t * event);
+
+/**
+ * Get the data status of a CPM connection.
+ * @param frame            InOut: Mera frame
+ * @param p_data_status    Out:   The CPM data status.
+ * @return  0  if data_status was successfully retrieved
+ *          -1 if an error occurred.
+ */
+int pf_mera_cpm_read_data_status (
+   pf_drv_frame_t * frame,
+   uint8_t * p_data_status);
+
+/**
+ * Read the number of received frames. Calling this operation
+ * resets the counter and the returned value is the number of
+ * received frame since last call to this function.
+ * @param frame            InOut: Mera frame
+ * @param rx_count         Out:   Number of received frames since last call.
+ * @return  0  if frame counter was successfully read
+ *          -1 if an error occurred.
+ */
+int pf_mera_cpm_read_rx_count (pf_drv_frame_t * frame, uint64_t * rx_count);
+
+/**
+ * Enable the RTE frame received timeout.
+ * A RTE stopped event will occur if no io data frames are
+ * received within the DHT period.
+ *
+ * @param frame           InOut: CPM frame
+ * @return  0  if RTP ID was successfully updated
+ *          -1 if an error occurred.
+ */
+int pf_mera_cpm_activate_rte_dht (pf_drv_frame_t * drv_frame);
+
+/**
+ * Get active status for a frame. A frame is active
+ * if the corresponding RTE configuration is running.
+ * @param frame           In: PPM or CPM frame
+ * @return true if frame is active
+ *         false in not
+ */
+bool pf_mera_is_active_frame (pf_drv_frame_t * frame);
+
+/**
+ * Show the status of the pf_mera component
+ * @param net              InOut: The p-net stack instance
+ * @param cpm              In: Show CPM info
+ * @param ppm              In: Show PPM info
+ */
+void pf_mera_show (pnet_t * net, bool cpm, bool ppm);
+
+/**
+ * Utility for finding the RTE port on which a
+ * mac address is found when a network bridge is used.
+ * If no bridge is configured operation fails and *p_port
+ * is set to 0.
+ * @param mac_address      In:  Controller mac address
+ * @param p_port           Out: RTE port on which controller is found
+ * @return 0 on success, -1 on error
+ */
+int pf_mera_get_port_from_remote_mac (
+   pnet_ethaddr_t * mac_address,
+   uint16_t * p_port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PF_MERA_H */

+ 129 - 0
src/drivers/lan9662/src/pf_mera_trace.c

@@ -0,0 +1,129 @@
+// Copyright (c) 2004-2020 Microchip Technology Inc. and its subsidiaries.
+// SPDX-License-Identifier: MIT
+
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ *
+ ********************************************************************/
+
+/**
+ * Mera lib log callbacks adapted for P-Net.
+ * Based on code in mera demo.
+ */
+
+#include "pf_includes.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include <string.h>
+#include <inttypes.h>
+#include "microchip/ethernet/rte/api.h"
+
+#define TRACE_GROUP_DEFAULT 0
+#define TRACE_GROUP_CNT     1
+
+typedef struct
+{
+   const char * name;
+   mera_trace_level_t level;
+} trace_group_t;
+
+static trace_group_t trace_groups[] = {
+   {.name = "default", .level = MERA_TRACE_LEVEL_NOISE},
+   {.name = "ib", .level = MERA_TRACE_LEVEL_NOISE},
+   {.name = "ob", .level = MERA_TRACE_LEVEL_NOISE},
+};
+
+static void printf_trace_head (
+   const char * mname,
+   const char * gname,
+   const mera_trace_level_t level,
+   const char * file,
+   const int line,
+   const char * function,
+   const char * lcont)
+{
+   struct timeval tv;
+   int h, m, s;
+   const char *p, *base_name = file;
+
+   for (p = file; *p != 0; p++)
+   {
+      if (*p == '/' || *p == '\\')
+      {
+         base_name = (p + 1);
+      }
+   }
+
+   (void)gettimeofday (&tv, NULL);
+   h = (tv.tv_sec / 3600 % 24);
+   m = (tv.tv_sec / 60 % 60);
+   s = (tv.tv_sec % 60);
+   printf (
+      "%u:%02u:%02u:%05lu %s/%s/%s %s(%u) %s%s",
+      h,
+      m,
+      s,
+      tv.tv_usec,
+      mname,
+      gname,
+      level == MERA_TRACE_LEVEL_ERROR
+         ? "error"
+         : level == MERA_TRACE_LEVEL_INFO
+              ? "info"
+              : level == MERA_TRACE_LEVEL_DEBUG
+                   ? "debug"
+                   : level == MERA_TRACE_LEVEL_NOISE ? "noise" : "?",
+      base_name,
+      line,
+      function,
+      lcont);
+}
+
+static void mera_printf_trace_head (
+   const mera_trace_group_t group,
+   const mera_trace_level_t level,
+   const char * file,
+   const int line,
+   const char * function,
+   const char * lcont)
+{
+   printf_trace_head (
+      "p-net",
+      (int)group < (int)NELEMENTS (trace_groups) ? trace_groups[group].name
+                                                 : "?",
+      level,
+      file,
+      line,
+      function,
+      lcont);
+}
+
+void pf_mera_callout_trace_printf (
+   const mera_trace_group_t group,
+   const mera_trace_level_t level,
+   const char * file,
+   const int line,
+   const char * function,
+   const char * format,
+   ...)
+{
+   va_list args;
+
+   mera_printf_trace_head (group, level, file, line, function, ": ");
+   va_start (args, format);
+   vprintf (format, args);
+   va_end (args);
+   printf ("\n");
+   fflush (stdout);
+}

+ 38 - 0
src/drivers/lan9662/src/pf_mera_trace.h

@@ -0,0 +1,38 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef PF_MERA_TRACE_H
+#define PF_MERA_TRACE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "microchip/ethernet/rte/api.h"
+
+void pf_mera_callout_trace_printf (
+   const mera_trace_group_t group,
+   const mera_trace_level_t level,
+   const char * file,
+   const int line,
+   const char * function,
+   const char * format,
+   ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PF_MERA_TRACE_H */

+ 328 - 0
src/drivers/lan9662/src/pf_ppm_driver_lan9662.c

@@ -0,0 +1,328 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2021 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#include "pf_includes.h"
+#include "pf_lan9662_mera.h"
+
+#include <string.h>
+#include <inttypes.h>
+
+/**
+ * @file
+ * @brief PPM driver for LAN9662
+ *
+ */
+
+int pf_ppm_drv_lan9662_create (pnet_t * net, pf_ar_t * p_ar, uint32_t crep)
+{
+   pf_mera_frame_cfg_t mera_ppm_cfg = {0};
+
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "PPM_DRV_MERA(%d): Allocate RTE frame (inbound data / tx frames) for "
+      "AREP %u "
+      "CREP "
+      "%" PRIu32 "\n",
+      __LINE__,
+      p_ar->arep,
+      crep);
+
+   mera_ppm_cfg.p_iocr = &p_ar->iocrs[crep];
+
+   pf_mera_get_port_from_remote_mac (
+      &p_ar->ar_param.cm_initiator_mac_add,
+      &mera_ppm_cfg.port);
+
+   memcpy (
+      &mera_ppm_cfg.main_mac_addr,
+      &net->pf_interface.main_port.mac_address,
+      sizeof (pnet_ethaddr_t));
+
+   p_ar->iocrs[crep].ppm.frame = pf_mera_ppm_alloc (net, &mera_ppm_cfg);
+
+   if (p_ar->iocrs[crep].ppm.frame == NULL)
+   {
+      LOG_ERROR (
+         PF_PPM_LOG,
+         "PPM_DRV_MERA(%d): Frame allocation failed\n",
+         __LINE__);
+      return -1;
+   }
+   return 0;
+}
+
+int pf_ppm_drv_lan9662_activate_req (pnet_t * net, pf_ar_t * p_ar, uint32_t crep)
+{
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "PPM_DRV_MERA(%d): Activate RTE (inbound data / tx frames) for AREP %u "
+      "CREP "
+      "%" PRIu32 "\n",
+      __LINE__,
+      p_ar->arep,
+      crep);
+
+   if (p_ar->iocrs[crep].ppm.frame == NULL)
+   {
+      LOG_ERROR (
+         PF_PPM_LOG,
+         "PPM_DRV_MERA(%d): No mera frame allocated\n",
+         __LINE__);
+      return -1;
+   }
+
+   return pf_mera_ppm_start (net, p_ar->iocrs[crep].ppm.frame);
+}
+
+/**
+ * @internal
+ * Close RTE resources used in PPM frame.
+ * @param net              InOut: The p-net stack instance
+ * @param arg              In:    PPM frame
+ * @param current_time     In:    The current system time, in microseconds,
+ *                                when the scheduler is started to execute
+ *                                stored tasks.
+ */
+static void pf_ppm_drv_lan9662_delayed_close (
+   pnet_t * net,
+   void * arg,
+   uint32_t current_time)
+{
+   pf_drv_frame_t * frame = (pf_drv_frame_t *)arg;
+   pf_mera_ppm_stop (net, frame);
+   pf_mera_ppm_free (net, frame);
+}
+
+int pf_ppm_drv_lan9662_close_req (pnet_t * net, pf_ar_t * p_ar, uint32_t crep)
+{
+   LOG_DEBUG (
+      PF_PPM_LOG,
+      "PPM(%d): Stop RTE for AREP %u CREP "
+      "%" PRIu32 "\n",
+      __LINE__,
+      p_ar->arep,
+      crep);
+
+   /* Delay close of pf_mera resources
+    * Workaround for delayed alarm transmission.
+    * Close RTE resources after alarm is sent.
+    */
+   pf_scheduler_init_handle (&p_ar->iocrs[crep].ppm.ci_timeout, "ppm_close");
+   pf_scheduler_add (
+      net,
+      2,
+      pf_ppm_drv_lan9662_delayed_close,
+      p_ar->iocrs[crep].ppm.frame,
+      &p_ar->iocrs[crep].ppm.ci_timeout);
+
+   return 0;
+}
+
+/**
+ * Write the ppm frame buffer
+ * @param iocr    InOut: Communication relation
+ * @param offset  In: Buffer offset to write
+ * @param data    In: Data source
+ * @param len     In: Data length
+ * @return 0 on success, -1 on error
+ */
+static int pf_ppm_drv_lan9662_write_frame_buffer (
+   pf_iocr_t * iocr,
+   uint32_t offset,
+   const uint8_t * data,
+   uint16_t len)
+{
+   memcpy (&iocr->ppm.buffer_data[offset], data, len);
+   return 0;
+}
+/**
+ * Read the ppm frame buffer
+ * @param iocr    InOut: Communication relation
+ * @param offset  In: Buffer offset to read
+ * @param data    In: Data destination
+ * @param len     In: Data length
+ * @return 0 on success, -1 on error
+ */
+static int pf_ppm_drv_lan9662_read_frame_buffer (
+   pf_iocr_t * iocr,
+   uint32_t offset,
+   uint8_t * data,
+   uint16_t len)
+{
+   memcpy (data, &iocr->ppm.buffer_data[offset], len);
+   return 0;
+}
+
+/**
+ * Update the PPM frame buffer and write
+ * data and iops to the active RTE frame.
+ */
+static int pf_ppm_drv_lan9662_write_data_and_iops (
+   pnet_t * net,
+   pf_iocr_t * iocr,
+   const pf_iodata_object_t * p_iodata,
+   const uint8_t * data,
+   uint16_t len,
+   const uint8_t * iops,
+   uint8_t iops_len)
+{
+   int ret = 0;
+
+   if (data != NULL)
+   {
+      pf_ppm_drv_lan9662_write_frame_buffer (
+         iocr,
+         p_iodata->data_offset,
+         data,
+         len);
+   }
+   pf_ppm_drv_lan9662_write_frame_buffer (
+      iocr,
+      p_iodata->iops_offset,
+      iops,
+      iops_len);
+
+   if (pf_mera_is_active_frame (iocr->ppm.frame))
+   {
+      /* Only initiate write to RTE if frame is active.
+       * Initialization values are still written to
+       * frame buffer and are used as initial SRAM values.
+       */
+
+      if (data != NULL)
+      {
+         ret =
+            pf_mera_ppm_write_input (net, iocr->ppm.frame, p_iodata, data, len);
+      }
+
+      if (ret == 0)
+      {
+         ret = pf_mera_ppm_write_iops (
+            net,
+            iocr->ppm.frame,
+            p_iodata,
+            iops,
+            iops_len);
+      }
+   }
+
+   return ret;
+}
+
+/**
+ * Read data and iops from SRAM
+ */
+static int pf_ppm_drv_lan9662_read_data_and_iops (
+   pnet_t * net,
+   pf_iocr_t * iocr,
+   const pf_iodata_object_t * p_iodata,
+   uint8_t * data,
+   uint16_t data_len,
+   uint8_t * iops,
+   uint8_t iops_len)
+{
+   return pf_mera_ppm_read_data_and_iops (
+      net,
+      iocr->ppm.frame,
+      p_iodata,
+      data,
+      data_len,
+      iops,
+      iops_len);
+}
+
+/**
+ * Write consumer status for a subslot.
+ * Write the PPM frame buffer and the active RTE frame.
+ */
+static int pf_ppm_drv_lan9662_write_iocs (
+   pnet_t * net,
+   pf_iocr_t * iocr,
+   const pf_iodata_object_t * p_iodata,
+   const uint8_t * iocs,
+   uint8_t iocs_len)
+{
+   return pf_ppm_drv_lan9662_write_frame_buffer (
+      iocr,
+      p_iodata->iocs_offset,
+      iocs,
+      iocs_len);
+}
+
+/**
+ * Read consumer status previously written
+ * using pf_ppm_drv_lan9662_write_iocs().
+ * Value is fetched from the PPM frame buffer,
+ * not from the RTE.
+ */
+static int pf_ppm_drv_lan9662_read_iocs (
+   pnet_t * net,
+   pf_iocr_t * iocr,
+   const pf_iodata_object_t * p_iodata,
+   uint8_t * iocs,
+   uint8_t iocs_len)
+{
+
+   return pf_ppm_drv_lan9662_read_frame_buffer (
+      iocr,
+      p_iodata->iocs_offset,
+      iocs,
+      iocs_len);
+}
+
+static int pf_ppm_drv_lan9662_write_data_status (
+   pnet_t * net,
+   pf_iocr_t * iocr,
+   uint8_t data_status)
+{
+   pf_ppm_drv_lan9662_write_frame_buffer (
+      iocr,
+      iocr->ppm.data_status_offset,
+      &data_status,
+      sizeof (data_status));
+
+   return pf_mera_ppm_write_data_status (net, iocr->ppm.frame, data_status);
+}
+
+/**
+ * Show the current state of the driver
+ * @param p_ppm   In: PPM instance
+ */
+void pf_ppm_drv_lan9662_show (const pf_ppm_t * p_ppm)
+{
+   printf ("Not implemented:  pf_ppm_drv_lan9662_show()\n");
+}
+
+int pf_driver_ppm_init (pnet_t * net)
+{
+   static const pf_ppm_driver_t drv = {
+      .create = pf_ppm_drv_lan9662_create,
+      .activate_req = pf_ppm_drv_lan9662_activate_req,
+      .close_req = pf_ppm_drv_lan9662_close_req,
+      .write_data_and_iops = pf_ppm_drv_lan9662_write_data_and_iops,
+      .read_data_and_iops = pf_ppm_drv_lan9662_read_data_and_iops,
+      .write_iocs = pf_ppm_drv_lan9662_write_iocs,
+      .read_iocs = pf_ppm_drv_lan9662_read_iocs,
+      .write_data_status = pf_ppm_drv_lan9662_write_data_status,
+      .show = pf_ppm_drv_lan9662_show};
+
+   net->ppm_drv = &drv;
+
+   LOG_INFO (
+      PF_PPM_LOG,
+      "PPM_DRV(%d): LAN9662 PPM driver installed\n",
+      __LINE__);
+   return 0;
+}

+ 182 - 0
src/drivers/lan9662/src/pf_rte_uio.c

@@ -0,0 +1,182 @@
+// Copyright (c) 2004-2020 Microchip Technology Inc. and its subsidiaries.
+// SPDX-License-Identifier: MIT
+
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ *
+ ********************************************************************/
+
+/**
+ * RTE UIO driver adapted for P-Net.
+ * Implements register read and write operations.
+ * Based on code in mera demo.
+ */
+
+#include <stdio.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <linux/i2c.h>     /* I2C support */
+#include <linux/i2c-dev.h> /* I2C support */
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/select.h>
+#include <sys/syscall.h>
+#include <sys/un.h>
+#include <endian.h>
+
+#include "microchip/ethernet/rte/api.h"
+#include "pf_includes.h"
+
+#define T_D(...) LOG_DEBUG (PNET_LOG, __VA_ARGS__)
+#define T_I(...) LOG_INFO (PNET_LOG, __VA_ARGS__)
+#define T_E(...) LOG_ERROR (PNET_LOG, __VA_ARGS__)
+
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+#define PCIE_HOST_CVT(x)                                                       \
+   __builtin_bswap32 ((x)) /* PCIe is LE - we're BE, so swap */
+#else
+#define PCIE_HOST_CVT(x) (x) /* We're LE already */
+#endif
+
+volatile uint32_t * base_mem = NULL;
+
+int pf_rte_uio_reg_read (
+   struct mera_inst * inst,
+   const uintptr_t addr,
+   uint32_t * data)
+{
+   if (base_mem != NULL)
+   {
+      *data = PCIE_HOST_CVT (base_mem[addr]);
+      return 0;
+   }
+   return -1;
+}
+
+int pf_rte_uio_reg_write (
+   struct mera_inst * inst,
+   const uintptr_t addr,
+   const uint32_t data)
+{
+   if (base_mem != NULL)
+   {
+      base_mem[addr] = PCIE_HOST_CVT (data);
+      return 0;
+   }
+   return -1;
+}
+
+int pf_rte_uio_init (void)
+{
+   const char * driver = "mscc_switch";
+   const char * top = "/sys/class/uio";
+   DIR * dir;
+   struct dirent * dent;
+   char fn[PATH_MAX], devname[128];
+   FILE * fp;
+   char iodev[512];
+   size_t mapsize;
+   int dev_fd, len, rc = -1;
+
+   if (!(dir = opendir (top)))
+   {
+      T_E ("operdir(%s) failed\n", top);
+      return rc;
+   }
+
+   while ((dent = readdir (dir)) != NULL)
+   {
+      if (dent->d_name[0] == '.')
+      {
+         continue;
+      }
+
+      snprintf (fn, sizeof (fn), "%s/%s/name", top, dent->d_name);
+      fp = fopen (fn, "r");
+      if (!fp)
+      {
+         T_E ("UIO: Failed to open: %s\n", fn);
+         continue;
+      }
+
+      const char * rrd = fgets (devname, sizeof (devname), fp);
+      fclose (fp);
+
+      if (!rrd)
+      {
+         T_E ("UIO: Failed to read: %s\n", fn);
+         continue;
+      }
+
+      len = strlen (devname);
+      if (len > 0 && devname[len - 1] == '\n')
+      {
+         devname[len - 1] = '\0';
+      }
+      T_D ("UIO: %s -> %s\n", fn, devname);
+      if (
+         !strstr (devname, "mscc_switch") &&
+         !strstr (devname, "vcoreiii_switch") &&
+         !strstr (devname, "lan966x_uio"))
+      {
+         continue;
+      }
+
+      snprintf (iodev, sizeof (iodev), "/dev/%s", dent->d_name);
+      snprintf (fn, sizeof (fn), "%s/%s/maps/map0/size", top, dent->d_name);
+      fp = fopen (fn, "r");
+      if (!fp)
+      {
+         continue;
+      }
+
+      if (fscanf (fp, "%zi", &mapsize))
+      {
+         fclose (fp);
+         rc = 0;
+         T_I ("Using UIO device: %s\n", devname);
+         break;
+      }
+      fclose (fp);
+   }
+   closedir (dir);
+
+   if (rc < 0)
+   {
+      T_E ("No suitable UIO device found!\n");
+      return rc;
+   }
+
+   /* Open the UIO device file */
+   T_D ("Using UIO, found '%s' driver at %s\n", driver, iodev);
+   dev_fd = open (iodev, O_RDWR);
+   if (dev_fd < 1)
+   {
+      T_E ("open(%s) failed\n", iodev);
+      rc = -1;
+   }
+   else
+   {
+      /* mmap the UIO device */
+      base_mem =
+         mmap (NULL, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, 0);
+      if (base_mem != MAP_FAILED)
+      {
+         T_D ("Mapped register memory @ %p\n", base_mem);
+      }
+      else
+      {
+         T_E ("mmap failed\n");
+         rc = -1;
+      }
+   }
+   return rc;
+}

+ 59 - 0
src/drivers/lan9662/src/pf_rte_uio.h

@@ -0,0 +1,59 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2020 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef PF_UIO_H
+#define PF_UIO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Init RTE UIO driver
+ * The RTE driver provides access to RTE registers from user space.
+ * The register read and write operations are used by the MERA library.
+ * @return 0 on success, - 1 on error error
+ */
+int pf_rte_uio_init (void);
+
+/**
+ * Read RTE register
+ * @param inst    In: MERA library instance
+ * @param addr    In: Register address
+ * @param data    In: Register value
+ * @return 0 on success, - 1 on error error
+ */
+int pf_rte_uio_reg_read (
+   struct mera_inst * inst,
+   const uintptr_t addr,
+   uint32_t * data);
+
+/**
+ * Write RTE register
+ * @param inst    In: MERA library instance
+ * @param addr    In: Register address
+ * @param data    InOut: Register value
+ * @return 0 on success, - 1 on error error
+ */
+int pf_rte_uio_reg_write (
+   struct mera_inst * inst,
+   const uintptr_t addr,
+   const uint32_t data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PF_RTE_UIO_H */

+ 343 - 0
src/drivers/lan9662/src/pf_sram_uio.c

@@ -0,0 +1,343 @@
+// Copyright (c) 2004-2020 Microchip Technology Inc. and its subsidiaries.
+// SPDX-License-Identifier: MIT
+
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ *
+ ********************************************************************/
+
+/**
+ * SRAM UIO driver adapted for P-Net.
+ * Implements read and write operations as well as
+ * a allocation/frame concept.
+ * Based on code in mera demo.
+ */
+
+#include <stdio.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/select.h>
+#include <sys/syscall.h>
+#include <sys/un.h>
+#include <endian.h>
+
+#include "microchip/ethernet/rte/api.h"
+#include "pf_includes.h"
+
+#include "pf_sram_uio.h"
+
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+#define HOST_CVT(x) (x)
+#else
+#define HOST_CVT(x) __builtin_bswap32 ((x)) /* PCIe is LE, so swap */
+#endif
+
+/* Do not start on address 0 - 0 is used "NULL"*/
+#define PF_SRAM_AREA_START_ADDRESS 100
+#define PF_SRAM_FRAME_SIZE         1600
+#define PF_SRAM_MAX_FRAMES         4
+#define PF_SRAM_MAX_ADDRESS        0x1FFFF
+
+typedef union pf_uio_reg
+{
+   uint32_t val;
+   uint8_t data[4];
+} pf_uio_reg_t;
+
+typedef struct pf_sram_frame
+{
+   bool used;
+   uint16_t start_address;
+} pf_sram_frame_t;
+
+typedef struct pf_sram_uio
+{
+   int dev_fd;
+   volatile uint32_t * base_mem;
+   pf_sram_frame_t frames[PF_SRAM_MAX_FRAMES];
+} pf_sram_uio_t;
+
+pf_sram_uio_t sram_uio = {0};
+
+static int uio_init (pf_sram_uio_t * handle)
+{
+   const char * driver = "mscc_sram";
+   const char * top = "/sys/class/uio";
+   DIR * dir;
+   struct dirent * dent;
+   char fn[PATH_MAX], devname[128];
+   FILE * fp;
+   char iodev[512];
+   size_t mapsize;
+   int len, rc = -1;
+
+   handle->dev_fd = -1;
+
+   if (!(dir = opendir (top)))
+   {
+      LOG_ERROR (PF_PPM_LOG, "SRAM UIO - opendir(%s) failed", top);
+      return rc;
+   }
+
+   while ((dent = readdir (dir)) != NULL)
+   {
+      if (dent->d_name[0] == '.')
+      {
+         continue;
+      }
+
+      snprintf (fn, sizeof (fn), "%s/%s/name", top, dent->d_name);
+      fp = fopen (fn, "r");
+      if (!fp)
+      {
+         LOG_ERROR (PF_PPM_LOG, "SRAM UIO -  Failed to open: %s", fn);
+         continue;
+      }
+
+      const char * rrd = fgets (devname, sizeof (devname), fp);
+      fclose (fp);
+
+      if (!rrd)
+      {
+         LOG_ERROR (PF_PPM_LOG, "SRAM UIO - Failed to read: %s", fn);
+         continue;
+      }
+
+      len = strlen (devname);
+      if (len > 0 && devname[len - 1] == '\n')
+      {
+         devname[len - 1] = '\0';
+      }
+
+      if (!strstr (devname, driver))
+      {
+         continue;
+      }
+
+      snprintf (iodev, sizeof (iodev), "/dev/%s", dent->d_name);
+      snprintf (fn, sizeof (fn), "%s/%s/maps/map0/size", top, dent->d_name);
+      fp = fopen (fn, "r");
+      if (!fp)
+      {
+         continue;
+      }
+
+      if (fscanf (fp, "%zi", &mapsize))
+      {
+         fclose (fp);
+         rc = 0;
+         LOG_DEBUG (PF_PPM_LOG, "SRAM UIO - Using device: %s\n", devname);
+         break;
+      }
+      fclose (fp);
+   }
+   closedir (dir);
+
+   if (rc < 0)
+   {
+      LOG_ERROR (PF_PPM_LOG, "SRAM UIO - No suitable UIO device found!\n");
+      return rc;
+   }
+
+   /* Open the UIO device file */
+   LOG_INFO (
+      PF_PPM_LOG,
+      "SRAM UIO - found '%s' driver at %s, size: %zu\n",
+      driver,
+      iodev,
+      mapsize);
+   handle->dev_fd = open (iodev, O_RDWR);
+   if (handle->dev_fd < 1)
+   {
+      LOG_INFO (PF_PPM_LOG, "SRAM UIO - open(%s) failed\n", iodev);
+      rc = -1;
+   }
+   else
+   {
+      /* mmap the UIO device */
+      handle->base_mem = mmap (
+         NULL,
+         mapsize,
+         PROT_READ | PROT_WRITE,
+         MAP_SHARED,
+         handle->dev_fd,
+         0);
+      if (handle->base_mem != MAP_FAILED)
+      {
+         LOG_DEBUG (
+            PF_PPM_LOG,
+            "SRAM UIO - Mapped register memory @ %p\n",
+            handle->base_mem);
+      }
+      else
+      {
+         LOG_ERROR (PF_PPM_LOG, "SRAM UIO - mmap failed\n");
+         rc = -1;
+      }
+   }
+   return rc;
+}
+
+int pf_sram_init (void)
+{
+   uint16_t i;
+
+   for (i = 0; i < PF_SRAM_MAX_FRAMES; i++)
+   {
+      sram_uio.frames[i].used = false;
+      sram_uio.frames[i].start_address =
+         PF_SRAM_AREA_START_ADDRESS + i * PF_SRAM_FRAME_SIZE;
+   }
+
+   if (uio_init (&sram_uio) != 0)
+   {
+      return -1;
+   }
+
+   return 0;
+}
+
+uint16_t pf_sram_frame_alloc (void)
+{
+   uint16_t i;
+
+   for (i = 0; i < PF_SRAM_MAX_FRAMES; i++)
+   {
+      if (sram_uio.frames[i].used == false)
+      {
+         sram_uio.frames[i].used = true;
+         return sram_uio.frames[i].start_address;
+      }
+   }
+
+   return PF_SRAM_INVALID_ADDRESS;
+}
+
+void pf_sram_frame_free (uint16_t frame_start_address)
+{
+   uint16_t i;
+
+   for (i = 0; i < PF_SRAM_MAX_FRAMES; i++)
+   {
+      if (sram_uio.frames[i].start_address == frame_start_address)
+      {
+         sram_uio.frames[i].used = false;
+         return;
+      }
+   }
+
+   LOG_ERROR (
+      PF_PPM_LOG,
+      "SRAM UIO - failed to free frame at address 0x%x\n",
+      frame_start_address);
+}
+
+static int reg_check (uint32_t addr)
+{
+   if (addr >= PF_SRAM_MAX_ADDRESS)
+   {
+      LOG_ERROR (PF_PPM_LOG, "SRAM UIO - address range is 17 bits\n");
+      return -1;
+   }
+   if (addr & 3)
+   {
+      LOG_ERROR (PF_PPM_LOG, "SRAM UIO - address must be 32-bit word aligned\n");
+      return -1;
+   }
+   return 0;
+}
+
+static int reg_read (uint32_t addr, uint32_t * val)
+{
+   if (reg_check (addr) < 0)
+   {
+      return -1;
+   }
+   *val = HOST_CVT (sram_uio.base_mem[addr / 4]);
+   return 0;
+}
+
+static int reg_write (uint32_t addr, uint32_t val)
+{
+   if (reg_check (addr) < 0)
+   {
+      return -1;
+   }
+   sram_uio.base_mem[addr / 4] = HOST_CVT (val);
+   return 0;
+}
+
+int pf_sram_write (uint16_t address, const uint8_t * data, uint16_t length)
+{
+   pf_uio_reg_t tmp;
+   uint32_t uio_reg_addr = address & 0xFFFFFFFC;
+   uint32_t offset;
+   uint32_t index = 0;
+   uint32_t i = 0;
+
+   offset = address - uio_reg_addr;
+   while (index < length)
+   {
+      if (reg_read (uio_reg_addr, &tmp.val) != 0)
+      {
+         return -1;
+      }
+
+      for (i = offset; i <= 3; i++)
+      {
+         if (index < length)
+         {
+            tmp.data[3 - i] = data[index++];
+         }
+      }
+
+      if (reg_write (uio_reg_addr, tmp.val) != 0)
+      {
+         return -1;
+      }
+
+      uio_reg_addr += 4;
+      offset = 0;
+   }
+   return 0;
+}
+
+int pf_sram_read (uint16_t address, uint8_t * data, uint16_t length)
+{
+   pf_uio_reg_t tmp;
+   uint32_t uio_reg_addr = address & 0xFFFFFFFC;
+   uint32_t offset;
+   uint32_t index = 0;
+   uint32_t i = 0;
+
+   offset = address - uio_reg_addr;
+   while (index < length)
+   {
+      if (reg_read (uio_reg_addr, &tmp.val) != 0)
+      {
+         return -1;
+      }
+
+      for (i = offset; i <= 3; i++)
+      {
+         if (index < length)
+         {
+            data[index++] = tmp.data[3 - i];
+         }
+      }
+
+      uio_reg_addr += 4;
+      offset = 0;
+   }
+   return 0;
+}

+ 68 - 0
src/drivers/lan9662/src/pf_sram_uio.h

@@ -0,0 +1,68 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * www.rt-labs.com
+ * Copyright 2020 rt-labs AB, Sweden.
+ *
+ * This software is dual-licensed under GPLv3 and a commercial
+ * license. See the file LICENSE.md distributed with this software for
+ * full license information.
+ ********************************************************************/
+
+#ifndef PF_SRAM_UIO_H
+#define PF_SRAM_UIO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PF_SRAM_INVALID_ADDRESS 0
+
+/**
+ * Init SRAM UIO driver.
+ * The SRAM driver provides access to LAN9662 SRAM used by the RTE.
+ * The driver implements a frame concept for managing the SRAM
+ * as frames mapping to a inbound or outbound RTE configurations.
+ * @return 0 on success, - 1 on error error
+ */
+int pf_sram_init (void);
+
+/**
+ * Allocate SRAM frame
+ * @return Frame start address, PF_SRAM_INVALID_ADDRESS on error
+ */
+uint16_t pf_sram_frame_alloc (void);
+
+/**
+ * Free SRAM frame
+ * @param Frame start address
+ */
+void pf_sram_frame_free (uint16_t frame_start_address);
+
+/**
+ * Write SRAM
+ * @param address    In: SRAM address to write
+ * @param data       In: Data
+ * @param length     In: Length of data
+ * @return 0 on success, -1 on error
+ */
+int pf_sram_write (uint16_t address, const uint8_t * data, uint16_t length);
+
+/**
+ * Read SRAM
+ * @param address    In:  SRAM address to read
+ * @param data       Out: Data
+ * @param length     In:  Length of data
+ * @return 0 on success, -1 on error
+ */
+int pf_sram_read (uint16_t address, uint8_t * data, uint16_t length);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PF_PORT_H */

+ 31 - 2
src/ports/linux/sampleapp_main.c

@@ -110,6 +110,13 @@ void show_usage()
            "Defaults to not read Button2.\n");
    printf ("   -p PATH      Absolute path to storage directory. Defaults to "
            "use current directory.\n");
+#if PNET_OPTION_DRIVER_ENABLE
+   printf ("   -m MODE      Application offload mode. Only used if P-Net is\n");
+   printf ("                built with hw offload enabled "
+           "                (PNET_OPTION_DRIVER_ENABLE). \n");
+   printf ("                Supported modes: none, cpu, full\n");
+   printf ("                Defaults to none\n");
+#endif
    printf ("\n");
    printf ("p-net revision: " PNET_VERSION "\n");
 }
@@ -146,8 +153,9 @@ app_args_t parse_commandline_arguments (int argc, char * argv[])
    output_arguments.show = 0;
    output_arguments.factory_reset = false;
    output_arguments.remove_files = false;
+   output_arguments.mode = MODE_HW_OFFLOAD_NONE;
 
-   while ((option = getopt (argc, argv, "hvgfri:s:b:d:p:")) != -1)
+   while ((option = getopt (argc, argv, "hvgfri:s:b:d:p:m:")) != -1)
    {
       switch (option)
       {
@@ -198,6 +206,27 @@ app_args_t parse_commandline_arguments (int argc, char * argv[])
          }
          strcpy (output_arguments.path_storage_directory, optarg);
          break;
+#if PNET_OPTION_DRIVER_ENABLE
+      case 'm':
+         if (strcmp ("none", optarg) == 0)
+         {
+            output_arguments.mode = MODE_HW_OFFLOAD_NONE;
+         }
+         else if (strcmp ("cpu", optarg) == 0)
+         {
+            output_arguments.mode = MODE_HW_OFFLOAD_CPU;
+         }
+         else if (strcmp ("full", optarg) == 0)
+         {
+            output_arguments.mode = MODE_HW_OFFLOAD_FULL;
+         }
+         else
+         {
+            printf ("Error: mode (-m) not supported.\n");
+            exit (EXIT_FAILURE);
+         }
+         break;
+#endif
       case 'h':
          /* fallthrough */
       case '?':
@@ -436,7 +465,7 @@ int main (int argc, char * argv[])
    }
 
    /* Initialise stack and application */
-   sample_app = app_init (&pnet_cfg);
+   sample_app = app_init (&pnet_cfg, &app_args);
    if (sample_app == NULL)
    {
       printf ("Failed to initialize P-Net.\n");