From e1f72ad7179fb70b7ce73a5e81bda7981f2b2ff2 Mon Sep 17 00:00:00 2001 From: Alexandre Howard Date: Thu, 11 Jul 2024 17:30:14 +0200 Subject: [PATCH] GenAVB/TSN SDK release 6.2.0 --- .gitignore | 63 + CMakeLists.linux | 51 + README => CMakeLists.rtos | 0 CMakeLists.txt | 57 + README.md | 178 + SCR_genavb-release.txt | 47 + api/api.cmake | 19 + api/clock.c | 46 + api/clock.h | 23 + api/config.h | 21 + api/control.c | 481 + api/control.h | 38 + api/error.c | 185 + api/init.h | 23 + api/linux/api.cmake | 22 + api/linux/api_os/control.h | 27 + api/linux/api_os/init.h | 28 + api/linux/api_os/streaming.h | 34 + api/linux/apis.map | 48 + api/linux/control.c | 167 + api/linux/init.c | 139 + api/linux/socket.c | 57 + api/linux/streaming.c | 723 ++ api/rtos/api.cmake | 46 + api/rtos/api_os/control.h | 34 + api/rtos/api_os/init.h | 54 + api/rtos/api_os/streaming.h | 29 + api/rtos/control.c | 190 + api/rtos/dsa.c | 21 + api/rtos/fdb.c | 44 + api/rtos/frer.c | 98 + api/rtos/generic.c | 72 + api/rtos/hsr.c | 40 + api/rtos/init.c | 393 + api/rtos/psfp.c | 151 + api/rtos/qos.c | 146 + api/rtos/socket.c | 160 + api/rtos/stream_identification.c | 32 + api/rtos/streaming.c | 250 + api/rtos/timer.c | 131 + api/rtos/vlan.c | 62 + api/socket.c | 388 + api/socket.h | 43 + api/streaming.c | 158 + api/streaming.h | 27 + api/timer.h | 26 + .../aem-manager/aem-audio-entities.cmake | 12 + .../aem-audio-video-entities.cmake | 3 + .../aem-avnu-vertification-entities.cmake | 3 + .../aem-manager/aem-controller-entities.cmake | 3 + .../aem-manager/aem-manager-helpers.cmake | 5 + .../aem-manager/aem-video-entities.cmake | 6 + apps/common/aem-manager/aem_manager_helpers.c | 430 + apps/common/aem-manager/aem_manager_helpers.h | 48 + apps/common/aem-manager/avdecc_controller.c | 23 + apps/common/aem-manager/avdecc_controller.h | 76 + apps/common/aem-manager/avnu_certification.c | 23 + apps/common/aem-manager/avnu_certification.h | 1029 ++ .../aem-manager/listener_audio_single.c | 23 + .../aem-manager/listener_audio_single.h | 257 + .../aem-manager/listener_audio_single_milan.c | 22 + .../aem-manager/listener_audio_single_milan.h | 272 + .../listener_talker_audio_single.c | 23 + .../listener_talker_audio_single.h | 347 + .../listener_talker_audio_single_milan.c | 22 + .../listener_talker_audio_single_milan.h | 443 + .../common/aem-manager/listener_video_multi.c | 23 + .../common/aem-manager/listener_video_multi.h | 491 + .../aem-manager/listener_video_single.c | 23 + .../aem-manager/listener_video_single.h | 284 + apps/common/aem-manager/talker_audio_single.c | 23 + apps/common/aem-manager/talker_audio_single.h | 241 + .../aem-manager/talker_audio_single_milan.c | 22 + .../aem-manager/talker_audio_single_milan.h | 259 + apps/common/aem-manager/talker_audio_video.c | 23 + apps/common/aem-manager/talker_audio_video.h | 473 + .../talker_listener_audio_default.c | 23 + .../talker_listener_audio_default.h | 424 + .../aem-manager/talker_listener_audio_multi.c | 23 + .../aem-manager/talker_listener_audio_multi.h | 1047 ++ .../talker_listener_audio_multi_aaf.c | 23 + .../talker_listener_audio_multi_aaf.h | 1047 ++ .../talker_listener_audio_multi_format.c | 22 + .../talker_listener_audio_multi_format.h | 1046 ++ apps/common/aem-manager/talker_video_multi.c | 23 + apps/common/aem-manager/talker_video_multi.h | 503 + apps/common/aem-manager/talker_video_single.c | 23 + apps/common/aem-manager/talker_video_single.h | 326 + apps/linux/CMakeLists.txt | 61 + apps/linux/aem-manager/CMakeLists.txt | 72 + apps/linux/aem-manager/main.c | 304 + apps/linux/alsa-audio-app/CMakeLists.txt | 31 + apps/linux/alsa-audio-app/main.c | 686 + apps/linux/common/acmp.c | 127 + apps/linux/common/acmp.h | 20 + apps/linux/common/adp.c | 188 + apps/linux/common/adp.h | 16 + apps/linux/common/aecp.c | 593 + apps/linux/common/aecp.h | 29 + apps/linux/common/alsa.c | 637 + apps/linux/common/alsa.h | 66 + apps/linux/common/alsa2.c | 1226 ++ apps/linux/common/alsa2.h | 173 + apps/linux/common/alsa_config.h | 50 + apps/linux/common/alsa_stream.c | 225 + apps/linux/common/alsa_stream.h | 48 + apps/linux/common/audio_mappings.c | 330 + apps/linux/common/audio_mappings.h | 17 + apps/linux/common/avb_stream.c | 326 + apps/linux/common/avb_stream.h | 49 + apps/linux/common/avb_stream_config.h | 60 + apps/linux/common/avdecc.c | 334 + apps/linux/common/avdecc.h | 15 + apps/linux/common/clock.c | 107 + apps/linux/common/clock.h | 39 + apps/linux/common/clock_domain.c | 278 + apps/linux/common/clock_domain.h | 46 + apps/linux/common/common.c | 740 ++ apps/linux/common/common.h | 140 + apps/linux/common/crf_stream.c | 215 + apps/linux/common/crf_stream.h | 38 + apps/linux/common/file_buffer.c | 131 + apps/linux/common/file_buffer.h | 53 + apps/linux/common/gst_pipeline_definitions.c | 690 + apps/linux/common/gst_pipeline_definitions.h | 67 + apps/linux/common/gstreamer.c | 747 ++ apps/linux/common/gstreamer.h | 172 + apps/linux/common/gstreamer.inc | 6 + apps/linux/common/gstreamer_custom_rt_pool.c | 122 + apps/linux/common/gstreamer_custom_rt_pool.h | 16 + apps/linux/common/gstreamer_multisink.c | 758 ++ apps/linux/common/gstreamer_multisink.h | 150 + apps/linux/common/gstreamer_single.c | 835 ++ apps/linux/common/gstreamer_single.h | 145 + apps/linux/common/helpers.h | 14 + apps/linux/common/log.c | 52 + apps/linux/common/log.h | 115 + apps/linux/common/msrp.c | 323 + apps/linux/common/msrp.h | 37 + apps/linux/common/open62541.inc | 6 + apps/linux/common/stats.c | 175 + apps/linux/common/stats.h | 54 + apps/linux/common/stream_stats.c | 100 + apps/linux/common/stream_stats.h | 46 + apps/linux/common/thread.c | 791 ++ apps/linux/common/thread.h | 146 + apps/linux/common/thread_config.h | 86 + apps/linux/common/time.c | 73 + apps/linux/common/time.h | 23 + apps/linux/common/timer.c | 100 + apps/linux/common/timer.h | 22 + apps/linux/common/ts_parser.c | 377 + apps/linux/common/ts_parser.h | 45 + .../genavb-controller-app/CMakeLists.txt | 25 + apps/linux/genavb-controller-app/main.c | 602 + apps/linux/genavb-controls-app/CMakeLists.txt | 23 + apps/linux/genavb-controls-app/main.c | 291 + apps/linux/genavb-media-app/CMakeLists.txt | 66 + apps/linux/genavb-media-app/alsa_config.c | 64 + .../genavb-media-app/avb_stream_config.c | 312 + .../linux/genavb-media-app/gstreamer_stream.c | 851 ++ .../linux/genavb-media-app/gstreamer_stream.h | 63 + apps/linux/genavb-media-app/h264_camera.c | 164 + apps/linux/genavb-media-app/h264_camera.h | 24 + apps/linux/genavb-media-app/main.c | 2104 +++ .../linux/genavb-media-app/multi_frame_sync.c | 263 + .../linux/genavb-media-app/multi_frame_sync.h | 19 + apps/linux/genavb-media-app/salsa_camera.c | 622 + apps/linux/genavb-media-app/salsa_camera.h | 31 + apps/linux/genavb-media-app/thread_config.c | 86 + .../genavb-multi-stream-app/CMakeLists.txt | 28 + apps/linux/genavb-multi-stream-app/main.c | 727 ++ .../genavb-video-player-app/CMakeLists.txt | 40 + .../genavb-video-player-app/gst_pipelines.c | 119 + .../genavb-video-player-app/gst_pipelines.h | 41 + .../linux/genavb-video-player-app/gstreamer.c | 473 + .../linux/genavb-video-player-app/gstreamer.h | 116 + .../gstreamer_single.c | 793 ++ .../gstreamer_single.h | 140 + apps/linux/genavb-video-player-app/main.c | 873 ++ .../genavb-video-player-app/salsa-camera.sh | 31 + .../genavb-video-server-app/CMakeLists.txt | 42 + apps/linux/genavb-video-server-app/main.c | 737 ++ .../genavb-video-server-app/pipeline_sm.dot | 27 + apps/linux/genavb-video-server-app/readme.txt | 63 + apps/linux/maap-ctrl-app/CMakeLists.txt | 20 + apps/linux/maap-ctrl-app/main.c | 312 + apps/linux/management-app/CMakeLists.txt | 24 + apps/linux/management-app/common.c | 149 + apps/linux/management-app/common.h | 16 + apps/linux/management-app/gptp_main.c | 364 + apps/linux/management-app/main.c | 47 + apps/linux/management-app/srp_main.c | 917 ++ apps/linux/msrp-ctrl-app/CMakeLists.txt | 21 + apps/linux/msrp-ctrl-app/main.c | 521 + apps/linux/salsacamctrl/CMakeLists.txt | 24 + apps/linux/salsacamctrl/main.c | 349 + apps/linux/salsacamctrl/salsacam-cmd.sh | 64 + apps/linux/salsacamctrl/salsacam-configs.inc | 20 + apps/linux/salsacamctrl/salsacam-setup.sh | 30 + apps/linux/salsacamctrl/udpcc2.c | 388 + apps/linux/salsacamctrl/udpcc2.h | 233 + apps/linux/simple-acf-app/CMakeLists.txt | 25 + apps/linux/simple-acf-app/main.c | 1024 ++ apps/linux/simple-audio-app/CMakeLists.txt | 27 + apps/linux/simple-audio-app/main.c | 947 ++ apps/linux/tsn-app/CMakeLists.txt | 44 + apps/linux/tsn-app/cyclic_task.c | 423 + apps/linux/tsn-app/cyclic_task.h | 70 + apps/linux/tsn-app/main.c | 429 + apps/linux/tsn-app/network_only.c | 95 + apps/linux/tsn-app/network_only.h | 21 + apps/linux/tsn-app/opcua/model/README.md | 47 + .../tsn-app/opcua/model/TsnApp.NodeSet2.xml | 3331 +++++ .../linux/tsn-app/opcua/model/tsn_app_model.c | 10735 ++++++++++++++++ .../linux/tsn-app/opcua/model/tsn_app_model.h | 24 + .../opcua/model/tsn_app_model_design.xml | 227 + .../opcua/model/tsn_app_ua_nodeid_header.h | 360 + apps/linux/tsn-app/opcua/opcua_server.c | 456 + apps/linux/tsn-app/opcua/opcua_server.h | 33 + apps/linux/tsn-app/serial_controller.c | 267 + apps/linux/tsn-app/serial_controller.h | 57 + apps/linux/tsn-app/thread_config.c | 38 + apps/linux/tsn-app/tsn_task.c | 687 + apps/linux/tsn-app/tsn_task.h | 166 + apps/linux/tsn-app/tsn_tasks_config.c | 191 + apps/linux/tsn-app/tsn_tasks_config.h | 47 + apps/linux/tsn-app/tsn_timer.c | 237 + apps/linux/tsn-app/tsn_timer.h | 55 + apps/rtos/aem-manager/aem-manager-rtos.cmake | 15 + apps/rtos/aem-manager/aem_manager_rtos.c | 152 + apps/rtos/aem-manager/aem_manager_rtos.h | 17 + avdecc/acmp.c | 1076 ++ avdecc/acmp.h | 66 + avdecc/acmp_ieee.c | 1035 ++ avdecc/acmp_ieee.h | 63 + avdecc/acmp_milan.c | 2388 ++++ avdecc/acmp_milan.h | 92 + avdecc/adp.c | 738 ++ avdecc/adp.h | 77 + avdecc/adp_ieee.c | 439 + avdecc/adp_ieee.h | 80 + avdecc/adp_milan.c | 688 + avdecc/adp_milan.h | 84 + avdecc/aecp.c | 3790 ++++++ avdecc/aecp.h | 75 + avdecc/aem.c | 168 + avdecc/aem.h | 201 + avdecc/avdecc.c | 2250 ++++ avdecc/avdecc.cmake | 22 + avdecc/avdecc.h | 175 + avdecc/avdecc_entry.h | 21 + avdecc/avdecc_ieee.c | 75 + avdecc/avdecc_ieee.h | 21 + avdecc/config.h | 69 + avdecc/entity.c | 35 + avdecc/entity.h | 405 + avdecc/linux/avdecc.cmake | 1 + avdecc/linux/main.c | 157 + avdecc/rtos/avdecc.cmake | 1 + avdecc/rtos/main.c | 230 + avtp/61883_iidc.c | 903 ++ avtp/61883_iidc.h | 41 + avtp/aaf.c | 551 + avtp/aaf.h | 29 + avtp/acf.c | 406 + avtp/acf.h | 31 + avtp/avtp.c | 845 ++ avtp/avtp.cmake | 22 + avtp/avtp.h | 121 + avtp/avtp_control.h | 18 + avtp/avtp_entry.h | 37 + avtp/clock_domain.c | 797 ++ avtp/clock_domain.h | 84 + avtp/clock_grid.c | 251 + avtp/clock_grid.h | 147 + avtp/clock_source.c | 496 + avtp/clock_source.h | 58 + avtp/config.h | 29 + avtp/crf.c | 781 ++ avtp/crf.h | 38 + avtp/cvf.c | 765 ++ avtp/cvf.h | 30 + avtp/linux/avtp.cmake | 1 + avtp/linux/main.c | 311 + avtp/media_clock.c | 1169 ++ avtp/media_clock.h | 152 + avtp/mma.h | 18 + avtp/rtos/avtp.cmake | 1 + avtp/rtos/main.c | 319 + avtp/stream.c | 1161 ++ avtp/stream.h | 353 + common/61883_iidc.c | 25 + common/61883_iidc.h | 21 + common/aaf.c | 33 + common/aaf.h | 19 + common/acmp.h | 44 + common/adp.h | 46 + common/aecp.h | 29 + common/avdecc.c | 398 + common/avdecc.h | 28 + common/avtp.h | 119 + common/clock.c | 30 + common/clock.h | 21 + common/common.cmake | 45 + common/config.h | 31 + common/crf.h | 18 + common/cvf.h | 103 + common/ether.h | 19 + common/filter.c | 261 + common/filter.h | 90 + common/hash.c | 32 + common/hash.h | 19 + common/ipc.h | 438 + common/list.h | 62 + common/log.c | 92 + common/log.h | 99 + common/maap.h | 19 + common/managed_objects.c | 593 + common/managed_objects.h | 204 + common/net.h | 62 + common/net_types.h | 19 + common/os/pi_common.c | 77 + common/os/pi_common.h | 25 + common/os/queue_common.c | 19 + common/os/queue_common.h | 229 + common/ptp.h | 2165 ++++ common/ptp_time_ops.h | 463 + common/random.c | 26 + common/random.h | 17 + common/srp.c | 53 + common/srp.h | 253 + common/stats.c | 109 + common/stats.h | 69 + common/timer.c | 356 + common/timer.h | 71 + common/types.h | 78 + config_armgcc.cmake | 44 + config_freertos_imx8mm_ca53.cmake | 42 + config_freertos_imx8mn_ca53.cmake | 42 + config_freertos_imx8mp_ca53.cmake | 42 + config_freertos_imx93_ca55.cmake | 41 + config_freertos_rt1052.cmake | 38 + config_freertos_rt1176.cmake | 39 + config_freertos_rt1187_cm33.cmake | 44 + config_freertos_rt1187_cm7.cmake | 43 + config_freertos_rt1189_cm33.cmake | 44 + config_freertos_rt1189_cm7.cmake | 43 + config_linux_common.cmake | 24 + config_linux_imx6.cmake | 5 + config_linux_imx6ull.cmake | 5 + config_linux_imx8.cmake | 5 + config_linux_ls1028.cmake | 5 + config_zephyr_imx8mm_ca53.cmake | 46 + config_zephyr_imx8mn_ca53.cmake | 46 + config_zephyr_imx8mp_ca53.cmake | 45 + config_zephyr_imx93_ca55.cmake | 43 + configs/bridge.cmake | 5 + configs/bridge_dsa.cmake | 8 + configs/configs.cmake | 18 + configs/endpoint_avb.cmake | 10 + configs/endpoint_avb_tsn.cmake | 2 + configs/endpoint_avb_tsn_bridge.cmake | 3 + configs/endpoint_avb_tsn_hybrid.cmake | 3 + configs/endpoint_tsn.cmake | 7 + configs/endpoint_tsn_no_gptp.cmake | 5 + configs/hybrid_avb.cmake | 2 + configs/hybrid_tsn.cmake | 7 + doc/CMakeLists.txt | 88 + doc/acmp_connect.msc | 34 + doc/avdecc_control.dot | 84 + doc/clock.md | 14 + doc/config.md | 312 + doc/control.md | 418 + doc/frame_preemption.md | 35 + doc/gptp.md | 295 + doc/help_template/HTML_PageLayout.xml | 181 + doc/help_template/html_custom.css | 1448 +++ doc/help_template/html_footer.html | 17 + doc/help_template/html_header.html | 56 + doc/init.md | 16 + doc/mainpage_linux.md | 19 + doc/mainpage_rtos.md | 20 + doc/platform_linux.md | 118 + doc/scheduled_traffic.md | 24 + doc/socket.md | 70 + doc/stream_formats.md | 324 + doc/streaming.md | 295 + doc/timer.md | 19 + environment-genavb | 165 + extensions.cmake | 163 + gptp/bmca.c | 103 + gptp/bmca.h | 41 + gptp/clock_ms_fsm.c | 351 + gptp/clock_ms_fsm.h | 25 + gptp/clock_sl_fsm.c | 323 + gptp/clock_sl_fsm.h | 23 + gptp/config.h | 160 + gptp/gptp.c | 3145 +++++ gptp/gptp.cmake | 21 + gptp/gptp.h | 497 + gptp/gptp_entry.h | 63 + gptp/gptp_managed_objects.c | 286 + gptp/gptp_managed_objects.h | 138 + gptp/linux/gptp.cmake | 1 + gptp/linux/main.c | 372 + gptp/md_fsm_802_3.c | 1968 +++ gptp/md_fsm_802_3.h | 43 + gptp/port_fsm.c | 1910 +++ gptp/port_fsm.h | 33 + gptp/ptp_time_ops.c | 177 + gptp/rtos/gptp.cmake | 1 + gptp/rtos/main.c | 402 + gptp/site_fsm.c | 556 + gptp/site_fsm.h | 26 + gptp/target_clock_adj.c | 318 + gptp/target_clock_adj.h | 67 + hsr/hsr.c | 1305 ++ hsr/hsr.cmake | 7 + hsr/hsr.h | 16 + hsr/rtos/hsr.cmake | 1 + hsr/rtos/main.c | 195 + include/genavb/61883_iidc.h | 184 + include/genavb/aaf.h | 292 + include/genavb/acf.h | 144 + include/genavb/acmp.h | 93 + include/genavb/adp.h | 98 + include/genavb/aecp.h | 757 ++ include/genavb/aem.h | 619 + include/genavb/aem_entity.h | 557 + include/genavb/aem_helpers.h | 38 + include/genavb/avdecc.h | 706 + include/genavb/avtp.h | 401 + include/genavb/clock.h | 43 + include/genavb/compat.h | 181 + include/genavb/config.h | 84 + include/genavb/control.h | 207 + include/genavb/control_avdecc.h | 191 + include/genavb/control_clock_domain.h | 137 + include/genavb/control_gptp.h | 74 + include/genavb/control_maap.h | 106 + include/genavb/control_srp.h | 262 + include/genavb/crf.h | 107 + include/genavb/cvf.h | 258 + include/genavb/dsa.h | 21 + include/genavb/error.h | 150 + include/genavb/ether.h | 125 + include/genavb/fdb.h | 51 + include/genavb/frer.h | 108 + include/genavb/genavb.h | 120 + include/genavb/helpers.h | 28 + include/genavb/hsr.h | 42 + include/genavb/init.h | 295 + include/genavb/log.h | 65 + include/genavb/media.h | 49 + include/genavb/net_types.h | 162 + include/genavb/port.h | 21 + include/genavb/psfp.h | 138 + include/genavb/ptp.h | 68 + include/genavb/qos.h | 211 + include/genavb/socket.h | 118 + include/genavb/sr_class.h | 125 + include/genavb/srp.h | 62 + include/genavb/stats.h | 29 + include/genavb/stream_identification.h | 128 + include/genavb/streaming.h | 159 + include/genavb/timer.h | 32 + include/genavb/tsn.h | 33 + include/genavb/types.h | 99 + include/genavb/vlan.h | 44 + include/linux/os/config_os.h | 29 + include/linux/os/control.h | 34 + include/linux/os/init.h | 23 + include/linux/os/log.h | 20 + include/linux/os/net_types.h | 39 + include/linux/os/qos.h | 20 + include/linux/os/socket.h | 34 + include/linux/os/stats.h | 20 + include/linux/os/streaming.h | 86 + include/linux/os/timer.h | 20 + include/linux/os/types.h | 27 + include/rtos/os/config_os.h | 21 + include/rtos/os/control.h | 45 + include/rtos/os/dsa.h | 36 + include/rtos/os/fdb.h | 61 + include/rtos/os/frer.h | 106 + include/rtos/os/hsr.h | 26 + include/rtos/os/init.h | 47 + include/rtos/os/log.h | 26 + include/rtos/os/net_types.h | 22 + include/rtos/os/port.h | 38 + include/rtos/os/psfp.h | 150 + include/rtos/os/qos.h | 89 + include/rtos/os/socket.h | 61 + include/rtos/os/stats.h | 48 + include/rtos/os/stream_identification.h | 44 + include/rtos/os/streaming.h | 48 + include/rtos/os/timer.h | 74 + include/rtos/os/types.h | 23 + include/rtos/os/vlan.h | 72 + licenses/BSD-3-Clause | 24 + licenses/CC0-1.0 | 121 + licenses/COPYING | 339 + linux/assert.c | 19 + linux/avb.h | 47 + linux/avb_main.c | 844 ++ linux/cfgfile.c | 841 ++ linux/cfgfile.h | 73 + linux/clock.c | 760 ++ linux/clock.h | 78 + linux/config.h | 21 + linux/configs/apps-avnu.cfg | 18 + linux/configs/apps-listener-alsa-milan.cfg | 17 + linux/configs/apps-listener-alsa.cfg | 17 + linux/configs/apps-listener-audio.cfg | 21 + linux/configs/apps-listener-simple-acf.cfg | 17 + linux/configs/apps-listener-simple.cfg | 14 + .../apps-listener-talker-multi-format.cfg | 21 + linux/configs/apps-listener-talker-multi.cfg | 14 + linux/configs/apps-listener-video-camera.cfg | 45 + linux/configs/apps-listener-video.cfg | 29 + linux/configs/apps-media-listener-talker.cfg | 18 + linux/configs/apps-media-master.cfg | 24 + linux/configs/apps-media-slave.cfg | 24 + linux/configs/apps-talker-simple-aaf.cfg | 14 + linux/configs/apps-talker-simple-acf.cfg | 17 + linux/configs/apps-talker-simple.cfg | 14 + linux/configs/apps-talker-video.cfg | 45 + linux/configs/apps-test-single-board.cfg | 15 + linux/configs/apps-tsn-network-controller.cfg | 8 + linux/configs/apps-tsn-network-iodevice.cfg | 8 + linux/configs/config | 32 + linux/configs/config_avb | 105 + linux/configs/config_avb_bridge | 5 + linux/configs/config_avb_hybrid | 11 + linux/configs/config_tsn | 21 + linux/configs/configs.cmake | 129 + linux/configs/fgptp-br.cfg | 214 + linux/configs/fgptp-br.cfg-1 | 64 + linux/configs/fgptp.cfg | 121 + linux/configs/fgptp.cfg-1 | 50 + linux/configs/genavb-audio-multi-aaf.cfg | 131 + linux/configs/genavb-audio-multi-btb-aaf.cfg | 131 + linux/configs/genavb-audio-multi-btb.cfg | 131 + linux/configs/genavb-audio-multi-format.cfg | 131 + linux/configs/genavb-audio-multi.cfg | 131 + linux/configs/genavb-avnu.cfg | 123 + linux/configs/genavb-listener-acf.cfg | 31 + linux/configs/genavb-listener-btb.cfg | 132 + linux/configs/genavb-listener-milan.cfg | 92 + .../genavb-listener-multi-video-btb.cfg | 132 + .../configs/genavb-listener-talker-milan.cfg | 92 + linux/configs/genavb-listener-video-btb.cfg | 109 + .../configs/genavb-listener-video-camera.cfg | 30 + .../genavb-listener-video-h264-btb.cfg | 133 + linux/configs/genavb-listener.cfg | 132 + linux/configs/genavb-talker-milan.cfg | 92 + linux/configs/genavb-talker-simple-acf.cfg | 31 + linux/configs/genavb-talker-simple.cfg | 132 + linux/configs/genavb-talker-video.cfg | 132 + linux/configs/linux_bridge_system.cfg | 13 + linux/configs/linux_hybrid_avb_system.cfg | 24 + .../linux_imx6_endpoint_avb_system.cfg | 15 + .../linux_imx6ull_endpoint_avb_system.cfg | 15 + .../linux_imx8_endpoint_avb_system.cfg | 15 + linux/configs/linux_imx8_endpoint_tsn_srp.cfg | 19 + .../linux_imx8_endpoint_tsn_system.cfg | 17 + linux/configs/srp-br.cfg | 23 + linux/configs/srp.cfg | 23 + linux/ebpf/ebpf.cmake | 61 + linux/ebpf/ebpf.h | 85 + linux/ebpf/genavb_xdp_main.c | 61 + linux/epoll.c | 45 + linux/epoll.h | 23 + linux/extensions.cmake | 97 + linux/fdb.c | 128 + linux/fdb.h | 22 + linux/fdb_std.c | 123 + linux/firmware/genavb-xdp.bin | Bin 0 -> 9616 bytes linux/fqtss.c | 68 + linux/fqtss.h | 25 + linux/fqtss_avb.c | 107 + linux/fqtss_std.c | 162 + linux/init.c | 119 + linux/init.h | 21 + linux/ipc.c | 548 + linux/ipc.h | 19 + linux/linux.cmake | 126 + linux/log.c | 83 + linux/log.h | 31 + linux/media.c | 238 + linux/media_clock.c | 323 + linux/modules/Makefile | 38 + linux/modules/Makefile.modules | 41 + linux/modules/avb/avbdrv.c | 565 + linux/modules/avb/avbdrv.h | 118 + linux/modules/avb/avtp.c | 541 + linux/modules/avb/avtp.h | 79 + linux/modules/avb/debugfs.c | 846 ++ linux/modules/avb/debugfs.h | 28 + linux/modules/avb/epit.c | 279 + linux/modules/avb/epit.h | 19 + linux/modules/avb/gpt.c | 703 + linux/modules/avb/gpt.h | 19 + linux/modules/avb/hw_timer.c | 272 + linux/modules/avb/hw_timer.h | 77 + linux/modules/avb/imx-pll.c | 93 + linux/modules/avb/imx-pll.h | 31 + linux/modules/avb/media.c | 1729 +++ linux/modules/avb/media.h | 161 + linux/modules/avb/media_clock.c | 339 + linux/modules/avb/media_clock.h | 203 + linux/modules/avb/media_clock_drv.c | 631 + linux/modules/avb/media_clock_drv.h | 88 + linux/modules/avb/media_clock_gen_ptp.c | 225 + linux/modules/avb/media_clock_gen_ptp.h | 45 + linux/modules/avb/media_clock_rec_pll.c | 925 ++ linux/modules/avb/media_clock_rec_pll.h | 170 + linux/modules/avb/mrp.c | 97 + linux/modules/avb/mrp.h | 21 + linux/modules/avb/mtimer.c | 174 + linux/modules/avb/mtimer.h | 60 + linux/modules/avb/mtimer_drv.c | 174 + linux/modules/avb/mtimer_drv.h | 33 + linux/modules/avb/net_logical_port.c | 65 + linux/modules/avb/net_logical_port.h | 40 + linux/modules/avb/net_port.c | 418 + linux/modules/avb/net_port.h | 85 + linux/modules/avb/net_rx.c | 138 + linux/modules/avb/net_rx.h | 43 + linux/modules/avb/net_socket.c | 922 ++ linux/modules/avb/net_socket.h | 180 + linux/modules/avb/net_tx.c | 1550 +++ linux/modules/avb/net_tx.h | 278 + linux/modules/avb/netdrv.c | 333 + linux/modules/avb/netdrv.h | 58 + linux/modules/avb/pi.c | 10 + linux/modules/avb/pi.h | 15 + linux/modules/avb/pool_dma.c | 223 + linux/modules/avb/pool_dma.h | 81 + linux/modules/avb/ptp.c | 206 + linux/modules/avb/ptp.h | 26 + linux/modules/avb/rational.c | 119 + linux/modules/avb/rational.h | 75 + linux/modules/avb/stats.c | 92 + linux/modules/avb/stats.h | 55 + linux/modules/avb/tpm.c | 665 + linux/modules/avb/tpm.h | 24 + linux/modules/common/ipc.c | 999 ++ linux/modules/common/ipc.h | 120 + linux/modules/common/pool.c | 152 + linux/modules/common/pool.h | 219 + linux/modules/common/port_config.h | 77 + linux/modules/common/queue.c | 10 + linux/modules/common/queue.h | 26 + linux/modules/std/ipc_module.c | 58 + linux/net.c | 621 + linux/net.h | 32 + linux/net_avb.c | 669 + linux/net_ipc.c | 439 + linux/net_logical_port.c | 125 + linux/net_logical_port.h | 24 + linux/net_std.c | 780 ++ linux/net_std_socket_filters.c | 126 + linux/net_std_socket_filters.h | 21 + linux/net_xdp.c | 1091 ++ linux/os_config.c | 148 + linux/os_config.h | 64 + linux/osal/assert.h | 16 + linux/osal/clock.h | 17 + linux/osal/config.h | 28 + linux/osal/epoll.h | 32 + linux/osal/fdb.h | 16 + linux/osal/ipc.h | 36 + linux/osal/log.h | 18 + linux/osal/media.h | 28 + linux/osal/media_clock.h | 39 + linux/osal/net.h | 98 + linux/osal/stdlib.h | 17 + linux/osal/sys_types.h | 39 + linux/osal/timer.h | 34 + linux/pool.c | 223 + linux/pool.h | 230 + linux/rtnetlink.c | 108 + linux/rtnetlink.h | 34 + linux/scripts/avb-bridge.sh | 168 + linux/scripts/avb.sh | 922 ++ linux/scripts/init.sh | 23 + linux/scripts/scripts.cmake | 36 + linux/scripts/tsn-app-setup.sh | 240 + linux/scripts/tsn.sh | 681 + linux/shmem.c | 119 + linux/shmem.h | 77 + linux/stdlib.c | 63 + linux/string.c | 52 + linux/timer.c | 211 + linux/timer_media.c | 82 + linux/timer_media.h | 38 + linux/tsn.h | 47 + linux/tsn_main.c | 1100 ++ linux/vlan.c | 47 + maap/config.h | 23 + maap/linux/maap.cmake | 1 + maap/linux/main.c | 155 + maap/maap.c | 1614 +++ maap/maap.cmake | 12 + maap/maap.h | 297 + maap/maap_entry.h | 19 + maap/rtos/maap.cmake | 1 + maap/rtos/main.c | 193 + management/config.h | 22 + management/linux/main.c | 142 + management/linux/management.cmake | 1 + management/mac_service.c | 259 + management/mac_service.h | 51 + management/management.c | 101 + management/management.cmake | 13 + management/management.h | 35 + management/management_entry.h | 21 + management/rtos/main.c | 160 + management/rtos/management.cmake | 1 + os/assert.h | 28 + os/clock.h | 117 + os/config.h | 19 + os/dsa.h | 21 + os/fdb.h | 25 + os/fqtss.h | 51 + os/frer.h | 34 + os/ipc.h | 95 + os/log.h | 37 + os/media.h | 45 + os/media_clock.h | 41 + os/net.h | 308 + os/psfp.h | 42 + os/qos.h | 65 + os/stdlib.h | 57 + os/stream_identification.h | 23 + os/string.h | 61 + os/sys_types.h | 19 + os/timer.h | 66 + os/vlan.h | 28 + public/aem_helpers.c | 170 + public/config.h | 20 + public/helpers.c | 153 + public/linux/aem_helpers.c | 116 + public/linux/public.cmake | 1 + public/public.cmake | 23 + public/qos.c | 68 + public/sr_class.c | 270 + rtos/assert.c | 17 + rtos/avb_queue.c | 15 + rtos/avb_queue.h | 25 + rtos/avtp.c | 430 + rtos/avtp.h | 77 + rtos/clock.c | 772 ++ rtos/clock.h | 85 + rtos/config.h | 122 + rtos/debug_print.c | 201 + rtos/debug_print.h | 44 + rtos/extensions.cmake | 52 + rtos/fdb.c | 93 + rtos/fp.c | 295 + rtos/fp.h | 80 + rtos/fqtss.c | 113 + rtos/fqtss.h | 19 + rtos/freertos/rtos_abstraction_layer.cmake | 19 + rtos/freertos/rtos_abstraction_layer.h | 27 + rtos/freertos/rtos_assert.h | 16 + rtos/freertos/rtos_atomic.c | 39 + rtos/freertos/rtos_atomic.h | 109 + rtos/freertos/rtos_event_group.h | 68 + rtos/freertos/rtos_heap.h | 21 + rtos/freertos/rtos_mqueue.c | 55 + rtos/freertos/rtos_mqueue.h | 197 + rtos/freertos/rtos_mutex.h | 74 + rtos/freertos/rtos_sched.c | 10 + rtos/freertos/rtos_sched.h | 55 + rtos/freertos/rtos_thread.c | 29 + rtos/freertos/rtos_thread.h | 24 + rtos/freertos/rtos_time.h | 53 + rtos/freertos/rtos_timer.c | 90 + rtos/freertos/rtos_timer.h | 97 + rtos/frer.c | 278 + rtos/gpt.c | 345 + rtos/gpt.h | 18 + rtos/gpt_rec.c | 208 + rtos/gpt_rec.h | 18 + rtos/gptp_dev.c | 291 + rtos/gptp_dev.h | 49 + rtos/hr_timer.c | 747 ++ rtos/hr_timer.h | 33 + rtos/hsr.c | 135 + rtos/hsr.h | 27 + rtos/hw_clock.c | 270 + rtos/hw_clock.h | 102 + rtos/hw_timer.c | 157 + rtos/hw_timer.h | 89 + rtos/imx-pll.c | 124 + rtos/imx-pll.h | 51 + rtos/ipc.c | 923 ++ rtos/ipc.h | 19 + rtos/l2.c | 205 + rtos/l2.h | 43 + rtos/log.c | 39 + rtos/media.c | 132 + rtos/media_clock.c | 824 ++ rtos/media_clock.h | 229 + rtos/media_clock_drv.h | 26 + rtos/media_clock_gen_ptp.c | 218 + rtos/media_clock_gen_ptp.h | 49 + rtos/media_clock_rec_pll.c | 593 + rtos/media_clock_rec_pll.h | 139 + rtos/media_queue.c | 1250 ++ rtos/media_queue.h | 114 + rtos/mrp.c | 120 + rtos/mrp.h | 27 + rtos/msgintr.c | 170 + rtos/msgintr.h | 57 + rtos/mtimer.c | 154 + rtos/mtimer.h | 77 + rtos/net.c | 655 + rtos/net.h | 21 + rtos/net_bridge.c | 275 + rtos/net_bridge.h | 125 + rtos/net_logical_port.c | 171 + rtos/net_logical_port.h | 49 + rtos/net_mdio.c | 154 + rtos/net_mdio.h | 30 + rtos/net_phy.c | 424 + rtos/net_phy.h | 27 + rtos/net_port.c | 934 ++ rtos/net_port.h | 248 + rtos/net_port_dsa.c | 70 + rtos/net_port_dsa.h | 33 + rtos/net_port_enet.c | 581 + rtos/net_port_enet.h | 20 + rtos/net_port_enet_mdio.c | 57 + rtos/net_port_enet_mdio.h | 19 + rtos/net_port_enet_qos.c | 1483 +++ rtos/net_port_enet_qos.h | 20 + rtos/net_port_enet_qos_mdio.c | 57 + rtos/net_port_enet_qos_mdio.h | 19 + rtos/net_port_enet_qos_stats.c | 149 + rtos/net_port_enet_stats.c | 116 + rtos/net_port_enetc_ep.c | 798 ++ rtos/net_port_enetc_ep.h | 24 + rtos/net_port_netc_1588.c | 678 + rtos/net_port_netc_1588.h | 40 + rtos/net_port_netc_mdio.c | 70 + rtos/net_port_netc_mdio.h | 18 + rtos/net_port_netc_psfp.c | 983 ++ rtos/net_port_netc_psfp.h | 21 + rtos/net_port_netc_stats.c | 568 + rtos/net_port_netc_stream_identification.c | 1261 ++ rtos/net_port_netc_stream_identification.h | 23 + rtos/net_port_netc_sw.c | 1966 +++ rtos/net_port_netc_sw.h | 25 + rtos/net_port_netc_sw_drv.h | 157 + rtos/net_port_netc_sw_dsa.c | 666 + rtos/net_port_netc_sw_dsa.h | 12 + rtos/net_port_netc_sw_frer.c | 841 ++ rtos/net_port_netc_sw_frer.h | 21 + rtos/net_port_stats.c | 56 + rtos/net_port_stats.h | 55 + rtos/net_rx.c | 133 + rtos/net_rx.h | 88 + rtos/net_socket.c | 854 ++ rtos/net_socket.h | 143 + rtos/net_task.c | 362 + rtos/net_task.h | 19 + rtos/net_tx.c | 1362 ++ rtos/net_tx.h | 263 + rtos/osal/assert.h | 16 + rtos/osal/clock.h | 16 + rtos/osal/config.h | 42 + rtos/osal/configs/config | 1 + rtos/osal/ipc.h | 31 + rtos/osal/log.h | 17 + rtos/osal/media.h | 24 + rtos/osal/media_clock.h | 37 + rtos/osal/net.h | 41 + rtos/osal/stdlib.h | 16 + rtos/osal/sys_types.h | 77 + rtos/osal/timer.h | 31 + rtos/pi.c | 13 + rtos/pi.h | 19 + rtos/prng_32.c | 55 + rtos/prng_32.h | 15 + rtos/psfp.c | 284 + rtos/ptp.c | 170 + rtos/ptp.h | 31 + rtos/qos.c | 271 + rtos/rational.c | 122 + rtos/rational.h | 78 + rtos/rtos.cmake | 92 + rtos/slist.h | 81 + rtos/stats_task.c | 47 + rtos/stats_task.h | 32 + rtos/stdlib.c | 72 + rtos/stream_identification.c | 84 + rtos/string.c | 51 + rtos/timer.c | 344 + rtos/tpm.c | 379 + rtos/tpm.h | 18 + rtos/tpm_rec.c | 207 + rtos/tpm_rec.h | 18 + rtos/vlan.c | 179 + srp/config.h | 52 + srp/linux/main.c | 153 + srp/linux/srp.cmake | 1 + srp/mmrp.c | 71 + srp/mmrp.h | 39 + srp/mrp.c | 1756 +++ srp/mrp.h | 383 + srp/msrp.c | 2380 ++++ srp/msrp.h | 193 + srp/msrp_map.c | 747 ++ srp/msrp_map.h | 122 + srp/mvrp.c | 780 ++ srp/mvrp.h | 99 + srp/mvrp_map.c | 163 + srp/mvrp_map.h | 71 + srp/rtos/main.c | 200 + srp/rtos/srp.cmake | 1 + srp/srp.c | 362 + srp/srp.cmake | 19 + srp/srp.h | 69 + srp/srp_entry.h | 21 + srp/srp_managed_objects.c | 427 + srp/srp_managed_objects.h | 110 + 930 files changed, 200037 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.linux rename README => CMakeLists.rtos (100%) mode change 100755 => 100644 create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 SCR_genavb-release.txt create mode 100644 api/api.cmake create mode 100644 api/clock.c create mode 100644 api/clock.h create mode 100644 api/config.h create mode 100644 api/control.c create mode 100644 api/control.h create mode 100644 api/error.c create mode 100644 api/init.h create mode 100644 api/linux/api.cmake create mode 100644 api/linux/api_os/control.h create mode 100644 api/linux/api_os/init.h create mode 100644 api/linux/api_os/streaming.h create mode 100644 api/linux/apis.map create mode 100644 api/linux/control.c create mode 100644 api/linux/init.c create mode 100644 api/linux/socket.c create mode 100644 api/linux/streaming.c create mode 100644 api/rtos/api.cmake create mode 100644 api/rtos/api_os/control.h create mode 100644 api/rtos/api_os/init.h create mode 100644 api/rtos/api_os/streaming.h create mode 100644 api/rtos/control.c create mode 100644 api/rtos/dsa.c create mode 100644 api/rtos/fdb.c create mode 100644 api/rtos/frer.c create mode 100644 api/rtos/generic.c create mode 100644 api/rtos/hsr.c create mode 100644 api/rtos/init.c create mode 100644 api/rtos/psfp.c create mode 100644 api/rtos/qos.c create mode 100644 api/rtos/socket.c create mode 100644 api/rtos/stream_identification.c create mode 100644 api/rtos/streaming.c create mode 100644 api/rtos/timer.c create mode 100644 api/rtos/vlan.c create mode 100644 api/socket.c create mode 100644 api/socket.h create mode 100644 api/streaming.c create mode 100644 api/streaming.h create mode 100644 api/timer.h create mode 100644 apps/common/aem-manager/aem-audio-entities.cmake create mode 100644 apps/common/aem-manager/aem-audio-video-entities.cmake create mode 100644 apps/common/aem-manager/aem-avnu-vertification-entities.cmake create mode 100644 apps/common/aem-manager/aem-controller-entities.cmake create mode 100644 apps/common/aem-manager/aem-manager-helpers.cmake create mode 100644 apps/common/aem-manager/aem-video-entities.cmake create mode 100644 apps/common/aem-manager/aem_manager_helpers.c create mode 100644 apps/common/aem-manager/aem_manager_helpers.h create mode 100644 apps/common/aem-manager/avdecc_controller.c create mode 100644 apps/common/aem-manager/avdecc_controller.h create mode 100644 apps/common/aem-manager/avnu_certification.c create mode 100644 apps/common/aem-manager/avnu_certification.h create mode 100644 apps/common/aem-manager/listener_audio_single.c create mode 100644 apps/common/aem-manager/listener_audio_single.h create mode 100644 apps/common/aem-manager/listener_audio_single_milan.c create mode 100644 apps/common/aem-manager/listener_audio_single_milan.h create mode 100644 apps/common/aem-manager/listener_talker_audio_single.c create mode 100644 apps/common/aem-manager/listener_talker_audio_single.h create mode 100644 apps/common/aem-manager/listener_talker_audio_single_milan.c create mode 100644 apps/common/aem-manager/listener_talker_audio_single_milan.h create mode 100644 apps/common/aem-manager/listener_video_multi.c create mode 100644 apps/common/aem-manager/listener_video_multi.h create mode 100644 apps/common/aem-manager/listener_video_single.c create mode 100644 apps/common/aem-manager/listener_video_single.h create mode 100644 apps/common/aem-manager/talker_audio_single.c create mode 100644 apps/common/aem-manager/talker_audio_single.h create mode 100644 apps/common/aem-manager/talker_audio_single_milan.c create mode 100644 apps/common/aem-manager/talker_audio_single_milan.h create mode 100644 apps/common/aem-manager/talker_audio_video.c create mode 100644 apps/common/aem-manager/talker_audio_video.h create mode 100644 apps/common/aem-manager/talker_listener_audio_default.c create mode 100644 apps/common/aem-manager/talker_listener_audio_default.h create mode 100644 apps/common/aem-manager/talker_listener_audio_multi.c create mode 100644 apps/common/aem-manager/talker_listener_audio_multi.h create mode 100644 apps/common/aem-manager/talker_listener_audio_multi_aaf.c create mode 100644 apps/common/aem-manager/talker_listener_audio_multi_aaf.h create mode 100644 apps/common/aem-manager/talker_listener_audio_multi_format.c create mode 100644 apps/common/aem-manager/talker_listener_audio_multi_format.h create mode 100644 apps/common/aem-manager/talker_video_multi.c create mode 100644 apps/common/aem-manager/talker_video_multi.h create mode 100644 apps/common/aem-manager/talker_video_single.c create mode 100644 apps/common/aem-manager/talker_video_single.h create mode 100644 apps/linux/CMakeLists.txt create mode 100644 apps/linux/aem-manager/CMakeLists.txt create mode 100644 apps/linux/aem-manager/main.c create mode 100644 apps/linux/alsa-audio-app/CMakeLists.txt create mode 100644 apps/linux/alsa-audio-app/main.c create mode 100644 apps/linux/common/acmp.c create mode 100644 apps/linux/common/acmp.h create mode 100644 apps/linux/common/adp.c create mode 100644 apps/linux/common/adp.h create mode 100644 apps/linux/common/aecp.c create mode 100644 apps/linux/common/aecp.h create mode 100644 apps/linux/common/alsa.c create mode 100644 apps/linux/common/alsa.h create mode 100644 apps/linux/common/alsa2.c create mode 100644 apps/linux/common/alsa2.h create mode 100644 apps/linux/common/alsa_config.h create mode 100644 apps/linux/common/alsa_stream.c create mode 100644 apps/linux/common/alsa_stream.h create mode 100644 apps/linux/common/audio_mappings.c create mode 100644 apps/linux/common/audio_mappings.h create mode 100644 apps/linux/common/avb_stream.c create mode 100644 apps/linux/common/avb_stream.h create mode 100644 apps/linux/common/avb_stream_config.h create mode 100644 apps/linux/common/avdecc.c create mode 100644 apps/linux/common/avdecc.h create mode 100644 apps/linux/common/clock.c create mode 100644 apps/linux/common/clock.h create mode 100644 apps/linux/common/clock_domain.c create mode 100644 apps/linux/common/clock_domain.h create mode 100644 apps/linux/common/common.c create mode 100644 apps/linux/common/common.h create mode 100644 apps/linux/common/crf_stream.c create mode 100644 apps/linux/common/crf_stream.h create mode 100644 apps/linux/common/file_buffer.c create mode 100644 apps/linux/common/file_buffer.h create mode 100644 apps/linux/common/gst_pipeline_definitions.c create mode 100644 apps/linux/common/gst_pipeline_definitions.h create mode 100644 apps/linux/common/gstreamer.c create mode 100644 apps/linux/common/gstreamer.h create mode 100644 apps/linux/common/gstreamer.inc create mode 100644 apps/linux/common/gstreamer_custom_rt_pool.c create mode 100644 apps/linux/common/gstreamer_custom_rt_pool.h create mode 100644 apps/linux/common/gstreamer_multisink.c create mode 100644 apps/linux/common/gstreamer_multisink.h create mode 100644 apps/linux/common/gstreamer_single.c create mode 100644 apps/linux/common/gstreamer_single.h create mode 100644 apps/linux/common/helpers.h create mode 100644 apps/linux/common/log.c create mode 100644 apps/linux/common/log.h create mode 100644 apps/linux/common/msrp.c create mode 100644 apps/linux/common/msrp.h create mode 100644 apps/linux/common/open62541.inc create mode 100644 apps/linux/common/stats.c create mode 100644 apps/linux/common/stats.h create mode 100644 apps/linux/common/stream_stats.c create mode 100644 apps/linux/common/stream_stats.h create mode 100644 apps/linux/common/thread.c create mode 100644 apps/linux/common/thread.h create mode 100644 apps/linux/common/thread_config.h create mode 100644 apps/linux/common/time.c create mode 100644 apps/linux/common/time.h create mode 100644 apps/linux/common/timer.c create mode 100644 apps/linux/common/timer.h create mode 100644 apps/linux/common/ts_parser.c create mode 100644 apps/linux/common/ts_parser.h create mode 100644 apps/linux/genavb-controller-app/CMakeLists.txt create mode 100644 apps/linux/genavb-controller-app/main.c create mode 100644 apps/linux/genavb-controls-app/CMakeLists.txt create mode 100644 apps/linux/genavb-controls-app/main.c create mode 100644 apps/linux/genavb-media-app/CMakeLists.txt create mode 100644 apps/linux/genavb-media-app/alsa_config.c create mode 100644 apps/linux/genavb-media-app/avb_stream_config.c create mode 100644 apps/linux/genavb-media-app/gstreamer_stream.c create mode 100644 apps/linux/genavb-media-app/gstreamer_stream.h create mode 100644 apps/linux/genavb-media-app/h264_camera.c create mode 100644 apps/linux/genavb-media-app/h264_camera.h create mode 100644 apps/linux/genavb-media-app/main.c create mode 100644 apps/linux/genavb-media-app/multi_frame_sync.c create mode 100644 apps/linux/genavb-media-app/multi_frame_sync.h create mode 100644 apps/linux/genavb-media-app/salsa_camera.c create mode 100644 apps/linux/genavb-media-app/salsa_camera.h create mode 100644 apps/linux/genavb-media-app/thread_config.c create mode 100644 apps/linux/genavb-multi-stream-app/CMakeLists.txt create mode 100644 apps/linux/genavb-multi-stream-app/main.c create mode 100644 apps/linux/genavb-video-player-app/CMakeLists.txt create mode 100644 apps/linux/genavb-video-player-app/gst_pipelines.c create mode 100644 apps/linux/genavb-video-player-app/gst_pipelines.h create mode 100644 apps/linux/genavb-video-player-app/gstreamer.c create mode 100644 apps/linux/genavb-video-player-app/gstreamer.h create mode 100644 apps/linux/genavb-video-player-app/gstreamer_single.c create mode 100644 apps/linux/genavb-video-player-app/gstreamer_single.h create mode 100644 apps/linux/genavb-video-player-app/main.c create mode 100755 apps/linux/genavb-video-player-app/salsa-camera.sh create mode 100644 apps/linux/genavb-video-server-app/CMakeLists.txt create mode 100644 apps/linux/genavb-video-server-app/main.c create mode 100644 apps/linux/genavb-video-server-app/pipeline_sm.dot create mode 100644 apps/linux/genavb-video-server-app/readme.txt create mode 100644 apps/linux/maap-ctrl-app/CMakeLists.txt create mode 100644 apps/linux/maap-ctrl-app/main.c create mode 100644 apps/linux/management-app/CMakeLists.txt create mode 100644 apps/linux/management-app/common.c create mode 100644 apps/linux/management-app/common.h create mode 100644 apps/linux/management-app/gptp_main.c create mode 100644 apps/linux/management-app/main.c create mode 100644 apps/linux/management-app/srp_main.c create mode 100644 apps/linux/msrp-ctrl-app/CMakeLists.txt create mode 100644 apps/linux/msrp-ctrl-app/main.c create mode 100644 apps/linux/salsacamctrl/CMakeLists.txt create mode 100644 apps/linux/salsacamctrl/main.c create mode 100755 apps/linux/salsacamctrl/salsacam-cmd.sh create mode 100644 apps/linux/salsacamctrl/salsacam-configs.inc create mode 100755 apps/linux/salsacamctrl/salsacam-setup.sh create mode 100644 apps/linux/salsacamctrl/udpcc2.c create mode 100644 apps/linux/salsacamctrl/udpcc2.h create mode 100644 apps/linux/simple-acf-app/CMakeLists.txt create mode 100644 apps/linux/simple-acf-app/main.c create mode 100644 apps/linux/simple-audio-app/CMakeLists.txt create mode 100644 apps/linux/simple-audio-app/main.c create mode 100644 apps/linux/tsn-app/CMakeLists.txt create mode 100644 apps/linux/tsn-app/cyclic_task.c create mode 100644 apps/linux/tsn-app/cyclic_task.h create mode 100644 apps/linux/tsn-app/main.c create mode 100644 apps/linux/tsn-app/network_only.c create mode 100644 apps/linux/tsn-app/network_only.h create mode 100755 apps/linux/tsn-app/opcua/model/README.md create mode 100644 apps/linux/tsn-app/opcua/model/TsnApp.NodeSet2.xml create mode 100644 apps/linux/tsn-app/opcua/model/tsn_app_model.c create mode 100644 apps/linux/tsn-app/opcua/model/tsn_app_model.h create mode 100644 apps/linux/tsn-app/opcua/model/tsn_app_model_design.xml create mode 100644 apps/linux/tsn-app/opcua/model/tsn_app_ua_nodeid_header.h create mode 100644 apps/linux/tsn-app/opcua/opcua_server.c create mode 100644 apps/linux/tsn-app/opcua/opcua_server.h create mode 100644 apps/linux/tsn-app/serial_controller.c create mode 100644 apps/linux/tsn-app/serial_controller.h create mode 100644 apps/linux/tsn-app/thread_config.c create mode 100644 apps/linux/tsn-app/tsn_task.c create mode 100644 apps/linux/tsn-app/tsn_task.h create mode 100644 apps/linux/tsn-app/tsn_tasks_config.c create mode 100644 apps/linux/tsn-app/tsn_tasks_config.h create mode 100644 apps/linux/tsn-app/tsn_timer.c create mode 100644 apps/linux/tsn-app/tsn_timer.h create mode 100644 apps/rtos/aem-manager/aem-manager-rtos.cmake create mode 100644 apps/rtos/aem-manager/aem_manager_rtos.c create mode 100644 apps/rtos/aem-manager/aem_manager_rtos.h create mode 100644 avdecc/acmp.c create mode 100644 avdecc/acmp.h create mode 100644 avdecc/acmp_ieee.c create mode 100644 avdecc/acmp_ieee.h create mode 100644 avdecc/acmp_milan.c create mode 100644 avdecc/acmp_milan.h create mode 100644 avdecc/adp.c create mode 100644 avdecc/adp.h create mode 100644 avdecc/adp_ieee.c create mode 100644 avdecc/adp_ieee.h create mode 100644 avdecc/adp_milan.c create mode 100644 avdecc/adp_milan.h create mode 100644 avdecc/aecp.c create mode 100644 avdecc/aecp.h create mode 100644 avdecc/aem.c create mode 100644 avdecc/aem.h create mode 100644 avdecc/avdecc.c create mode 100644 avdecc/avdecc.cmake create mode 100644 avdecc/avdecc.h create mode 100644 avdecc/avdecc_entry.h create mode 100644 avdecc/avdecc_ieee.c create mode 100644 avdecc/avdecc_ieee.h create mode 100644 avdecc/config.h create mode 100644 avdecc/entity.c create mode 100644 avdecc/entity.h create mode 100644 avdecc/linux/avdecc.cmake create mode 100644 avdecc/linux/main.c create mode 100644 avdecc/rtos/avdecc.cmake create mode 100644 avdecc/rtos/main.c create mode 100644 avtp/61883_iidc.c create mode 100644 avtp/61883_iidc.h create mode 100644 avtp/aaf.c create mode 100644 avtp/aaf.h create mode 100644 avtp/acf.c create mode 100644 avtp/acf.h create mode 100644 avtp/avtp.c create mode 100644 avtp/avtp.cmake create mode 100644 avtp/avtp.h create mode 100644 avtp/avtp_control.h create mode 100644 avtp/avtp_entry.h create mode 100644 avtp/clock_domain.c create mode 100644 avtp/clock_domain.h create mode 100644 avtp/clock_grid.c create mode 100644 avtp/clock_grid.h create mode 100644 avtp/clock_source.c create mode 100644 avtp/clock_source.h create mode 100644 avtp/config.h create mode 100644 avtp/crf.c create mode 100644 avtp/crf.h create mode 100644 avtp/cvf.c create mode 100644 avtp/cvf.h create mode 100644 avtp/linux/avtp.cmake create mode 100644 avtp/linux/main.c create mode 100644 avtp/media_clock.c create mode 100644 avtp/media_clock.h create mode 100644 avtp/mma.h create mode 100644 avtp/rtos/avtp.cmake create mode 100644 avtp/rtos/main.c create mode 100644 avtp/stream.c create mode 100644 avtp/stream.h create mode 100644 common/61883_iidc.c create mode 100644 common/61883_iidc.h create mode 100644 common/aaf.c create mode 100644 common/aaf.h create mode 100644 common/acmp.h create mode 100644 common/adp.h create mode 100644 common/aecp.h create mode 100644 common/avdecc.c create mode 100644 common/avdecc.h create mode 100644 common/avtp.h create mode 100644 common/clock.c create mode 100644 common/clock.h create mode 100644 common/common.cmake create mode 100644 common/config.h create mode 100644 common/crf.h create mode 100644 common/cvf.h create mode 100644 common/ether.h create mode 100644 common/filter.c create mode 100644 common/filter.h create mode 100644 common/hash.c create mode 100644 common/hash.h create mode 100644 common/ipc.h create mode 100644 common/list.h create mode 100644 common/log.c create mode 100644 common/log.h create mode 100644 common/maap.h create mode 100644 common/managed_objects.c create mode 100644 common/managed_objects.h create mode 100644 common/net.h create mode 100644 common/net_types.h create mode 100644 common/os/pi_common.c create mode 100644 common/os/pi_common.h create mode 100644 common/os/queue_common.c create mode 100644 common/os/queue_common.h create mode 100644 common/ptp.h create mode 100644 common/ptp_time_ops.h create mode 100644 common/random.c create mode 100644 common/random.h create mode 100644 common/srp.c create mode 100644 common/srp.h create mode 100644 common/stats.c create mode 100644 common/stats.h create mode 100644 common/timer.c create mode 100644 common/timer.h create mode 100644 common/types.h create mode 100644 config_armgcc.cmake create mode 100644 config_freertos_imx8mm_ca53.cmake create mode 100644 config_freertos_imx8mn_ca53.cmake create mode 100644 config_freertos_imx8mp_ca53.cmake create mode 100644 config_freertos_imx93_ca55.cmake create mode 100644 config_freertos_rt1052.cmake create mode 100644 config_freertos_rt1176.cmake create mode 100644 config_freertos_rt1187_cm33.cmake create mode 100644 config_freertos_rt1187_cm7.cmake create mode 100644 config_freertos_rt1189_cm33.cmake create mode 100644 config_freertos_rt1189_cm7.cmake create mode 100644 config_linux_common.cmake create mode 100644 config_linux_imx6.cmake create mode 100644 config_linux_imx6ull.cmake create mode 100644 config_linux_imx8.cmake create mode 100644 config_linux_ls1028.cmake create mode 100644 config_zephyr_imx8mm_ca53.cmake create mode 100644 config_zephyr_imx8mn_ca53.cmake create mode 100644 config_zephyr_imx8mp_ca53.cmake create mode 100644 config_zephyr_imx93_ca55.cmake create mode 100644 configs/bridge.cmake create mode 100644 configs/bridge_dsa.cmake create mode 100644 configs/configs.cmake create mode 100644 configs/endpoint_avb.cmake create mode 100644 configs/endpoint_avb_tsn.cmake create mode 100644 configs/endpoint_avb_tsn_bridge.cmake create mode 100644 configs/endpoint_avb_tsn_hybrid.cmake create mode 100644 configs/endpoint_tsn.cmake create mode 100644 configs/endpoint_tsn_no_gptp.cmake create mode 100644 configs/hybrid_avb.cmake create mode 100644 configs/hybrid_tsn.cmake create mode 100644 doc/CMakeLists.txt create mode 100644 doc/acmp_connect.msc create mode 100644 doc/avdecc_control.dot create mode 100644 doc/clock.md create mode 100644 doc/config.md create mode 100644 doc/control.md create mode 100644 doc/frame_preemption.md create mode 100644 doc/gptp.md create mode 100644 doc/help_template/HTML_PageLayout.xml create mode 100644 doc/help_template/html_custom.css create mode 100644 doc/help_template/html_footer.html create mode 100644 doc/help_template/html_header.html create mode 100644 doc/init.md create mode 100644 doc/mainpage_linux.md create mode 100644 doc/mainpage_rtos.md create mode 100644 doc/platform_linux.md create mode 100644 doc/scheduled_traffic.md create mode 100644 doc/socket.md create mode 100644 doc/stream_formats.md create mode 100644 doc/streaming.md create mode 100644 doc/timer.md create mode 100644 environment-genavb create mode 100644 extensions.cmake create mode 100644 gptp/bmca.c create mode 100644 gptp/bmca.h create mode 100644 gptp/clock_ms_fsm.c create mode 100644 gptp/clock_ms_fsm.h create mode 100644 gptp/clock_sl_fsm.c create mode 100644 gptp/clock_sl_fsm.h create mode 100644 gptp/config.h create mode 100644 gptp/gptp.c create mode 100644 gptp/gptp.cmake create mode 100644 gptp/gptp.h create mode 100644 gptp/gptp_entry.h create mode 100644 gptp/gptp_managed_objects.c create mode 100644 gptp/gptp_managed_objects.h create mode 100644 gptp/linux/gptp.cmake create mode 100644 gptp/linux/main.c create mode 100644 gptp/md_fsm_802_3.c create mode 100644 gptp/md_fsm_802_3.h create mode 100644 gptp/port_fsm.c create mode 100644 gptp/port_fsm.h create mode 100644 gptp/ptp_time_ops.c create mode 100644 gptp/rtos/gptp.cmake create mode 100644 gptp/rtos/main.c create mode 100644 gptp/site_fsm.c create mode 100644 gptp/site_fsm.h create mode 100644 gptp/target_clock_adj.c create mode 100644 gptp/target_clock_adj.h create mode 100644 hsr/hsr.c create mode 100644 hsr/hsr.cmake create mode 100644 hsr/hsr.h create mode 100644 hsr/rtos/hsr.cmake create mode 100644 hsr/rtos/main.c create mode 100644 include/genavb/61883_iidc.h create mode 100644 include/genavb/aaf.h create mode 100644 include/genavb/acf.h create mode 100644 include/genavb/acmp.h create mode 100644 include/genavb/adp.h create mode 100644 include/genavb/aecp.h create mode 100644 include/genavb/aem.h create mode 100644 include/genavb/aem_entity.h create mode 100644 include/genavb/aem_helpers.h create mode 100644 include/genavb/avdecc.h create mode 100644 include/genavb/avtp.h create mode 100644 include/genavb/clock.h create mode 100644 include/genavb/compat.h create mode 100644 include/genavb/config.h create mode 100644 include/genavb/control.h create mode 100644 include/genavb/control_avdecc.h create mode 100644 include/genavb/control_clock_domain.h create mode 100644 include/genavb/control_gptp.h create mode 100644 include/genavb/control_maap.h create mode 100644 include/genavb/control_srp.h create mode 100644 include/genavb/crf.h create mode 100644 include/genavb/cvf.h create mode 100644 include/genavb/dsa.h create mode 100644 include/genavb/error.h create mode 100644 include/genavb/ether.h create mode 100644 include/genavb/fdb.h create mode 100644 include/genavb/frer.h create mode 100644 include/genavb/genavb.h create mode 100644 include/genavb/helpers.h create mode 100644 include/genavb/hsr.h create mode 100644 include/genavb/init.h create mode 100644 include/genavb/log.h create mode 100644 include/genavb/media.h create mode 100644 include/genavb/net_types.h create mode 100644 include/genavb/port.h create mode 100644 include/genavb/psfp.h create mode 100644 include/genavb/ptp.h create mode 100644 include/genavb/qos.h create mode 100644 include/genavb/socket.h create mode 100644 include/genavb/sr_class.h create mode 100644 include/genavb/srp.h create mode 100644 include/genavb/stats.h create mode 100644 include/genavb/stream_identification.h create mode 100644 include/genavb/streaming.h create mode 100644 include/genavb/timer.h create mode 100644 include/genavb/tsn.h create mode 100644 include/genavb/types.h create mode 100644 include/genavb/vlan.h create mode 100644 include/linux/os/config_os.h create mode 100644 include/linux/os/control.h create mode 100644 include/linux/os/init.h create mode 100644 include/linux/os/log.h create mode 100644 include/linux/os/net_types.h create mode 100644 include/linux/os/qos.h create mode 100644 include/linux/os/socket.h create mode 100644 include/linux/os/stats.h create mode 100644 include/linux/os/streaming.h create mode 100644 include/linux/os/timer.h create mode 100644 include/linux/os/types.h create mode 100644 include/rtos/os/config_os.h create mode 100644 include/rtos/os/control.h create mode 100644 include/rtos/os/dsa.h create mode 100644 include/rtos/os/fdb.h create mode 100644 include/rtos/os/frer.h create mode 100644 include/rtos/os/hsr.h create mode 100644 include/rtos/os/init.h create mode 100644 include/rtos/os/log.h create mode 100644 include/rtos/os/net_types.h create mode 100644 include/rtos/os/port.h create mode 100644 include/rtos/os/psfp.h create mode 100644 include/rtos/os/qos.h create mode 100644 include/rtos/os/socket.h create mode 100644 include/rtos/os/stats.h create mode 100644 include/rtos/os/stream_identification.h create mode 100644 include/rtos/os/streaming.h create mode 100644 include/rtos/os/timer.h create mode 100644 include/rtos/os/types.h create mode 100644 include/rtos/os/vlan.h create mode 100644 licenses/BSD-3-Clause create mode 100644 licenses/CC0-1.0 create mode 100644 licenses/COPYING create mode 100644 linux/assert.c create mode 100644 linux/avb.h create mode 100644 linux/avb_main.c create mode 100644 linux/cfgfile.c create mode 100644 linux/cfgfile.h create mode 100644 linux/clock.c create mode 100644 linux/clock.h create mode 100644 linux/config.h create mode 100644 linux/configs/apps-avnu.cfg create mode 100644 linux/configs/apps-listener-alsa-milan.cfg create mode 100644 linux/configs/apps-listener-alsa.cfg create mode 100644 linux/configs/apps-listener-audio.cfg create mode 100644 linux/configs/apps-listener-simple-acf.cfg create mode 100644 linux/configs/apps-listener-simple.cfg create mode 100644 linux/configs/apps-listener-talker-multi-format.cfg create mode 100644 linux/configs/apps-listener-talker-multi.cfg create mode 100644 linux/configs/apps-listener-video-camera.cfg create mode 100644 linux/configs/apps-listener-video.cfg create mode 100644 linux/configs/apps-media-listener-talker.cfg create mode 100644 linux/configs/apps-media-master.cfg create mode 100644 linux/configs/apps-media-slave.cfg create mode 100644 linux/configs/apps-talker-simple-aaf.cfg create mode 100644 linux/configs/apps-talker-simple-acf.cfg create mode 100644 linux/configs/apps-talker-simple.cfg create mode 100644 linux/configs/apps-talker-video.cfg create mode 100644 linux/configs/apps-test-single-board.cfg create mode 100644 linux/configs/apps-tsn-network-controller.cfg create mode 100644 linux/configs/apps-tsn-network-iodevice.cfg create mode 100644 linux/configs/config create mode 100644 linux/configs/config_avb create mode 100644 linux/configs/config_avb_bridge create mode 100644 linux/configs/config_avb_hybrid create mode 100644 linux/configs/config_tsn create mode 100644 linux/configs/configs.cmake create mode 100644 linux/configs/fgptp-br.cfg create mode 100644 linux/configs/fgptp-br.cfg-1 create mode 100644 linux/configs/fgptp.cfg create mode 100644 linux/configs/fgptp.cfg-1 create mode 100644 linux/configs/genavb-audio-multi-aaf.cfg create mode 100644 linux/configs/genavb-audio-multi-btb-aaf.cfg create mode 100644 linux/configs/genavb-audio-multi-btb.cfg create mode 100644 linux/configs/genavb-audio-multi-format.cfg create mode 100644 linux/configs/genavb-audio-multi.cfg create mode 100644 linux/configs/genavb-avnu.cfg create mode 100644 linux/configs/genavb-listener-acf.cfg create mode 100644 linux/configs/genavb-listener-btb.cfg create mode 100644 linux/configs/genavb-listener-milan.cfg create mode 100644 linux/configs/genavb-listener-multi-video-btb.cfg create mode 100644 linux/configs/genavb-listener-talker-milan.cfg create mode 100644 linux/configs/genavb-listener-video-btb.cfg create mode 100644 linux/configs/genavb-listener-video-camera.cfg create mode 100644 linux/configs/genavb-listener-video-h264-btb.cfg create mode 100644 linux/configs/genavb-listener.cfg create mode 100644 linux/configs/genavb-talker-milan.cfg create mode 100644 linux/configs/genavb-talker-simple-acf.cfg create mode 100644 linux/configs/genavb-talker-simple.cfg create mode 100644 linux/configs/genavb-talker-video.cfg create mode 100644 linux/configs/linux_bridge_system.cfg create mode 100644 linux/configs/linux_hybrid_avb_system.cfg create mode 100644 linux/configs/linux_imx6_endpoint_avb_system.cfg create mode 100644 linux/configs/linux_imx6ull_endpoint_avb_system.cfg create mode 100644 linux/configs/linux_imx8_endpoint_avb_system.cfg create mode 100644 linux/configs/linux_imx8_endpoint_tsn_srp.cfg create mode 100644 linux/configs/linux_imx8_endpoint_tsn_system.cfg create mode 100644 linux/configs/srp-br.cfg create mode 100644 linux/configs/srp.cfg create mode 100644 linux/ebpf/ebpf.cmake create mode 100644 linux/ebpf/ebpf.h create mode 100644 linux/ebpf/genavb_xdp_main.c create mode 100644 linux/epoll.c create mode 100644 linux/epoll.h create mode 100644 linux/extensions.cmake create mode 100644 linux/fdb.c create mode 100644 linux/fdb.h create mode 100644 linux/fdb_std.c create mode 100644 linux/firmware/genavb-xdp.bin create mode 100644 linux/fqtss.c create mode 100644 linux/fqtss.h create mode 100644 linux/fqtss_avb.c create mode 100644 linux/fqtss_std.c create mode 100644 linux/init.c create mode 100644 linux/init.h create mode 100644 linux/ipc.c create mode 100644 linux/ipc.h create mode 100644 linux/linux.cmake create mode 100644 linux/log.c create mode 100644 linux/log.h create mode 100644 linux/media.c create mode 100644 linux/media_clock.c create mode 100644 linux/modules/Makefile create mode 100644 linux/modules/Makefile.modules create mode 100644 linux/modules/avb/avbdrv.c create mode 100644 linux/modules/avb/avbdrv.h create mode 100644 linux/modules/avb/avtp.c create mode 100644 linux/modules/avb/avtp.h create mode 100644 linux/modules/avb/debugfs.c create mode 100644 linux/modules/avb/debugfs.h create mode 100644 linux/modules/avb/epit.c create mode 100644 linux/modules/avb/epit.h create mode 100644 linux/modules/avb/gpt.c create mode 100644 linux/modules/avb/gpt.h create mode 100644 linux/modules/avb/hw_timer.c create mode 100644 linux/modules/avb/hw_timer.h create mode 100644 linux/modules/avb/imx-pll.c create mode 100644 linux/modules/avb/imx-pll.h create mode 100644 linux/modules/avb/media.c create mode 100644 linux/modules/avb/media.h create mode 100644 linux/modules/avb/media_clock.c create mode 100644 linux/modules/avb/media_clock.h create mode 100644 linux/modules/avb/media_clock_drv.c create mode 100644 linux/modules/avb/media_clock_drv.h create mode 100644 linux/modules/avb/media_clock_gen_ptp.c create mode 100644 linux/modules/avb/media_clock_gen_ptp.h create mode 100644 linux/modules/avb/media_clock_rec_pll.c create mode 100644 linux/modules/avb/media_clock_rec_pll.h create mode 100644 linux/modules/avb/mrp.c create mode 100644 linux/modules/avb/mrp.h create mode 100644 linux/modules/avb/mtimer.c create mode 100644 linux/modules/avb/mtimer.h create mode 100644 linux/modules/avb/mtimer_drv.c create mode 100644 linux/modules/avb/mtimer_drv.h create mode 100644 linux/modules/avb/net_logical_port.c create mode 100644 linux/modules/avb/net_logical_port.h create mode 100644 linux/modules/avb/net_port.c create mode 100644 linux/modules/avb/net_port.h create mode 100644 linux/modules/avb/net_rx.c create mode 100644 linux/modules/avb/net_rx.h create mode 100644 linux/modules/avb/net_socket.c create mode 100644 linux/modules/avb/net_socket.h create mode 100644 linux/modules/avb/net_tx.c create mode 100644 linux/modules/avb/net_tx.h create mode 100644 linux/modules/avb/netdrv.c create mode 100644 linux/modules/avb/netdrv.h create mode 100644 linux/modules/avb/pi.c create mode 100644 linux/modules/avb/pi.h create mode 100644 linux/modules/avb/pool_dma.c create mode 100644 linux/modules/avb/pool_dma.h create mode 100644 linux/modules/avb/ptp.c create mode 100644 linux/modules/avb/ptp.h create mode 100644 linux/modules/avb/rational.c create mode 100644 linux/modules/avb/rational.h create mode 100644 linux/modules/avb/stats.c create mode 100644 linux/modules/avb/stats.h create mode 100644 linux/modules/avb/tpm.c create mode 100644 linux/modules/avb/tpm.h create mode 100644 linux/modules/common/ipc.c create mode 100644 linux/modules/common/ipc.h create mode 100644 linux/modules/common/pool.c create mode 100644 linux/modules/common/pool.h create mode 100644 linux/modules/common/port_config.h create mode 100644 linux/modules/common/queue.c create mode 100644 linux/modules/common/queue.h create mode 100644 linux/modules/std/ipc_module.c create mode 100644 linux/net.c create mode 100644 linux/net.h create mode 100644 linux/net_avb.c create mode 100644 linux/net_ipc.c create mode 100644 linux/net_logical_port.c create mode 100644 linux/net_logical_port.h create mode 100644 linux/net_std.c create mode 100644 linux/net_std_socket_filters.c create mode 100644 linux/net_std_socket_filters.h create mode 100644 linux/net_xdp.c create mode 100644 linux/os_config.c create mode 100644 linux/os_config.h create mode 100644 linux/osal/assert.h create mode 100644 linux/osal/clock.h create mode 100644 linux/osal/config.h create mode 100644 linux/osal/epoll.h create mode 100644 linux/osal/fdb.h create mode 100644 linux/osal/ipc.h create mode 100644 linux/osal/log.h create mode 100644 linux/osal/media.h create mode 100644 linux/osal/media_clock.h create mode 100644 linux/osal/net.h create mode 100644 linux/osal/stdlib.h create mode 100644 linux/osal/sys_types.h create mode 100644 linux/osal/timer.h create mode 100644 linux/pool.c create mode 100644 linux/pool.h create mode 100644 linux/rtnetlink.c create mode 100644 linux/rtnetlink.h create mode 100755 linux/scripts/avb-bridge.sh create mode 100755 linux/scripts/avb.sh create mode 100755 linux/scripts/init.sh create mode 100644 linux/scripts/scripts.cmake create mode 100755 linux/scripts/tsn-app-setup.sh create mode 100755 linux/scripts/tsn.sh create mode 100644 linux/shmem.c create mode 100644 linux/shmem.h create mode 100644 linux/stdlib.c create mode 100644 linux/string.c create mode 100644 linux/timer.c create mode 100644 linux/timer_media.c create mode 100644 linux/timer_media.h create mode 100644 linux/tsn.h create mode 100644 linux/tsn_main.c create mode 100644 linux/vlan.c create mode 100644 maap/config.h create mode 100644 maap/linux/maap.cmake create mode 100644 maap/linux/main.c create mode 100644 maap/maap.c create mode 100644 maap/maap.cmake create mode 100644 maap/maap.h create mode 100644 maap/maap_entry.h create mode 100644 maap/rtos/maap.cmake create mode 100644 maap/rtos/main.c create mode 100644 management/config.h create mode 100644 management/linux/main.c create mode 100644 management/linux/management.cmake create mode 100644 management/mac_service.c create mode 100644 management/mac_service.h create mode 100644 management/management.c create mode 100644 management/management.cmake create mode 100644 management/management.h create mode 100644 management/management_entry.h create mode 100644 management/rtos/main.c create mode 100644 management/rtos/management.cmake create mode 100644 os/assert.h create mode 100644 os/clock.h create mode 100644 os/config.h create mode 100644 os/dsa.h create mode 100644 os/fdb.h create mode 100644 os/fqtss.h create mode 100644 os/frer.h create mode 100644 os/ipc.h create mode 100644 os/log.h create mode 100644 os/media.h create mode 100644 os/media_clock.h create mode 100644 os/net.h create mode 100644 os/psfp.h create mode 100644 os/qos.h create mode 100644 os/stdlib.h create mode 100644 os/stream_identification.h create mode 100644 os/string.h create mode 100644 os/sys_types.h create mode 100644 os/timer.h create mode 100644 os/vlan.h create mode 100644 public/aem_helpers.c create mode 100644 public/config.h create mode 100644 public/helpers.c create mode 100644 public/linux/aem_helpers.c create mode 100644 public/linux/public.cmake create mode 100644 public/public.cmake create mode 100644 public/qos.c create mode 100644 public/sr_class.c create mode 100644 rtos/assert.c create mode 100644 rtos/avb_queue.c create mode 100644 rtos/avb_queue.h create mode 100644 rtos/avtp.c create mode 100644 rtos/avtp.h create mode 100644 rtos/clock.c create mode 100644 rtos/clock.h create mode 100644 rtos/config.h create mode 100644 rtos/debug_print.c create mode 100644 rtos/debug_print.h create mode 100644 rtos/extensions.cmake create mode 100644 rtos/fdb.c create mode 100644 rtos/fp.c create mode 100644 rtos/fp.h create mode 100644 rtos/fqtss.c create mode 100644 rtos/fqtss.h create mode 100644 rtos/freertos/rtos_abstraction_layer.cmake create mode 100644 rtos/freertos/rtos_abstraction_layer.h create mode 100644 rtos/freertos/rtos_assert.h create mode 100644 rtos/freertos/rtos_atomic.c create mode 100644 rtos/freertos/rtos_atomic.h create mode 100644 rtos/freertos/rtos_event_group.h create mode 100644 rtos/freertos/rtos_heap.h create mode 100644 rtos/freertos/rtos_mqueue.c create mode 100644 rtos/freertos/rtos_mqueue.h create mode 100644 rtos/freertos/rtos_mutex.h create mode 100644 rtos/freertos/rtos_sched.c create mode 100644 rtos/freertos/rtos_sched.h create mode 100644 rtos/freertos/rtos_thread.c create mode 100644 rtos/freertos/rtos_thread.h create mode 100644 rtos/freertos/rtos_time.h create mode 100644 rtos/freertos/rtos_timer.c create mode 100644 rtos/freertos/rtos_timer.h create mode 100644 rtos/frer.c create mode 100644 rtos/gpt.c create mode 100644 rtos/gpt.h create mode 100644 rtos/gpt_rec.c create mode 100644 rtos/gpt_rec.h create mode 100644 rtos/gptp_dev.c create mode 100644 rtos/gptp_dev.h create mode 100644 rtos/hr_timer.c create mode 100644 rtos/hr_timer.h create mode 100644 rtos/hsr.c create mode 100644 rtos/hsr.h create mode 100644 rtos/hw_clock.c create mode 100644 rtos/hw_clock.h create mode 100644 rtos/hw_timer.c create mode 100644 rtos/hw_timer.h create mode 100644 rtos/imx-pll.c create mode 100644 rtos/imx-pll.h create mode 100644 rtos/ipc.c create mode 100644 rtos/ipc.h create mode 100644 rtos/l2.c create mode 100644 rtos/l2.h create mode 100644 rtos/log.c create mode 100644 rtos/media.c create mode 100644 rtos/media_clock.c create mode 100644 rtos/media_clock.h create mode 100644 rtos/media_clock_drv.h create mode 100644 rtos/media_clock_gen_ptp.c create mode 100644 rtos/media_clock_gen_ptp.h create mode 100644 rtos/media_clock_rec_pll.c create mode 100644 rtos/media_clock_rec_pll.h create mode 100644 rtos/media_queue.c create mode 100644 rtos/media_queue.h create mode 100644 rtos/mrp.c create mode 100644 rtos/mrp.h create mode 100644 rtos/msgintr.c create mode 100644 rtos/msgintr.h create mode 100644 rtos/mtimer.c create mode 100644 rtos/mtimer.h create mode 100644 rtos/net.c create mode 100644 rtos/net.h create mode 100644 rtos/net_bridge.c create mode 100644 rtos/net_bridge.h create mode 100644 rtos/net_logical_port.c create mode 100644 rtos/net_logical_port.h create mode 100644 rtos/net_mdio.c create mode 100644 rtos/net_mdio.h create mode 100644 rtos/net_phy.c create mode 100644 rtos/net_phy.h create mode 100644 rtos/net_port.c create mode 100644 rtos/net_port.h create mode 100644 rtos/net_port_dsa.c create mode 100644 rtos/net_port_dsa.h create mode 100644 rtos/net_port_enet.c create mode 100644 rtos/net_port_enet.h create mode 100644 rtos/net_port_enet_mdio.c create mode 100644 rtos/net_port_enet_mdio.h create mode 100644 rtos/net_port_enet_qos.c create mode 100644 rtos/net_port_enet_qos.h create mode 100644 rtos/net_port_enet_qos_mdio.c create mode 100644 rtos/net_port_enet_qos_mdio.h create mode 100644 rtos/net_port_enet_qos_stats.c create mode 100644 rtos/net_port_enet_stats.c create mode 100644 rtos/net_port_enetc_ep.c create mode 100644 rtos/net_port_enetc_ep.h create mode 100644 rtos/net_port_netc_1588.c create mode 100644 rtos/net_port_netc_1588.h create mode 100644 rtos/net_port_netc_mdio.c create mode 100644 rtos/net_port_netc_mdio.h create mode 100644 rtos/net_port_netc_psfp.c create mode 100644 rtos/net_port_netc_psfp.h create mode 100644 rtos/net_port_netc_stats.c create mode 100644 rtos/net_port_netc_stream_identification.c create mode 100644 rtos/net_port_netc_stream_identification.h create mode 100644 rtos/net_port_netc_sw.c create mode 100644 rtos/net_port_netc_sw.h create mode 100644 rtos/net_port_netc_sw_drv.h create mode 100644 rtos/net_port_netc_sw_dsa.c create mode 100644 rtos/net_port_netc_sw_dsa.h create mode 100644 rtos/net_port_netc_sw_frer.c create mode 100644 rtos/net_port_netc_sw_frer.h create mode 100644 rtos/net_port_stats.c create mode 100644 rtos/net_port_stats.h create mode 100644 rtos/net_rx.c create mode 100644 rtos/net_rx.h create mode 100644 rtos/net_socket.c create mode 100644 rtos/net_socket.h create mode 100644 rtos/net_task.c create mode 100644 rtos/net_task.h create mode 100644 rtos/net_tx.c create mode 100644 rtos/net_tx.h create mode 100644 rtos/osal/assert.h create mode 100644 rtos/osal/clock.h create mode 100644 rtos/osal/config.h create mode 100644 rtos/osal/configs/config create mode 100644 rtos/osal/ipc.h create mode 100644 rtos/osal/log.h create mode 100644 rtos/osal/media.h create mode 100644 rtos/osal/media_clock.h create mode 100644 rtos/osal/net.h create mode 100644 rtos/osal/stdlib.h create mode 100644 rtos/osal/sys_types.h create mode 100644 rtos/osal/timer.h create mode 100644 rtos/pi.c create mode 100644 rtos/pi.h create mode 100644 rtos/prng_32.c create mode 100644 rtos/prng_32.h create mode 100644 rtos/psfp.c create mode 100644 rtos/ptp.c create mode 100644 rtos/ptp.h create mode 100644 rtos/qos.c create mode 100644 rtos/rational.c create mode 100644 rtos/rational.h create mode 100644 rtos/rtos.cmake create mode 100644 rtos/slist.h create mode 100644 rtos/stats_task.c create mode 100644 rtos/stats_task.h create mode 100644 rtos/stdlib.c create mode 100644 rtos/stream_identification.c create mode 100644 rtos/string.c create mode 100644 rtos/timer.c create mode 100644 rtos/tpm.c create mode 100644 rtos/tpm.h create mode 100644 rtos/tpm_rec.c create mode 100644 rtos/tpm_rec.h create mode 100644 rtos/vlan.c create mode 100644 srp/config.h create mode 100644 srp/linux/main.c create mode 100644 srp/linux/srp.cmake create mode 100644 srp/mmrp.c create mode 100644 srp/mmrp.h create mode 100644 srp/mrp.c create mode 100644 srp/mrp.h create mode 100644 srp/msrp.c create mode 100644 srp/msrp.h create mode 100644 srp/msrp_map.c create mode 100644 srp/msrp_map.h create mode 100644 srp/mvrp.c create mode 100644 srp/mvrp.h create mode 100644 srp/mvrp_map.c create mode 100644 srp/mvrp_map.h create mode 100644 srp/rtos/main.c create mode 100644 srp/rtos/srp.cmake create mode 100644 srp/srp.c create mode 100644 srp/srp.cmake create mode 100644 srp/srp.h create mode 100644 srp/srp_entry.h create mode 100644 srp/srp_managed_objects.c create mode 100644 srp/srp_managed_objects.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3d3384 --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# +# NOTE! Don't add files that are generated in specific +# subdirectories here. Add them in the ".gitignore" file +# in that subdirectory instead. +# +# NOTE! Please use 'git ls-files -i --exclude-standard' +# command after changing this file, to see if there are +# any tracked files which get ignored after the change. +# +# Normal rules +# +.* +*.o +*.o.* +*.a +*.s +*.ko +*.so +*.so.dbg +*.mod.c +*.i +*.lst +*.elf +*.bin +*.gz +*.bz2 +*.lzma +*.xz +*.lzo +*.patch +*.gcno +*.orig +*.rej +*~ + +# Track the XDP program +!linux/firmware/genavb-xdp.bin + +!.gitignore + +# Default build directory +/build + +# Generated include files +/common/version.h + +# local config files +local_config*.mk + +# CMake +local_config*.cmake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# cscope files +cscope.* diff --git a/CMakeLists.linux b/CMakeLists.linux new file mode 100644 index 0000000..c2f0cc2 --- /dev/null +++ b/CMakeLists.linux @@ -0,0 +1,51 @@ +option(BUILD_KERNEL_MODULE "Build kernel module" ON) +option(BUILD_APPS "Build example applications" ON) + +set(modules_obj_dir ${CMAKE_BINARY_DIR}/modules) + +set(modules_extra_flags + MAKEFLAGS= + KERNELDIR=${KERNELDIR} + GENAVB_INCLUDE=${TOPDIR}/include + MODULES_OBJ_DIR=${modules_obj_dir} + GENAVB_ROOT_DIR=${TOPDIR} + target=${TARGET} + config=${CONFIG} + PREFIX="\$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}" +) + +add_custom_target(modules-dir + COMMAND $(MAKE) -C ${CMAKE_SOURCE_DIR}/linux/modules modules_sources + ${modules_extra_flags} + COMMENT "Generate modules src files" +) + +if(${CMAKE_VERSION} VERSION_LESS "3.15.0") + set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${modules_obj_dir}) +else() + set_property(TARGET modules-dir APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${modules_obj_dir}) +endif() + +if(BUILD_KERNEL_MODULE) + +add_custom_target(modules ALL + COMMAND $(MAKE) -C ${CMAKE_SOURCE_DIR}/linux/modules modules + ${modules_extra_flags} + DEPENDS modules-dir + COMMENT "Building modules" +) + +add_custom_target(modules-install + COMMAND $(MAKE) -C ${CMAKE_SOURCE_DIR}/linux/modules modules_install + ${modules_extra_flags} + DEPENDS modules-dir + COMMENT "Installing modules" +) + +install(CODE "execute_process(COMMAND make -C ${CMAKE_SOURCE_DIR}/linux/modules modules_install ${modules_extra_flags})") + +endif() + +if(BUILD_APPS) +add_subdirectory(${CMAKE_SOURCE_DIR}/apps/linux) +endif() diff --git a/README b/CMakeLists.rtos old mode 100755 new mode 100644 similarity index 100% rename from README rename to CMakeLists.rtos diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..66c1b29 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.10) + +project(GenAVB/TSN) + +set(TOPDIR ${CMAKE_CURRENT_SOURCE_DIR}) + +set(VERSION_FILE common/version.h) + +add_custom_target(stack) + +if(NOT DEFINED TARGET) + message(FATAL_ERROR "Target not defined") +endif() +message(STATUS "TARGET: ${TARGET}") + +set(target_file "${CMAKE_CURRENT_SOURCE_DIR}/config_${TARGET}.cmake") +if(NOT EXISTS ${target_file}) + message(FATAL_ERROR "cannot find ${target_file}") +endif() + +set(CMAKE_INSTALL_MESSAGE LAZY) +# If not explicetly defined, set install prefix to build directory +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/target" + CACHE PATH "default install path" FORCE) +endif() + +include(configs/configs.cmake) +include(${target_file}) +include(extensions.cmake) + +list(APPEND maindirs + ${TARGET_OS} + management + gptp + srp + maap + hsr + avtp + avdecc + api + public + common +) + +foreach(dir ${maindirs}) + include(${dir}/${dir}.cmake) +endforeach() + +include(CMakeLists.${TARGET_OS}) +include(doc/CMakeLists.txt) + +genavb_generate_version() + +genavb_generate_archives() + +genavb_install() diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b96aaf --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +GenAVB/TSN +---------- +GenAVB/TSN is a generic AVB/TSN stack developed by NXP for NXP MCUs and MPUs. +It is cross-platform, currently supporting Linux, FreeRTOS and Zephyr. + + +Supported hardware targets and configurations +--------------------------------------------- +This project supports several hardware targets and several different +configuration modes. The table below summarizes the supported combinations, +and their status for this specific release: +- **O**: supported by the project and validated for this release +- x: supported by the project, but not validated for this release +- empty cell: unsupported combination + +| Compilation target | Hardware target | AVB endpoint | TSN endpoint | AVB/TSN bridge | AVB hybrid | +| :--------------------: | :--------------------------------------------: | :----------: | :----------: | :------------: | :--------: | +| `linux i.MX6` | `i.MX 6Q`, `i.MX 6QP`, `i.MX 6D`, `i.MX 6SX` | x | | | | +| `linux i.MX6ULL` | `i.MX 6ULL` | **O** | | | | +| `linux i.MX8` | `i.MX 8MM` | **O** | | | | +| `linux i.MX8` | `i.MX 8MP`, `i.MX 8DXL`, `i.MX 93` | **O** | **O** | | | +| `linux i.MX8` | `i.MX 8DXL + SJA1105Q` | | | **O** | | +| `linux i.MX8` | `i.MX 93 + SJA1105Q` | | | **O** | **O** | +| `linux LS1028` | `LS1028A` | | | **O** | | +| `freertos RT1052` | `i.MX RT1052` | x | x | | | +| `freertos RT1176` | `i.MX RT1176` | x | x | | | +| `freertos RT1187 M33` | `i.MX RT1187` | | | x | | +| `freertos RT1187 M7 ` | `i.MX RT1187` | | x | | | +| `freertos RT1189 M33` | `i.MX RT1189` | | | x | | +| `freertos RT1189 M7 ` | `i.MX RT1189` | | x | | | +| `freertos i.MX8MM A53` | `i.MX 8MM` | **O** | **O** | | | +| `freertos i.MX8MN A53` | `i.MX 8MN` | **O** | **O** | | | +| `freertos i.MX8MP A53` | `i.MX 8MP` | **O** | **O** | | | +| `freertos i.MX93 A55` | `i.MX 93` | **O** | **O** | | | +| `zephyr i.MX8MM A53` | `i.MX 8MM` | **O** | **O** | | | +| `zephyr i.MX8MN A53` | `i.MX 8MN` | **O** | **O** | | | +| `zephyr i.MX8MP A53` | `i.MX 8MP` | **O** | **O** | | | +| `zephyr i.MX93 A55` | `i.MX 93` | **O** | **O** | | | + +Features +-------- +- IEEE-802.1AS-2020 implementation, both time-aware Bridge and Endpoint support. +- IEEE-802.1Q-2022 implementation, both Bridge and Endpoint Support + - VLAN/FDB + - Stream Reservation Protocol (Qat-2010) + - Scheduled Traffic (Qbv-2015) + - Frame preemption (Qbu-2016) + - Per Stream Filtering and Policing (Qci-2017) + - Forwarding and Queuing for Time-Sensitive Streams (Qav-2009) +- IEEE 802.1CB-2017 implementation for Frame Replication and Elimination for Reliability. +- IEEE 802.3br-2016 implementation for Interspersing Express Traffic. +- IEEE-1722-2016 implementation, with MAAP support. +- IEEE-1722.1-2013 implementation, with support for Milan v1.2 mode. +- IEC 62439-3:2022 + - High-availability Seamless Redundancy (HSR) +- Protocol stacks running in standalone userspace processes for Linux, and dedicated threads for FreeRTOS. +- Public C API provided by library plus header files. +- Example applications. + + +Repository structure +-------------------- +- api: the public API +- apps: source code and makefiles for example applications +- avdecc: IEEE 1722.1-2013/Milan v1.2 component stack +- avtp: IEEE 1722-2016 component stack +- common: common code +- configs: configuration files +- doc: documentation +- rtos: RTOS specific code +- gptp: IEEE 802.1AS-2020 component stack +- hsr: IEC 62439-3:2022 HSR component stack +- linux: Linux specific code +- maap: MAAP component code +- public: common code shared between applications and stack +- srp: IEEE 802.1Qat-2010 component stack + + +Build +----- +GenAVB/TSN is using Cmake to generate build system. + +Some preliminary configuration is required to provide path to the toolchain, +staging directory and external components. + +Local configuration files can be included by the build system in order to +to define some variables specific to the developer environment. +The local config file name is `./local_config_${target}.cmake` + + +Build requirements +------------------ +- Linux host system and development tools (git, make, doxygen for the docs, ...) +- An ARM gcc toolchain is sufficient for the stack. However building the linux +applications requires a more complete cross-compilation SDK (because of +additional dependencies such as alsa and gstreamer). +- AVB endpoint builds depend on custom changes to the Linux kernel and specific Yocto distribution: https://www.nxp.com/design/software/development-software/real-time-edge-software:REALTIME-EDGE-SOFTWARE +- FreeRTOS builds require additional MCUXpresso SDK software: https://www.nxp.com/design/software/development-software/mcuxpresso-software-and-tools-/wired-communications-middleware-for-nxp-microcontrollers:WIRED-COMM-MIDDLEWARE?fpsp=1&#avb-tsn +- Zephyr builds require the usage of an [RTOS abstraction layer](https://github.com/NXP/rtos-abstraction-layer) and additional NXP software. Refer to [Harpoon Software](https://github.com/NXP/harpoon-apps) for reference. + +### FreeRTOS +Currently GenAVB/TSN stack support only ARM gcc toolchain for FreeRTOS targets. +To be able to build the stack, ARMGCC_DIR environment variable pointing +to arm-gcc toolchain must be defined. +``` +export ARMGCC_DIR=/path/to/gcc-arm-none-eabi-xxx +``` + +The local config file should define: +``` +set(MCUX_SDK "/path/to/freertos_MCUXpresso_SDK" CACHE PATH "Path to MCUXpresso SDK") +set(RTOS_APPS "/path/to/freertos_application" CACHE PATH "Path to FreeRTOS application repository") +``` + +### Zephyr +The GenAVB/TSN stack can be built as a Cmake subdirectory of a top level Zephyr application +Refer to [Harpoon Software](https://github.com/NXP/harpoon-apps) for an example application. + +### Linux +The Linux target is usually built using a complete toolchain, which helps +setting most of the environment variables required for cross-compilation. +In the case of a Yocto SDK/toolchain for example, the following command will +setup $CROSS_COMPILE as well as other related environment variables: + +``` +source /path/to/yocto/toolchain/environment-setup-xxx +``` + +The local config should however define: +``` +set(KERNELDIR "/path/to/linux_avb" CACHE PATH "Path to Linux kernel") +``` + + +Build commands +-------------- +Syntax is: +``` +cmake . -B -DTARGET= -DCONFIG= +make -C install +``` + +The GenAVB/TSN stack also provides a couple shell functions with auto-completion +to facilitate the build process. +Usage is +``` +# Setup environment, see Build paragraph above +source environment-genavb +make_genavb [target] [config_list] +clean_genavb [target] +``` +where target and config_list are optional. If no config_list is defined, all available +configurations for specified target are built. If no target is specified the default +is linux_imx6. +config_list is of the form: configA configB ..., with one or more members. + +To generate doxygen HTML documentation: +``` +cmake . -B -DBUILD_DOC=ON -DTARGET= -DCONFIG= +make -C doc_doxygen +``` + +The generated documentation is available under `/doc` +To install documentation under a custom path: use cmake variable `-DDOC_OUTPUT_DIR=` + +### Generated binaries +The generated binaries are installed under `/` + +### Installing binaries to target + +#### Linux +Copy the content of `/target/` to the root directory of the target filesystem + +#### FreeRTOS +Refer to the FreeRTOS application README. + +#### Zephyr +Refer to the Harpoon application README. diff --git a/SCR_genavb-release.txt b/SCR_genavb-release.txt new file mode 100644 index 0000000..6dcd231 --- /dev/null +++ b/SCR_genavb-release.txt @@ -0,0 +1,47 @@ +NXP Software Content Register + +Release Name: genavb-sdk +Version: GenAVB/TSN 6_2_0 +Date Created: July-2024 +Release Location: https://github.com/NXP/GenAVB_TSN +Description: GenAVB/TSN protocol stack for NXP MPUs and MCUs processors +Origin: NXP (BSD-3-Clause) + NXP (GPL-2.0-or-later) + NXP (CC0-1.0) + xoroshiro generator - David Blackman and Sebastiano Vigna (vigna@acm.org) (CC0-1.0) - https://prng.di.unimi.it/xoroshiro64starstar.c + +---------------------------------------- + +Package: api + apps + avdecc + avtp + common + configs + gptp + hsr + include + linux + maap + management + os + public + rtos + srp +Outgoing License: BSD-3-Clause +License File: licenses/BSD-3-Clause +Package Category: GenAVB/TSN Stack and Applications +Description and comments: GenAVB/TSN stack with example applications +Type of content: Source code +Description and comments: GenAVB/TSN library +Origin: NXP (BSD-3-Clause) + NXP (CC0-1.0) + xoroshiro generator - David Blackman and Sebastiano Vigna (vigna@acm.org) (CC0-1.0) - https://prng.di.unimi.it/xoroshiro64starstar.c + +Package: linux/modules +Outgoing License: GPL-2.0-or-later +License File: licenses/COPYING +Package Category: GenAVB/TSN Stack +Description and comments: Linux modules driver for GenAVB/TSN stack +Type of content: Source code +Origin: NXP (GPL-2.0-or-later) diff --git a/api/api.cmake b/api/api.cmake new file mode 100644 index 0000000..57557c2 --- /dev/null +++ b/api/api.cmake @@ -0,0 +1,19 @@ +if(CONFIG_API) + + genavb_include_os(${TARGET_OS}/api.cmake) + + genavb_target_add_srcs(TARGET genavb + SRCS + error.c + control.c + clock.c + ) + + if(CONFIG_AVTP) + genavb_target_add_srcs(TARGET genavb SRCS streaming.c) + endif() + + genavb_target_add_srcs(TARGET genavb SRCS socket.c) + +endif() + diff --git a/api/clock.c b/api/clock.c new file mode 100644 index 0000000..f736007 --- /dev/null +++ b/api/clock.c @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file clock.c + \brief GenAVB public API + \details API definition for the GenAVB library + + \copyright Copyright 2019-2023 NXP +*/ + +#include "clock.h" + +#include "genavb/error.h" + +static const os_clock_id_t public_clock_to_os_clock[] = { + [GENAVB_CLOCK_MONOTONIC] = OS_CLOCK_SYSTEM_MONOTONIC, + [GENAVB_CLOCK_GPTP_0_0] = OS_CLOCK_GPTP_EP_0_0, + [GENAVB_CLOCK_GPTP_0_1] = OS_CLOCK_GPTP_EP_0_1, + [GENAVB_CLOCK_GPTP_1_0] = OS_CLOCK_GPTP_EP_1_0, + [GENAVB_CLOCK_GPTP_1_1] = OS_CLOCK_GPTP_EP_1_1, + [GENAVB_CLOCK_BR_0_0] = OS_CLOCK_GPTP_BR_0_0, + [GENAVB_CLOCK_BR_0_1] = OS_CLOCK_GPTP_BR_0_1, +}; + +os_clock_id_t genavb_clock_to_os_clock(genavb_clock_id_t id) +{ + if (id >= GENAVB_CLOCK_MAX) + return OS_CLOCK_MAX; + + return public_clock_to_os_clock[id]; +} + +int genavb_clock_gettime64(genavb_clock_id_t id, uint64_t *ns) +{ + if (id >= GENAVB_CLOCK_MAX || !ns) + return -GENAVB_ERR_INVALID; + + if (os_clock_gettime64(public_clock_to_os_clock[id], ns) < 0) + return -GENAVB_ERR_CLOCK; + + return GENAVB_SUCCESS; +} diff --git a/api/clock.h b/api/clock.h new file mode 100644 index 0000000..c537ec4 --- /dev/null +++ b/api/clock.h @@ -0,0 +1,23 @@ +/* + * Copyright 2019-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file clock.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2019-2021, 2023 NXP +*/ + +#ifndef _PRIVATE_CLOCK_H_ +#define _PRIVATE_CLOCK_H_ + +#include "genavb/clock.h" +#include "os/clock.h" + +os_clock_id_t genavb_clock_to_os_clock(genavb_clock_id_t id); + +#endif /* _PRIVATE_CLOCK_H_ */ diff --git a/api/config.h b/api/config.h new file mode 100644 index 0000000..314f942 --- /dev/null +++ b/api/config.h @@ -0,0 +1,21 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2016, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Public API static configuration + @details Contains all compile time configuration options for the public API library +*/ + +#ifndef _API_CFG_H_ +#define _API_CFG_H_ + +#include "common/config.h" + +#define api_CFG_LOG CFG_LOG + +#endif /* _API_CFG_H_ */ diff --git a/api/control.c b/api/control.c new file mode 100644 index 0000000..4e06a56 --- /dev/null +++ b/api/control.c @@ -0,0 +1,481 @@ +/* +* Copyright 2018-2024 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + \file control.c + \brief GenAVB public API for linux + \details API definition for the GenAVB library + \copyright Copyright 2018-2024 NXP +*/ + +#include "os/string.h" +#include "os/stdlib.h" +#include "common/ipc.h" + +#include "control.h" + +const ipc_id_t ipc_id[GENAVB_CTRL_ID_MAX][3] = +{ + [GENAVB_CTRL_AVDECC_MEDIA_STACK] = { + [CTRL_TX] = IPC_MEDIA_STACK_AVDECC, + [CTRL_RX] = IPC_AVDECC_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_ID_NONE, + }, + + [GENAVB_CTRL_AVDECC_CONTROLLER] = { + [CTRL_TX] = IPC_CONTROLLER_AVDECC, + [CTRL_RX] = IPC_AVDECC_CONTROLLER, + [CTRL_RX_SYNC] = IPC_AVDECC_CONTROLLER_SYNC + }, + + [GENAVB_CTRL_AVDECC_CONTROLLED] = { + [CTRL_TX] = IPC_CONTROLLED_AVDECC, + [CTRL_RX] = IPC_AVDECC_CONTROLLED, + [CTRL_RX_SYNC] = IPC_ID_NONE, + }, + + [GENAVB_CTRL_MSRP] = { + [CTRL_TX] = IPC_MEDIA_STACK_MSRP, + [CTRL_RX] = IPC_MSRP_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_MSRP_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_MSRP_1] = { + [CTRL_TX] = IPC_MEDIA_STACK_MSRP_1, + [CTRL_RX] = IPC_MSRP_1_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_MSRP_1_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_MSRP_BRIDGE] = { + [CTRL_TX] = IPC_MEDIA_STACK_MSRP_BRIDGE, + [CTRL_RX] = IPC_MSRP_BRIDGE_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_MSRP_BRIDGE_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_MVRP] = { + [CTRL_TX] = IPC_MEDIA_STACK_MVRP, + [CTRL_RX] = IPC_MVRP_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_MVRP_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_MVRP_1] = { + [CTRL_TX] = IPC_MEDIA_STACK_MVRP_1, + [CTRL_RX] = IPC_MVRP_1_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_MVRP_1_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_MVRP_BRIDGE] = { + [CTRL_TX] = IPC_MEDIA_STACK_MVRP_BRIDGE, + [CTRL_RX] = IPC_MVRP_BRIDGE_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_MVRP_BRIDGE_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_CLOCK_DOMAIN] = { + [CTRL_TX] = IPC_MEDIA_STACK_CLOCK_DOMAIN, + [CTRL_RX] = IPC_CLOCK_DOMAIN_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_CLOCK_DOMAIN_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_GPTP] = { + [CTRL_TX] = IPC_MEDIA_STACK_GPTP, + [CTRL_RX] = IPC_GPTP_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_GPTP_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_GPTP_1] = { + [CTRL_TX] = IPC_MEDIA_STACK_GPTP_1, + [CTRL_RX] = IPC_GPTP_1_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_GPTP_1_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_GPTP_BRIDGE] = { + [CTRL_TX] = IPC_MEDIA_STACK_GPTP_BRIDGE, + [CTRL_RX] = IPC_GPTP_BRIDGE_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_GPTP_BRIDGE_MEDIA_STACK_SYNC, + }, + + [GENAVB_CTRL_MAAP] = { + [CTRL_TX] = IPC_MEDIA_STACK_MAAP, + [CTRL_RX] = IPC_MAAP_MEDIA_STACK, + [CTRL_RX_SYNC] = IPC_MAAP_MEDIA_STACK_SYNC, + } +}; + +#define MAAP_MAX_CTRL_ID 256 /* Control application can allocate range with ID between 0 and 255 */ + +#define HEARTBEAT_TIMEOUT 3000 +int send_heartbeat(struct ipc_tx *tx, struct ipc_rx *rx, unsigned int flags) +{ + int rc; + struct ipc_heartbeat hb; + unsigned int msg_type, msg_len; + + hb.status = 0; + + /* + * Send Heartbeat message + */ + rc = avb_ipc_send(tx, IPC_HEARTBEAT, &hb, sizeof(hb), flags); + if (rc != GENAVB_SUCCESS) { + //printf("Couldn't send HEARTBEAT message rc(%d)\n", rc); + goto exit; + } + + if (rx) { + msg_len = sizeof(struct ipc_heartbeat); + rc = avb_ipc_receive_sync(rx, &msg_type, &hb, &msg_len, HEARTBEAT_TIMEOUT); + if ((rc != GENAVB_SUCCESS) || (msg_type != IPC_HEARTBEAT)) { + //printf("Couldn't receive HEARTBEAT reply rc(%d) type(%d)\n", rc, msg_type); + goto exit; + } + } + +exit: + return rc; +} + +int avb_ipc_send(const struct ipc_tx *tx, genavb_msg_type_t msg_type, void const *msg, unsigned int msg_len, unsigned int flags) +{ + struct ipc_desc *desc; + int rc; + + if (msg_len > sizeof(desc->u)) + return -GENAVB_ERR_CTRL_LEN; + + desc = ipc_alloc(tx, DEFAULT_IPC_DATA_SIZE); + if (desc) { + desc->type = msg_type; + desc->len = msg_len; + desc->flags = flags; + + os_memcpy(&desc->u, msg, msg_len); + + if (ipc_tx(tx, desc) < 0) { + rc = -GENAVB_ERR_CTRL_TX; + goto err_ipc_tx; + } + } else { + rc = -GENAVB_ERR_CTRL_ALLOC; + goto err_ipc_alloc; + } + + return GENAVB_SUCCESS; + +err_ipc_tx: + ipc_free(tx, desc); + +err_ipc_alloc: + return rc; +} + +int avb_ipc_receive(struct ipc_rx const *rx, unsigned int *msg_type, void *msg, unsigned int *msg_len) +{ + struct ipc_desc *desc; + int rc = GENAVB_SUCCESS; + + if (!msg) { + rc = -GENAVB_ERR_INVALID_PARAMS; + goto err_rx_ioctl; + } + + desc = __ipc_rx(rx); + if (!desc) { + rc = (-GENAVB_ERR_CTRL_RX); + goto err_rx_ioctl; + } + + *msg_type = desc->type; + + if (*msg_len < desc->len) { + rc = (-GENAVB_ERR_CTRL_TRUNCATED); + } else + *msg_len = desc->len; + + os_memcpy(msg, (void*)&desc->u, *msg_len); + + if (*msg_type == GENAVB_MSG_ERROR_RESPONSE) { + rc = -desc->u.error.status; + } + + ipc_free(rx, desc); + +err_rx_ioctl: + return rc; +} + +int genavb_control_close(struct genavb_control_handle *handle) +{ + if (ipc_id[handle->id][CTRL_RX_SYNC] != IPC_ID_NONE) + ipc_rx_exit(&handle->rx_sync); + + ipc_rx_exit(&handle->rx); + + ipc_tx_exit(&handle->tx); + + os_free(handle); + + return GENAVB_SUCCESS; +} + +int genavb_control_receive(struct genavb_control_handle const *handle, genavb_msg_type_t *msg_type, void *msg, unsigned int *msg_len) +{ + unsigned int __msg_type; + int rc; + + rc = avb_ipc_receive(&handle->rx, &__msg_type, msg, msg_len); + if (rc == GENAVB_SUCCESS) + *msg_type = __msg_type; + + return rc; +} + +static int avb_control_valid(struct genavb_control_handle const *handle, genavb_msg_type_t msg_type, void const *msg, unsigned int msg_len) +{ + int rc = 0; + avb_u8 inner_type; + + + switch (handle->id) { + case GENAVB_CTRL_AVDECC_MEDIA_STACK: + switch (msg_type) { + case GENAVB_MSG_MEDIA_STACK_BIND: + if (msg_len == sizeof(struct genavb_msg_media_stack_bind)) + rc = 1; + break; + + default: + break; + } + break; + case GENAVB_CTRL_AVDECC_CONTROLLED: + switch (msg_type) { + case GENAVB_MSG_AECP: + if (msg_len >= (offsetof(struct genavb_aecp_msg, buf) + sizeof(struct aecp_aem_pdu))) { + struct aecp_aem_pdu *pdu = (struct aecp_aem_pdu *)((struct genavb_aecp_msg *)msg)->buf; + avb_u16 response_type = AECP_AEM_GET_CMD_TYPE(pdu); + inner_type = ((struct genavb_aecp_msg *)msg)->msg_type; + if (inner_type == AECP_AEM_RESPONSE) { + switch (response_type) { + case AECP_AEM_CMD_SET_CONTROL: + case AECP_AEM_CMD_START_STREAMING: + case AECP_AEM_CMD_STOP_STREAMING: + case AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS: + case AECP_AEM_CMD_ADD_AUDIO_MAPPINGS: + case AECP_AEM_CMD_GET_AUDIO_MAP: + rc = 1; + break; + default: + break; + } + } + } + break; + default: + break; + } + break; + case GENAVB_CTRL_AVDECC_CONTROLLER: + switch (msg_type) { + case GENAVB_MSG_AECP: + if (msg_len >= (offsetof(struct genavb_aecp_msg, buf) + sizeof(struct aecp_aem_pdu))) { + inner_type = ((struct genavb_aecp_msg *)msg)->msg_type; + if (inner_type == AECP_AEM_COMMAND) + rc = 1; + } + break; + case GENAVB_MSG_ACMP_COMMAND: + if (msg_len >= sizeof(struct genavb_acmp_command)) { + inner_type = ((struct genavb_acmp_command *)msg)->message_type; + switch (inner_type) { + case ACMP_CONNECT_RX_COMMAND: + case ACMP_DISCONNECT_RX_COMMAND: + case ACMP_GET_TX_STATE_COMMAND: + case ACMP_GET_RX_STATE_COMMAND: + case ACMP_GET_TX_CONNECTION_COMMAND: + rc = 1; + break; + default: + break; + } + } + break; + case GENAVB_MSG_ADP: + if (msg_len >= sizeof(struct genavb_adp_msg)) { + inner_type = ((struct genavb_adp_msg *)msg)->msg_type; + switch (inner_type) { + case ADP_ENTITY_DISCOVER: + case ADP_ENTITY_AVAILABLE: + rc = 1; + break; + default: + break; + } + } + break; + + default: + break; + } + + break; + + case GENAVB_CTRL_MSRP: + case GENAVB_CTRL_MSRP_BRIDGE: + switch (msg_type) { + case GENAVB_MSG_TALKER_REGISTER: + if (msg_len == sizeof(struct genavb_msg_talker_register)) + rc = 1; + break; + + case GENAVB_MSG_TALKER_DEREGISTER: + if (msg_len == sizeof(struct genavb_msg_talker_deregister)) + rc = 1; + + break; + + case GENAVB_MSG_LISTENER_REGISTER: + if (msg_len == sizeof(struct genavb_msg_listener_register)) + rc = 1; + + break; + + case GENAVB_MSG_LISTENER_DEREGISTER: + if (msg_len == sizeof(struct genavb_msg_listener_deregister)) + rc = 1; + + break; + + case GENAVB_MSG_MANAGED_SET: + case GENAVB_MSG_MANAGED_GET: + rc = 1; + break; + + default: + break; + } + break; + + case GENAVB_CTRL_MVRP: + case GENAVB_CTRL_MVRP_BRIDGE: + switch (msg_type) { + case GENAVB_MSG_VLAN_REGISTER: + if (msg_len == sizeof(struct genavb_msg_vlan_register)) + rc = 1; + + break; + + case GENAVB_MSG_VLAN_DEREGISTER: + if (msg_len == sizeof(struct genavb_msg_vlan_deregister)) + rc = 1; + + break; + + default: + break; + } + + break; + + case GENAVB_CTRL_CLOCK_DOMAIN: + switch (msg_type) { + case GENAVB_MSG_CLOCK_DOMAIN_SET_SOURCE: + if (msg_len == sizeof(struct genavb_msg_clock_domain_set_source)) + rc = 1; + + break; + + case GENAVB_MSG_CLOCK_DOMAIN_GET_STATUS: + if (msg_len == sizeof(struct genavb_msg_clock_domain_get_status)) + rc = 1; + + break; + + default: + break; + } + + break; + + case GENAVB_CTRL_GPTP: + case GENAVB_CTRL_GPTP_BRIDGE: + switch (msg_type) { + case GENAVB_MSG_MANAGED_SET: + case GENAVB_MSG_MANAGED_GET: + rc = 1; + break; + default: + break; + } + + break; + + case GENAVB_CTRL_MAAP: + switch (msg_type) { + case GENAVB_MSG_MAAP_CREATE_RANGE: + if (msg_len == sizeof(struct genavb_msg_maap_create)) { + avb_u32 range_id = ((struct genavb_msg_maap_create *)msg)->range_id; + + if (range_id < MAAP_MAX_CTRL_ID) + rc = 1; + } + + break; + + case GENAVB_MSG_MAAP_DELETE_RANGE: + if (msg_len == sizeof(struct genavb_msg_maap_delete)) { + avb_u32 range_id = ((struct genavb_msg_maap_delete *)msg)->range_id; + + if (range_id < MAAP_MAX_CTRL_ID) + rc = 1; + } + + break; + + default: + break; + } + + default: + break; + } + + return rc; +} + +int _avb_control_send(struct genavb_control_handle const *handle, genavb_msg_type_t msg_type, void const *msg, unsigned int msg_len, unsigned int flags) +{ + if (!avb_control_valid(handle, msg_type, msg, msg_len)) + return -GENAVB_ERR_CTRL_INVALID; + + return avb_ipc_send(&handle->tx, msg_type, msg, msg_len, flags); +} + +int genavb_control_send(struct genavb_control_handle const *handle, genavb_msg_type_t msg_type, void const *msg, unsigned int msg_len) +{ + return _avb_control_send(handle, msg_type, msg, msg_len, 0); +} + +int genavb_control_send_sync(struct genavb_control_handle const *handle, genavb_msg_type_t *msg_type, void const *msg, unsigned int msg_len, void *rsp, unsigned int *rsp_len, int timeout) +{ + unsigned int __msg_type; + int rc; + + if (ipc_id[handle->id][CTRL_RX_SYNC] == IPC_ID_NONE) { + rc = -GENAVB_ERR_CTRL_INVALID; + goto exit; + } + + rc = _avb_control_send(handle, *msg_type, msg, msg_len, IPC_FLAGS_AVB_MSG_SYNC); + if (rc != GENAVB_SUCCESS) + goto exit; + + rc = avb_ipc_receive_sync(&handle->rx_sync, &__msg_type, rsp, rsp_len, timeout); + if (rc == GENAVB_SUCCESS) + *msg_type = __msg_type; + +exit: + return rc; +} diff --git a/api/control.h b/api/control.h new file mode 100644 index 0000000..a4569f0 --- /dev/null +++ b/api/control.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file control.h + \brief GenAVB API private control API includes + \details private definitions for the GenAVB library control API + + \copyright Copyright 2018-2021, 2023 NXP +*/ + +#ifndef _PRIVATE_CONTROL_H_ +#define _PRIVATE_CONTROL_H_ + +#include "common/ipc.h" + +#include "api_os/control.h" + +#define CTRL_TX 0 +#define CTRL_RX 1 +#define CTRL_RX_SYNC 2 + +extern const ipc_id_t ipc_id[GENAVB_CTRL_ID_MAX][3]; + +int _avb_control_send(struct genavb_control_handle const *handle, genavb_msg_type_t msg_type, void const *msg, unsigned int msg_len, unsigned int flags); + +int avb_ipc_send(const struct ipc_tx *tx, genavb_msg_type_t msg_type, void const *msg, unsigned int msg_len, unsigned int flags); + +int avb_ipc_receive(struct ipc_rx const *rx, unsigned int *msg_type, void *msg, unsigned int *msg_len); + +int avb_ipc_receive_sync(struct ipc_rx const *rx, unsigned int *msg_type, void *msg, unsigned int *msg_len, int timeout); + +int send_heartbeat(struct ipc_tx *tx, struct ipc_rx *rx, unsigned int flags); + +#endif /* _PRIVATE_CONTROL_H_ */ diff --git a/api/error.c b/api/error.c new file mode 100644 index 0000000..ed32f1a --- /dev/null +++ b/api/error.c @@ -0,0 +1,185 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file error.c + \brief GenAVB public API + \details API definition for the GenAVB library + + \copyright Copyright 2014-2016 Freescale Semiconductor, Inc. + \copyright Copyright 2016-2021, 2023-2024 NXP +*/ + +#include "genavb/error.h" + +#define __ERR_NUM(err) [GENAVB_ERR_NUM(err)] + +static const char *genavb_GENERAL_error[] = { + __ERR_NUM(GENAVB_SUCCESS) = "Success", + __ERR_NUM(GENAVB_ERR_NO_MEMORY) = "Out of memory", + __ERR_NUM(GENAVB_ERR_ALREADY_INITIALIZED) = "Library already initialized", + __ERR_NUM(GENAVB_ERR_INVALID) = "Invalid parameters", + __ERR_NUM(GENAVB_ERR_INVALID_PARAMS) = "Invalid library handle", + __ERR_NUM(GENAVB_ERR_INVALID_PORT) = "Invalid port", + __ERR_NUM(GENAVB_ERR_NOT_SUPPORTED) = "Feature not supported", +}; + +static const char *genavb_STREAM_error[] = { + __ERR_NUM(GENAVB_ERR_STREAM_API_OPEN) = "Stream open error", + __ERR_NUM(GENAVB_ERR_STREAM_BIND) = "Stream bind error", + __ERR_NUM(GENAVB_ERR_STREAM_TX) = "Stream data write error", + __ERR_NUM(GENAVB_ERR_STREAM_RX) = "Stream data read error", + __ERR_NUM(GENAVB_ERR_STREAM_INVALID) = "Stream handle invalid", + __ERR_NUM(GENAVB_ERR_STREAM_PARAMS) = "Invalid stream parameters", + __ERR_NUM(GENAVB_ERR_STREAM_TX_NOT_ENOUGH_DATA) = "Stream data not enough write error", + __ERR_NUM(GENAVB_ERR_STREAM_NO_CALLBACK) = "Stream callback not set", +}; + +static const char *genavb_CTRL_error[] = { + __ERR_NUM(GENAVB_ERR_CTRL_TRUNCATED) = "Control message truncated", + __ERR_NUM(GENAVB_ERR_CTRL_INIT) = "Control init error", + __ERR_NUM(GENAVB_ERR_CTRL_ALLOC) = "Control message allocation error", + __ERR_NUM(GENAVB_ERR_CTRL_TX) = "Control message write error", + __ERR_NUM(GENAVB_ERR_CTRL_RX) = "Control message read error", + __ERR_NUM(GENAVB_ERR_CTRL_LEN) = "Invalid control message length", + __ERR_NUM(GENAVB_ERR_CTRL_TIMEOUT) = "Control message timeout", + __ERR_NUM(GENAVB_ERR_CTRL_INVALID) = "Control message incompatible with control channel", + __ERR_NUM(GENAVB_ERR_CTRL_FAILED) = "Control command failed", + __ERR_NUM(GENAVB_ERR_CTRL_UNKNOWN) = "Unknown control command", + __ERR_NUM(GENAVB_ERR_STACK_NOT_READY) = "Stack not ready", + __ERR_NUM(GENAVB_ERR_PTP_DOMAIN_INVALID) = "Invalid gPTP domain", +}; + +static const char *genavb_SOCKET_error[] = { + __ERR_NUM(GENAVB_ERR_SOCKET_INIT) = "Socket open error", + __ERR_NUM(GENAVB_ERR_SOCKET_PARAMS) = "Socket parameters invalid", + __ERR_NUM(GENAVB_ERR_SOCKET_AGAIN) = "Socket no data available", + __ERR_NUM(GENAVB_ERR_SOCKET_INVALID) = "Socket parameters invalid", + __ERR_NUM(GENAVB_ERR_SOCKET_FAULT) = "Socket invalid address", + __ERR_NUM(GENAVB_ERR_SOCKET_INTR) = "Socket blocking rx interrupted", + __ERR_NUM(GENAVB_ERR_SOCKET_TX) = "Socket transmit error", + __ERR_NUM(GENAVB_ERR_SOCKET_BUFLEN) = "Socket buffer length error", +}; + +static const char *genavb_CLOCK_error[] = { + __ERR_NUM(GENAVB_ERR_CLOCK) = "Clock error", +}; + +static const char *genavb_TIMER_error[] = { + __ERR_NUM(GENAVB_ERR_TIMER) = "Timer error", +}; + +static const char *genavb_ST_error[] = { + __ERR_NUM(GENAVB_ERR_ST) = "Scheduled Traffic config error", + __ERR_NUM(GENAVB_ERR_ST_NOT_SUPPORTED) = "Scheduled Traffic not supported error", + __ERR_NUM(GENAVB_ERR_ST_HW_CONFIG) = "Scheduled Traffic hardware config error", + __ERR_NUM(GENAVB_ERR_ST_MAX_SDU_NOT_SUPPORTED) = "Scheduled Traffic max SDU config error", +}; + +static const char *genavb_SF_error[] = { + __ERR_NUM(GENAVB_ERR_SF_NOT_SUPPORTED) = "Stream Filter not supported", +}; + +static const char *genavb_SG_error[] = { + __ERR_NUM(GENAVB_ERR_SG_NOT_SUPPORTED) = "Stream Gate not supported", + __ERR_NUM(GENAVB_ERR_SG_INVALID_CYCLE_PARAMS) = "Stream Gate invalid cycle parameters", + __ERR_NUM(GENAVB_ERR_SG_INVALID_CYCLE_TIME) = "Stream Gate invalid cycle time", + __ERR_NUM(GENAVB_ERR_SG_INVALID_BASETIME) = "Stream Gate invalid base time", + __ERR_NUM(GENAVB_ERR_SG_GETTIME) = "Stream Gate gettime error", + __ERR_NUM(GENAVB_ERR_SG_ADD) = "Stream Gate add entry error", + __ERR_NUM(GENAVB_ERR_SG_UPDATE) = "Stream Gate update entry error", + __ERR_NUM(GENAVB_ERR_SG_DELETE) = "Stream Gate delete entry error", + __ERR_NUM(GENAVB_ERR_SG_MAX_ENTRIES) = "Stream Gate invalid entry id", + __ERR_NUM(GENAVB_ERR_SG_ENTRY_NOT_FOUND) = "Stream Gate entry not found", + __ERR_NUM(GENAVB_ERR_SG_GET_ENTRY) = "Stream Gate get entry error", + __ERR_NUM(GENAVB_ERR_SG_GET_STATE_ENTRY) = "Stream Gate State get entry error", + __ERR_NUM(GENAVB_ERR_SG_LIST_ADD) = "Stream Gate List add entry error", + __ERR_NUM(GENAVB_ERR_SG_LIST_DELETE_ADMIN) = "Stream Gate List delete admin entry error", + __ERR_NUM(GENAVB_ERR_SG_LIST_DELETE_OPER) = "Stream Gate List delete oper entry error", + __ERR_NUM(GENAVB_ERR_SG_LIST_ENTRY_NOT_FOUND) = "Stream Gate List entry not found", + __ERR_NUM(GENAVB_ERR_SG_LIST_GET_ADMIN_ENTRY) = "Stream Gate State get admin entry error", + __ERR_NUM(GENAVB_ERR_SG_LIST_GET_OPER_ENTRY) = "Stream Gate State get oper entry error", + __ERR_NUM(GENAVB_ERR_SG_LIST_ENTRY_ALLOC_EID) = "Stream Gate List can not be allocated", + __ERR_NUM(GENAVB_ERR_SG_RESET_IRX_OEX) = "Stream Gate reset IRX OEX error", +}; + +static const char *genavb_FM_error[] = { + __ERR_NUM(GENAVB_ERR_FM_NOT_SUPPORTED) = "Flow Meter not supported", + __ERR_NUM(GENAVB_ERR_FM_ADD) = "Flow Meter add entry error", + __ERR_NUM(GENAVB_ERR_FM_UPDATE) = "Flow Meter update entry error", + __ERR_NUM(GENAVB_ERR_FM_DELETE) = "Flow Meter delete entry error", + __ERR_NUM(GENAVB_ERR_FM_MAX_ENTRIES) = "Flow Meter invalid entry id", + __ERR_NUM(GENAVB_ERR_FM_ENTRY_NOT_FOUND) = "Flow Meter entry not found", +}; + +static const char *genavb_VLAN_error[] = { + __ERR_NUM(GENAVB_ERR_VLAN_VID) = "Invalid VID number", + __ERR_NUM(GENAVB_ERR_VLAN_CONTROL) = "Invalid VLAN control field", + __ERR_NUM(GENAVB_ERR_VLAN_NOT_FOUND) = "VLAN entry not found", + __ERR_NUM(GENAVB_ERR_VLAN_HW_CONFIG) = "VLAN hardware config error", + __ERR_NUM(GENAVB_ERR_VLAN_DEFAULT_NOT_SUPPORTED) = "VLAN port default (PVID) not supported" +}; + +static const char *genavb_FRER_error[] = { + __ERR_NUM(GENAVB_ERR_FRER_NOT_SUPPORTED) = "FRER not supported", + __ERR_NUM(GENAVB_ERR_FRER_MAX_ENTRIES) = "FRER invalid entry id", + __ERR_NUM(GENAVB_ERR_FRER_ENTRY_NOT_FOUND) = "FRER entry not found", + __ERR_NUM(GENAVB_ERR_FRER_MAX_STREAM) = "FRER stream number invalid", + __ERR_NUM(GENAVB_ERR_FRER_PARAMS) = "FRER parameters invalid", + __ERR_NUM(GENAVB_ERR_FRER_ENTRY_USED) = "FRER entry already used", + __ERR_NUM(GENAVB_ERR_FRER_HW_CONFIG) = "FRER hardware config error", + __ERR_NUM(GENAVB_ERR_FRER_HW_READ) = "FRER hardware read error", +}; + +static const char *genavb_DSA_error[] = { + __ERR_NUM(GENAVB_ERR_DSA_NOT_SUPPORTED) = "DSA not supported", + __ERR_NUM(GENAVB_ERR_DSA_NOT_FOUND) = "DSA hardware table entry not found", + __ERR_NUM(GENAVB_ERR_DSA_HW_CONFIG) = "DSA hardware table config error", +}; + +#define GENAVB_ERR_TYPE_STRINGS(type) \ + [GENAVB_ERR_TYPE_ ## type] = { \ + .str = genavb_ ## type ## _error, \ + .num_max = sizeof(genavb_ ## type ## _error), \ + } + +static const struct { + const char **str; + unsigned int num_max; +} genavb_error_type_strings[GENAVB_ERR_TYPE_MAX] = { + GENAVB_ERR_TYPE_STRINGS(GENERAL), + GENAVB_ERR_TYPE_STRINGS(STREAM), + GENAVB_ERR_TYPE_STRINGS(CTRL), + GENAVB_ERR_TYPE_STRINGS(SOCKET), + GENAVB_ERR_TYPE_STRINGS(CLOCK), + GENAVB_ERR_TYPE_STRINGS(TIMER), + GENAVB_ERR_TYPE_STRINGS(ST), + GENAVB_ERR_TYPE_STRINGS(SF), + GENAVB_ERR_TYPE_STRINGS(SG), + GENAVB_ERR_TYPE_STRINGS(FM), + GENAVB_ERR_TYPE_STRINGS(VLAN), + GENAVB_ERR_TYPE_STRINGS(FRER), + GENAVB_ERR_TYPE_STRINGS(DSA), +}; + +const char *genavb_strerror(int error) +{ + unsigned int err, type, num; + + if (error < 0) + err = -error; + else + err = error; + + type = GENAVB_ERR_TYPE(err); + num = GENAVB_ERR_NUM(err); + + if ((type < GENAVB_ERR_TYPE_MAX) && (num < genavb_error_type_strings[type].num_max)) + return genavb_error_type_strings[type].str[num]; + else + return "Unknown error code"; +} diff --git a/api/init.h b/api/init.h new file mode 100644 index 0000000..bcf9f1c --- /dev/null +++ b/api/init.h @@ -0,0 +1,23 @@ +/* + * Copyright 2018, 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file init.h + \brief GenAVB API private initialization API includes + \details private definitions for the GenAVB library initialization API + + \copyright Copyright 2018, 2020-2021, 2023 NXP +*/ + +#ifndef _PRIVATE_INIT_H_ +#define _PRIVATE_INIT_H_ + +#include "api_os/init.h" + +#define AVTP_INITIALIZED (1 << 20) +#define HSR_INITIALIZED (1 << 19) + +#endif /* _PRIVATE_INIT_H_ */ diff --git a/api/linux/api.cmake b/api/linux/api.cmake new file mode 100644 index 0000000..0ed6af5 --- /dev/null +++ b/api/linux/api.cmake @@ -0,0 +1,22 @@ + +genavb_target_add_srcs(TARGET genavb + SRCS + init.c + control.c + ) + +if(CONFIG_AVTP) + genavb_target_add_srcs(TARGET genavb + SRCS + streaming.c + ) +endif() + +if(CONFIG_SOCKET) + genavb_target_add_srcs(TARGET genavb + SRCS + socket.c + ) +endif() + +genavb_target_add_linker_script(TARGET genavb LINKER_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/apis.map") diff --git a/api/linux/api_os/control.h b/api/linux/api_os/control.h new file mode 100644 index 0000000..6484052 --- /dev/null +++ b/api/linux/api_os/control.h @@ -0,0 +1,27 @@ +/* + * Copyright 2018, 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file control.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2018, 2020-2021, 2023 NXP +*/ + +#ifndef _LINUX_PRIVATE_CONTROL_H_ +#define _LINUX_PRIVATE_CONTROL_H_ + +#include "common/ipc.h" + +struct genavb_control_handle { + int id; + struct ipc_tx tx; + struct ipc_rx rx; + struct ipc_rx rx_sync; +}; + +#endif /* _LINUX_PRIVATE_CONTROL_H_ */ diff --git a/api/linux/api_os/init.h b/api/linux/api_os/init.h new file mode 100644 index 0000000..01925f6 --- /dev/null +++ b/api/linux/api_os/init.h @@ -0,0 +1,28 @@ +/* + * Copyright 2018, 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file init.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2018, 2020-2021, 2023 NXP +*/ + +#ifndef _LINUX_PRIVATE_INIT_H_ +#define _LINUX_PRIVATE_INIT_H_ + +#include "common/ipc.h" +#include "common/list.h" + +struct genavb_handle { + int flags; + struct list_head streams; + struct ipc_tx avtp_tx; + struct ipc_rx avtp_rx; +}; + +#endif /* _LINUX_PRIVATE_INIT_H_ */ diff --git a/api/linux/api_os/streaming.h b/api/linux/api_os/streaming.h new file mode 100644 index 0000000..5d9272a --- /dev/null +++ b/api/linux/api_os/streaming.h @@ -0,0 +1,34 @@ +/* + * Copyright 2018, 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file streaming.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2018, 2020-2021, 2023 NXP +*/ + +#ifndef _LINUX_PRIVATE_STREAMING_H_ +#define _LINUX_PRIVATE_STREAMING_H_ + +#include "common/list.h" +#include "include/genavb/genavb.h" + +#include "init.h" + +struct genavb_stream_handle { + struct list_head list; + int fd; + struct genavb_handle *genavb; + struct genavb_stream_params params; + unsigned int max_payload_size; /* Transmit maximum packet payload size (in byte units) */ + unsigned int partial_iovec; + int expect_new_frame; + unsigned int batch; /* Transmit batch (in packet units) */ +}; + +#endif /* _LINUX_PRIVATE_STREAMING_H_ */ diff --git a/api/linux/apis.map b/api/linux/apis.map new file mode 100644 index 0000000..26d29b5 --- /dev/null +++ b/api/linux/apis.map @@ -0,0 +1,48 @@ +{ + global: + genavb_init; + genavb_exit; + genavb_stream_create; + genavb_stream_destroy; + genavb_stream_receive; + genavb_stream_receive_iov; + genavb_stream_send; + genavb_stream_send_iov; + genavb_stream_h264_send; + genavb_stream_fd; + genavb_stream_presentation_offset; + genavb_strerror; + genavb_control_open; + genavb_control_close; + genavb_control_send; + genavb_control_send_sync; + genavb_control_receive; + genavb_control_rx_fd; + genavb_control_tx_fd; + genavb_socket_rx_open; + genavb_socket_tx_open; + genavb_socket_rx_fd; + genavb_socket_tx_fd; + genavb_socket_rx; + genavb_socket_tx; + genavb_socket_rx_close; + genavb_socket_tx_close; + genavb_clock_gettime64; + avdecc_fmt_sample_rate; + avdecc_fmt_samples_per_packet; + avdecc_fmt_hdr_size; + __avdecc_fmt_payload_size; + sr_class_interval_p; + sr_class_interval_q; + sr_class_max_transit_time; + sr_class_max_timing_uncertainty; + sr_class_pcp; + h_strncpy; + h_strncpy_strict; + h_strtoul; + h_strtoull; + h_strtol; + h_snprintf_strict; + h_snprintf; + local: *; +}; diff --git a/api/linux/control.c b/api/linux/control.c new file mode 100644 index 0000000..30057ba --- /dev/null +++ b/api/linux/control.c @@ -0,0 +1,167 @@ +/* + * Copyright 2018-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file control.c + \brief GenAVB public API for linux + \details API definition for the GenAVB library + \copyright Copyright 2018-2021, 2023 NXP +*/ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include + +#include "common/ipc.h" + +#include "api/control.h" + +int avb_ipc_receive_sync(struct ipc_rx const *rx, unsigned int *msg_type, void *msg, unsigned int *msg_len, int timeout) +{ + struct pollfd sync_poll; + int rc; + + sync_poll.fd = rx->fd; + sync_poll.events = POLLIN; + rc = poll(&sync_poll, 1, timeout); + while (rc == -1) { + if (errno == EINTR) + rc = poll(&sync_poll, 1, timeout); + else { + rc = -GENAVB_ERR_CTRL_RX; + goto exit; + } + } + + if (rc != 0) { + if (sync_poll.revents & POLLIN) + rc = avb_ipc_receive(rx, msg_type, msg, msg_len); + else + rc = -GENAVB_ERR_CTRL_RX; + } else + rc = -GENAVB_ERR_CTRL_TIMEOUT; + +exit: + return rc; +} + +int genavb_control_rx_fd(struct genavb_control_handle const *handle) +{ + return handle->rx.fd; +} + +int genavb_control_tx_fd(struct genavb_control_handle const *handle) +{ + return handle->tx.fd; +} + +int genavb_control_open(struct genavb_handle const *genavb, struct genavb_control_handle **control, genavb_control_id_t id) +{ + int rc; + + /* + * allocate new stream + */ + + if (id >= GENAVB_CTRL_ID_MAX) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_invalid_id; + } + + *control = malloc(sizeof(struct genavb_control_handle)); + if (!*control) { + rc = (-GENAVB_ERR_NO_MEMORY); + goto err_alloc; + } + memset((*control), 0, sizeof(struct genavb_control_handle)); + + (*control)->id = id; + + if (ipc_rx_init_no_notify(&(*control)->rx, ipc_id[id][CTRL_RX]) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto error_control_rx_init; + } + + if (ipc_tx_init(&(*control)->tx, ipc_id[id][CTRL_TX]) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto error_control_tx_init; + } + + if (ipc_id[id][CTRL_RX_SYNC] != IPC_ID_NONE) { + if (ipc_rx_init_no_notify(&(*control)->rx_sync, ipc_id[id][CTRL_RX_SYNC]) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto error_control_rx_sync_init; + } + } + + switch (id) { + case GENAVB_CTRL_AVDECC_MEDIA_STACK: + case GENAVB_CTRL_AVDECC_CONTROLLED: + if (send_heartbeat(&(*control)->tx, &(*control)->rx, 0) < 0) { + rc = -GENAVB_ERR_STACK_NOT_READY; + goto err_tx_heartbeat; + } + + break; + + case GENAVB_CTRL_AVDECC_CONTROLLER: + if (send_heartbeat(&(*control)->tx, &(*control)->rx_sync, IPC_FLAGS_AVB_MSG_SYNC) < 0) { + rc = -GENAVB_ERR_STACK_NOT_READY; + goto err_tx_heartbeat; + } + + break; + + case GENAVB_CTRL_MSRP: + case GENAVB_CTRL_MSRP_BRIDGE: + case GENAVB_CTRL_MVRP: + case GENAVB_CTRL_MVRP_BRIDGE: + case GENAVB_CTRL_GPTP: + case GENAVB_CTRL_GPTP_BRIDGE: + case GENAVB_CTRL_CLOCK_DOMAIN: + case GENAVB_CTRL_MAAP: + if (ipc_tx_connect(&(*control)->tx, &(*control)->rx) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_connect; + } + + if (ipc_tx_connect(&(*control)->tx, &(*control)->rx_sync) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_connect; + } + + break; + + default: + break; + } + + return GENAVB_SUCCESS; + +err_connect: +err_tx_heartbeat: + if (ipc_id[id][CTRL_RX_SYNC] != IPC_ID_NONE) + ipc_rx_exit(&(*control)->rx_sync); + +error_control_rx_sync_init: + ipc_tx_exit(&(*control)->tx); + +error_control_tx_init: + ipc_rx_exit(&(*control)->rx); + +error_control_rx_init: + free(*control); + *control = NULL; + +err_alloc: +err_invalid_id: + return rc; +} diff --git a/api/linux/init.c b/api/linux/init.c new file mode 100644 index 0000000..4c69a3e --- /dev/null +++ b/api/linux/init.c @@ -0,0 +1,139 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2018-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file init.c + \brief GenAVB public initialization API for linux + \details API definition for the GenAVB library + \copyright Copyright 2014 Freescale Semiconductor, Inc. + \copyright Copyright 2018-2023 NXP +*/ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include + +#include "linux/init.h" + +#include "common/log.h" +#include "common/ipc.h" + +#include "api/init.h" + +#ifdef CONFIG_AVTP +void streaming_exit(struct genavb_handle *genavb); +#endif + +pthread_mutex_t avb_mutex = PTHREAD_MUTEX_INITIALIZER; +struct genavb_handle *genavb_handle = NULL; + + +int genavb_init(struct genavb_handle **genavb, unsigned int flags) +{ + struct os_net_config net_config = { .api_sockets_net_mode = CONFIG_API_SOCKETS_DEFAULT_NET }; + int rc; + + pthread_mutex_lock(&avb_mutex); + + if (genavb_handle) { + rc = -GENAVB_ERR_ALREADY_INITIALIZED; + goto err_genavb_handle; + } + + *genavb = malloc(sizeof(struct genavb_handle)); + if (!*genavb) { + rc = -GENAVB_ERR_NO_MEMORY; + goto err_alloc; + } + + memset(*genavb, 0, sizeof(struct genavb_handle)); + + (*genavb)->flags = flags; + (*genavb)->flags &= ~AVTP_INITIALIZED; + + list_head_init(&(*genavb)->streams); + + /* reduce stack debug print */ + log_level_set(api_COMPONENT_ID, LOG_CRIT); + + if (flags & GENAVB_FLAGS_NET_MASK) { + switch (flags & GENAVB_FLAGS_NET_MASK) { + case GENAVB_FLAGS_NET_STD: + net_config.api_sockets_net_mode = NET_STD; + break; + case GENAVB_FLAGS_NET_XDP: + net_config.api_sockets_net_mode = NET_XDP; + break; + default: + rc = -GENAVB_ERR_INVALID_PARAMS; + goto err_net_mode; + } + } + + if (os_init(&net_config) < 0) { + rc = -GENAVB_ERR_INVALID_PARAMS; + goto err_osal; + } + + genavb_handle = *genavb; + + pthread_mutex_unlock(&avb_mutex); + + return GENAVB_SUCCESS; + +err_osal: +err_net_mode: + free(*genavb); + *genavb = NULL; +err_alloc: +err_genavb_handle: + pthread_mutex_unlock(&avb_mutex); + + return rc; +} + +int genavb_exit(struct genavb_handle *genavb) +{ + int rc; + + if (!genavb) { + rc = -GENAVB_ERR_INVALID; + goto err_genavb_handle_null; + } + + pthread_mutex_lock(&avb_mutex); + + if (genavb_handle != genavb) { + rc = -GENAVB_ERR_INVALID; + goto err_genavb_handle_invalid; + } + + os_exit(); + + genavb_handle = NULL; + +#ifdef CONFIG_AVTP + streaming_exit(genavb); +#endif + + pthread_mutex_unlock(&avb_mutex); + + free(genavb); + + return GENAVB_SUCCESS; + +err_genavb_handle_invalid: + pthread_mutex_unlock(&avb_mutex); + +err_genavb_handle_null: + return rc; +} diff --git a/api/linux/socket.c b/api/linux/socket.c new file mode 100644 index 0000000..2c341fa --- /dev/null +++ b/api/linux/socket.c @@ -0,0 +1,57 @@ +/* + * Copyright 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file socket.c + \brief control public API for linux + \details + \copyright Copyright 2020-2021, 2023 NXP +*/ + +#define _POSIX_C_SOURCE 200809L + +#include "genavb/error.h" +#include "os/stdlib.h" +#include "api/socket.h" + +int socket_rx_event_init(struct genavb_socket_rx *sock) +{ + if (sock->flags & GENAVB_SOCKF_NONBLOCK) { + sock->priv = -1; + return 0; + } else { + return -1; + } +} + +void socket_rx_event_exit(struct genavb_socket_rx *sock) +{ +} + +int socket_rx_event_check(struct genavb_socket_rx *sock) +{ + return 0; +} + +void socket_rx_event_rearm(struct genavb_socket_rx *sock) +{ +} + +int genavb_socket_rx_fd(struct genavb_socket_rx *sock) +{ + if (sock->net.fd < 0) + return -GENAVB_ERR_SOCKET_INVALID; + + return sock->net.fd; +} + +int genavb_socket_tx_fd(struct genavb_socket_tx *sock) +{ + if (sock->net.fd < 0) + return -GENAVB_ERR_SOCKET_INVALID; + + return sock->net.fd; +} diff --git a/api/linux/streaming.c b/api/linux/streaming.c new file mode 100644 index 0000000..a6e2a68 --- /dev/null +++ b/api/linux/streaming.c @@ -0,0 +1,723 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file streaming.c + \brief GenAVB public API for linux + \details API definition for the GenAVB library + \copyright Copyright 2014-2016 Freescale Semiconductor, Inc. + \copyright Copyright 2016-2021, 2023 NXP +*/ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include + +#include "modules/media.h" + +#include "common/ipc.h" +#include "common/avdecc.h" +#include "common/avtp.h" +#include "common/cvf.h" + +#include "api/streaming.h" +#include "api/control.h" + +#include "genavb/streaming.h" + +#define MEDIA_QUEUE_API_FILE "/dev/media_queue_api" +#define API_SYNC_POLL_TIMEOUT 1000 + +extern pthread_mutex_t avb_mutex; +extern struct genavb_handle *genavb_handle; + +int __avb_stream_destroy(struct genavb_stream_handle *handle) +{ + disconnect_avtp(handle->genavb, &handle->params); + + if (handle->fd >= 0) + close(handle->fd); + + list_del(&handle->list); + + free(handle); + + return GENAVB_SUCCESS; +} + + +int streaming_init(struct genavb_handle *genavb) +{ + int rc; + + /* + * setup media stack to avtp ipc + */ + if (ipc_tx_init(&genavb->avtp_tx, IPC_MEDIA_STACK_AVTP) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err_ipc_tx_init; + } + + if (ipc_rx_init_no_notify(&genavb->avtp_rx, IPC_AVTP_MEDIA_STACK) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err_ipc_rx_init; + } + + if (ipc_tx_connect(&genavb->avtp_tx, &genavb->avtp_rx) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err_ipc_tx_connect; + } + + /* We don't wait for a reply HEARTBEAT message here: because of current limitations of the IPC implementations (one single + * reader per IPC), we cannot open the RX channel here, as we want to open it only for applications doing stream create/destroy. + */ + if (send_heartbeat(&genavb->avtp_tx, NULL, 0) < 0) { + rc = -GENAVB_ERR_STACK_NOT_READY; + goto err_tx_heartbeat; + } + + return GENAVB_SUCCESS; + +err_tx_heartbeat: +err_ipc_tx_connect: + ipc_rx_exit(&genavb->avtp_rx); + +err_ipc_rx_init: + ipc_tx_exit(&genavb->avtp_tx); + +err_ipc_tx_init: + return rc; +} + +void streaming_exit(struct genavb_handle *genavb) +{ + struct list_head *entry, *next; + struct genavb_stream_handle *stream; + + for (entry = list_first(&genavb->streams); next = list_next(entry), entry != &genavb->streams; entry = next) { + stream = container_of(entry, struct genavb_stream_handle, list); + __avb_stream_destroy(stream); + } + + if (genavb->flags & AVTP_INITIALIZED) { + ipc_rx_exit(&genavb->avtp_rx); + + ipc_tx_exit(&genavb->avtp_tx); + } +} + +static unsigned int avtp_fmt_sample_stride(unsigned int subtype, const struct avdecc_format *format) +{ + unsigned int sample_stride = 0; + + switch (subtype) { + case AVTP_SUBTYPE_NTSCF: /* not avdecc defined format */ + sample_stride = 1; + break; + default: + sample_stride = avdecc_fmt_sample_stride(format); + break; + } + + return sample_stride; +} + +static int avtp_subtype_mode_check(unsigned int flags, unsigned int subtype) +{ + int rc = 0; + + /* TSCF and NTSF only supported in Datagram mode */ + if ((flags & AVTP_DGRAM) && (subtype != AVTP_SUBTYPE_TSCF) && (subtype != AVTP_SUBTYPE_NTSCF)) + rc = -1; + + /* Datagram mode only supports TSCF and NTSCF */ + if (!(flags & AVTP_DGRAM) && ((subtype == AVTP_SUBTYPE_TSCF) || (subtype == AVTP_SUBTYPE_NTSCF))) + rc = -1; + + return rc; +} + +int genavb_stream_create(struct genavb_handle *genavb, struct genavb_stream_handle **stream, struct genavb_stream_params const *params, + unsigned int *batch_size, genavb_stream_create_flags_t flags) +{ + struct media_queue_api_params msg; + int fd, rc; + + if (!genavb) { + rc = -GENAVB_ERR_INVALID; + goto err_genavb_handle_null; + } + + pthread_mutex_lock(&avb_mutex); + + if (genavb_handle != genavb) { + rc = -GENAVB_ERR_INVALID; + goto err_genavb_handle_invalid; + } + + /* + * allocate new stream + */ + *stream = malloc(sizeof(struct genavb_stream_handle)); + if (!*stream) { + rc = -GENAVB_ERR_NO_MEMORY; + goto err_alloc; + } + memset(*stream, 0, sizeof(struct genavb_stream_handle)); + + memcpy(&(*stream)->params, params, sizeof(struct genavb_stream_params)); + + /* + * connect avtp + */ + rc = connect_avtp(genavb, *stream); + if (rc != GENAVB_SUCCESS) { + goto err_connect; + } + + if (params->subtype == AVTP_SUBTYPE_CRF) { + fd = -1; + *batch_size = 0; + } else { + /* + * open device file and attach to the stream + */ + if (params->direction == AVTP_DIRECTION_LISTENER) + fd = open(MEDIA_QUEUE_API_FILE, O_RDONLY | O_CLOEXEC); + else + fd = open(MEDIA_QUEUE_API_FILE, O_WRONLY | O_CLOEXEC); + + if (fd < 0) { + rc = -GENAVB_ERR_STREAM_API_OPEN; + goto err_open; + } + + if (avtp_subtype_mode_check(flags, params->subtype) < 0) { + rc = -GENAVB_ERR_INVALID_PARAMS; + goto err_subtype_mode; + } + + if (params->direction == AVTP_DIRECTION_TALKER) { + if (flags & AVTP_DGRAM) { + msg.max_payload_size = (*stream)->max_payload_size; + } else { + /* Align batch size to AVTP thread batch size */ + unsigned int min_batch_size = (*stream)->batch * (*stream)->max_payload_size; + + if (*batch_size > min_batch_size) + *batch_size = (*batch_size / min_batch_size) * min_batch_size; + + msg.max_payload_size = (*stream)->max_payload_size; + } + } else { + if (flags & AVTP_DGRAM) + msg.max_payload_size = (*stream)->max_payload_size; + else + msg.max_payload_size = avdecc_fmt_payload_size(¶ms->format, params->stream_class); + } + + /* + * bind file descriptor and stream id + */ + msg.port = params->port; + copy_64(msg.stream_id, ¶ms->stream_id); + msg.batch_size = *batch_size; + msg.frame_stride = avtp_fmt_sample_stride(params->subtype, ¶ms->format); + msg.frame_size = avtp_fmt_sample_size(params->subtype, ¶ms->format); + msg.queue_size = 0; + msg.flags = flags; + + if (ioctl(fd, MEDIA_IOC_API_BIND, &msg) < 0) { + rc = -GENAVB_ERR_STREAM_BIND; + goto err_ioctl; + } + + *batch_size = msg.batch_size; + (*stream)->max_payload_size = msg.max_payload_size; + (*stream)->expect_new_frame = 1; + + if ((params->direction == AVTP_DIRECTION_TALKER) && + (avdecc_format_is_cvf_h264(¶ms->format)) && + (msg.max_payload_size < FU_HEADER_SIZE)) { + + rc = -GENAVB_ERR_STREAM_BIND; + goto err_ioctl; + } + } + + (*stream)->fd = fd; + + + (*stream)->genavb = genavb; + + list_add(&genavb->streams, &(*stream)->list); + + pthread_mutex_unlock(&avb_mutex); + + return GENAVB_SUCCESS; + +err_ioctl: +err_subtype_mode: + if (fd >= 0) + close(fd); + +err_open: + disconnect_avtp(genavb, params); + +err_connect: + free(*stream); + +err_alloc: +err_genavb_handle_invalid: + pthread_mutex_unlock(&avb_mutex); + +err_genavb_handle_null: + *stream = NULL; + + return rc; +} + + +int genavb_stream_fd(struct genavb_stream_handle const *handle) +{ + if (handle->fd < 0) + return -GENAVB_ERR_STREAM_INVALID; + + return handle->fd; +} + + +int genavb_stream_receive(struct genavb_stream_handle const *handle, void *data, unsigned int data_len, + struct genavb_event *event, unsigned int *event_len) +{ + struct genavb_iovec data_iov, event_iov; + + data_iov.iov_base = data; + data_iov.iov_len = data_len; + + if (event && event_len) { + event_iov.iov_base = event; + event_iov.iov_len = *event_len; + + return genavb_stream_receive_iov(handle, &data_iov, 1, &event_iov, 1, event_len); + } else + return genavb_stream_receive_iov(handle, &data_iov, 1, NULL, 0, NULL); +} + + +int genavb_stream_receive_iov(struct genavb_stream_handle const *handle, struct genavb_iovec const *data_iov, unsigned int data_iov_len, + struct genavb_iovec const *event_iov, unsigned int event_iov_len, unsigned int *event_len) +{ + struct media_queue_rx msg; + int rc = GENAVB_SUCCESS; + + if (event_iov && event_iov_len && !event_len) + return (-GENAVB_ERR_STREAM_RX); + + msg.data_iov_len = data_iov_len; + msg.data_iov = data_iov; + + msg.event_iov_len = event_iov_len; + msg.event_iov = event_iov; + + if (ioctl(handle->fd, MEDIA_IOC_RX, &msg) < 0) + rc = (-GENAVB_ERR_STREAM_RX); + else + rc = msg.data_read; + + if (event_len) + *event_len = msg.event_read; + + return rc; +} + + +/** Checks for the start code in a H264 ByteStream and return its length + * + * Start Code Prefix can be 0x0.0x0.0x0.0x1 or 0x0.0x0.0x1 + * + * \return lenght of the start code (3 or 4) or 0 if not found + * \param buf pointer to NALUto parse + * \param len length ofthe NALU to parse + */ +static unsigned int _h264_check_start_code(const u8 *buf, unsigned int len) +{ + unsigned int ret = 0; + + if (len < 4) + return ret; + + if ((buf[0] == 0x0) && (buf[1] == 0x0)) { + if(buf[2] == 0x1) + ret = 3; + else if ((buf[2] == 0x0) && buf[3] == 0x1) + ret = 4; + else + ret = 0; + } + else + ret = 0; + + return ret; + +} + +int genavb_stream_h264_send(struct genavb_stream_handle *handle, void *data, unsigned int data_len, + struct genavb_event *event, unsigned int event_len) +{ + struct genavb_iovec data_iov[IOV_MAX + 1] = { {NULL, 0} }; + + unsigned int start_code; + unsigned int max_fu_payload_size; + unsigned int iov_idx = 0; + int rc; + unsigned int first_fu = 0; + u16 fu_header[IOV_MAX] = { 0xdead }; /*At max every two iovec will be an FU HEADER of 2 bytes*/ + unsigned int fu_hdr_idx = 0; + unsigned int data_to_send; + int real_offset[IOV_MAX + 1] = { 0 }; /*Used to keep track of nalu offset, useful for incomplete data send to know the exact value to return to app*/ + unsigned int offset_idx = 0; + unsigned int total_sent = 0; + unsigned int sent,left_data; + int real_written,total_offset; + unsigned int already_written = 0; + unsigned int is_end_of_frame = 0; + u8 *b_data = (u8 *) data; + unsigned int idx,off_idx,current_iovec; + + /*Check the H264 start code prefix*/ + start_code = _h264_check_start_code((u8 *)data, data_len); + + max_fu_payload_size = handle->max_payload_size - FU_HEADER_SIZE; + + if(!data_len && !data) { + /*Clear the previous state if an empty AVTP_FLUSH or AVTP_FRAME_END is performed*/ + if ((event && event_len) && ((event->event_mask & AVTP_FLUSH) || (event->event_mask & AVTP_FRAME_END))) { + handle->partial_iovec = 0; + handle->expect_new_frame = 1; + } + + data_iov[iov_idx].iov_base = (void *)data; + data_iov[iov_idx].iov_len = data_len; + + return genavb_stream_send_iov(handle, data_iov, 1, event, event_len); + } + + if (start_code && handle->partial_iovec) + return -GENAVB_ERR_STREAM_TX; + + if (start_code && !handle->expect_new_frame) + return -GENAVB_ERR_STREAM_TX; + + if (!start_code && handle->expect_new_frame) { + if (data_len < 4) + return -GENAVB_ERR_STREAM_TX_NOT_ENOUGH_DATA; + else + return -GENAVB_ERR_STREAM_TX; + } + + + is_end_of_frame = event->event_mask & (AVTP_FRAME_END); + + left_data = data_len; + + if(start_code) { + if (data_len - start_code <= handle->max_payload_size) { + /* At this stage, the next call will introduce a start of frame: + * Either we successfully send all the bytes (the whole NALU) or we fail to write any of them*/ + handle->expect_new_frame = 1; + + + /*This is the start of a new frame whith the rest yet to come + * Return a not enough data error to make the caller send more data (either the full NALU + * with the frame end flag or at least enough to exceed the max payload and make it a FU)*/ + if (!is_end_of_frame) + return -GENAVB_ERR_STREAM_TX_NOT_ENOUGH_DATA; + + /*The whole NALU can be send in one packet*/ + data_to_send = data_len - start_code; + data_iov[iov_idx].iov_base = (void *)(b_data + start_code); /*Get rid of the start_code*/ + data_iov[iov_idx].iov_len = data_to_send; + rc = genavb_stream_send_iov(handle, data_iov, 1, event, event_len); + + if (rc > 0 && rc != data_to_send) { + /* We should never have a partial send because either there is at least one media buffer (of size max_payload_size) + * to accept the data or there is none and we get 0 bytes written*/ + rc = -GENAVB_ERR_STREAM_TX; + } else if (rc > 0) /*We should return the right written value, start_code included*/ + rc += start_code; + + return rc; + } + else + { + first_fu = 1; + b_data += (start_code - 1); + left_data -= (start_code - 1); + } + } + + /*Clear the end of frame flags to make sure we send it only with the last bytes later*/ + event->event_mask &= (~AVTP_FRAME_END); + + if ((handle->partial_iovec)) + { + data_to_send = (left_data > handle->partial_iovec) ? handle->partial_iovec : left_data; + + data_iov[iov_idx].iov_base = (void *) b_data; + data_iov[iov_idx].iov_len = data_to_send; + iov_idx++; + b_data += data_to_send; + left_data -= data_to_send; + total_sent += data_to_send; + + handle->partial_iovec -= data_to_send; + real_offset[offset_idx++] = 0; + + /* The new frame is smaller or equal to partial_iovec + * jump to send_iov and send end of frame */ + if (!left_data) + goto send_iov; + + } + + /* Construct the first FU header outside the loop and jump directly to the next one + * This header contain the NALU HDR (1 byte) then an empty byte */ + if(first_fu && left_data) { + data_iov[iov_idx].iov_base = (void *) b_data; + data_iov[iov_idx].iov_len = FU_HEADER_SIZE; + total_sent += FU_HEADER_SIZE; + + /* 3 bytes of the start code (sent by user) were not sent to the driver, log them for further + * error handling */ + real_offset[offset_idx++] = 3; + + /*Set a marker for further identification : + * To differentiate from a single nal packet and a first + * FU-A packet */ + b_data[0] = CVF_H264_NALU_TYPE_FU_A; + b_data += FU_HEADER_SIZE; + left_data -= FU_HEADER_SIZE; + first_fu = 0; + iov_idx++; + + goto second_iovec_member; + + } + + /* Send iovec two by two : first one should contain the FU HEADER + * and second should contain FU Payload */ + while(left_data) { + /* This is an empty (both bytes) FU header*/ + data_iov[iov_idx].iov_base = &(fu_header[fu_hdr_idx++]); + data_iov[iov_idx].iov_len = FU_HEADER_SIZE; + total_sent += FU_HEADER_SIZE; + + /* Two additional bytes (not sent by user) are sent to the driver */ + real_offset[offset_idx++] = -2; + + iov_idx++; + +second_iovec_member: + data_to_send = (left_data > max_fu_payload_size) ? max_fu_payload_size : left_data; + data_iov[iov_idx].iov_base = b_data; + data_iov[iov_idx].iov_len = data_to_send; + b_data += data_to_send; + left_data -= data_to_send; + total_sent += data_to_send; + real_offset[offset_idx++] = 0; + iov_idx++; + + /*Send by batch of IOV_MAX*/ + if(iov_idx > (IOV_MAX - 2)) { + + if (is_end_of_frame) { + /*Put the AVTP_FRAME_END flag if we are sending the last bytes of the frame*/ + if(!left_data) + event->event_mask |= AVTP_FRAME_END; + } else { + /* Check and keep track of partial iovec and not and of frame to + * prevent an injection of unnecessary FU header */ + handle->partial_iovec = max_fu_payload_size - data_iov[iov_idx - 1].iov_len; + } + + rc = genavb_stream_send_iov(handle, data_iov, iov_idx , event, event_len); + + if (rc < 0) + return rc; + else if (rc != total_sent) + goto incomplete_send; + + /* Send the AVTP_SYNC only on first packet*/ + event->event_mask &= (~AVTP_SYNC); + + if (event->event_mask & AVTP_FRAME_END) + handle->expect_new_frame = 1; /*Successfully sent the last bytes with an end of frame*/ + else if (!left_data) + handle->expect_new_frame = 0; /*Successfully sent the last bytes without an end of frame*/ + + iov_idx = 0; + fu_hdr_idx = 0; + offset_idx = 0; + /*At this stage, all data were successfully written*/ + already_written = (data_len - left_data); + total_sent = 0; + } + } + +send_iov: + + if(iov_idx) { + + if (is_end_of_frame) + { + handle->partial_iovec = 0; + /*Put the AVTP_FRAME_END flag to declare end of NALU */ + event->event_mask |= AVTP_FRAME_END; + } else { + /* Check and keep track of partial iovec and not and of frame to prevent + * an injection of unnecessary FU header */ + handle->partial_iovec = max_fu_payload_size - data_iov[iov_idx - 1].iov_len; + } + + rc = genavb_stream_send_iov(handle, data_iov, iov_idx , event, event_len); + + if (rc < 0) + return rc; + else if (rc != total_sent) + goto incomplete_send; + + /* All bytes were successfully sent, if this is an end of frame, expect a new frame on the next call*/ + if (event->event_mask & AVTP_FRAME_END) + handle->expect_new_frame = 1; + else + handle->expect_new_frame = 0; + + } + + return data_len; + +incomplete_send: + + /*If a non-zero size of bytes was sent, the next call should always send the rest of the NALU*/ + if (rc > 0 || already_written) + handle->expect_new_frame = 0; + + /*Make sure we return the right number of written bytes */ + sent = 0; + real_written = 0; + idx = 0; + off_idx = 0; + total_offset = 0; + current_iovec = 0; + + while (sent < rc) { + total_offset += real_offset[off_idx++]; + current_iovec = data_iov[idx++].iov_len; + sent += current_iovec; + real_written = ((int) sent + total_offset); + } + + if (sent > rc) { + return -GENAVB_ERR_STREAM_TX; + } else { /*sent == rc*/ + if (current_iovec && (current_iovec != max_fu_payload_size)) + return -GENAVB_ERR_STREAM_TX; + + /*reset the handle->partial_iovec, it was not sent anyway*/ + handle->partial_iovec = 0; + } + + real_written += already_written; + return real_written; +} + +int genavb_stream_send(struct genavb_stream_handle const *handle, void const *data, unsigned int data_len, + struct genavb_event const *event, unsigned int event_len) +{ + struct genavb_iovec data_iov; + + data_iov.iov_base = (void *)data; /* We need to remove the const qualifier here, or we would need to define 2 separate iovec structures for tx/rx. */ + data_iov.iov_len = data_len; + + return genavb_stream_send_iov(handle, &data_iov, 1, event, event_len); +} + + +int genavb_stream_send_iov(struct genavb_stream_handle const *handle, struct genavb_iovec const *data_iov, unsigned int data_iov_len, + struct genavb_event const *event, unsigned int event_len) +{ + struct media_queue_tx msg; + int rc; + + if (!handle) { + rc = -GENAVB_ERR_STREAM_INVALID; + goto exit; + } + + msg.data_iov_len = data_iov_len; + msg.data_iov = data_iov; + msg.event = event; + msg.event_len = event_len; + + rc = ioctl(handle->fd, MEDIA_IOC_TX, &msg); + if (rc < 0) { + if (errno == EAGAIN || errno == EINTR) + rc = 0; + else + rc = (-GENAVB_ERR_STREAM_TX); + } + +exit: + return rc; +} + + +int genavb_stream_destroy(struct genavb_stream_handle *handle) +{ + struct list_head *entry, *next; + struct genavb_stream_handle *stream; + int rc; + + if (!handle) { + rc = -GENAVB_ERR_STREAM_INVALID; + goto err_handle_null; + } + + pthread_mutex_lock(&avb_mutex); + + if (handle->genavb != genavb_handle) { + rc = -GENAVB_ERR_STREAM_INVALID; + goto err_handle_invalid; + } + + rc = -GENAVB_ERR_STREAM_INVALID; + for (entry = list_first(&genavb_handle->streams); next = list_next(entry), entry != &genavb_handle->streams; entry = next) { + stream = container_of(entry, struct genavb_stream_handle, list); + + if (stream == handle) { + rc = __avb_stream_destroy(stream); + break; + } + } + + pthread_mutex_unlock(&avb_mutex); + + return rc; + +err_handle_invalid: + pthread_mutex_unlock(&avb_mutex); + +err_handle_null: + return rc; +} diff --git a/api/rtos/api.cmake b/api/rtos/api.cmake new file mode 100644 index 0000000..544551a --- /dev/null +++ b/api/rtos/api.cmake @@ -0,0 +1,46 @@ +genavb_target_add_srcs(TARGET ${avb} + SRCS + control.c + fdb.c + frer.c + generic.c + init.c + psfp.c + qos.c + stream_identification.c + timer.c + vlan.c + ../error.c + ../control.c + ../clock.c + ) + +if(CONFIG_SOCKET) + genavb_target_add_srcs(TARGET ${avb} + SRCS + socket.c + ../socket.c + ) +endif() + +if(CONFIG_AVTP) + genavb_target_add_srcs(TARGET ${avb} + SRCS + streaming.c + ../streaming.c + ) +endif() + +if(CONFIG_HSR) + genavb_target_add_srcs(TARGET ${avb} + SRCS + hsr.c + ) +endif() + +if(CONFIG_DSA) + genavb_target_add_srcs(TARGET ${avb} + SRCS + dsa.c + ) +endif() diff --git a/api/rtos/api_os/control.h b/api/rtos/api_os/control.h new file mode 100644 index 0000000..850c204 --- /dev/null +++ b/api/rtos/api_os/control.h @@ -0,0 +1,34 @@ +/* + * Copyright 2018, 2020-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file control.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2018, 2020-2021, 2023-2024 NXP +*/ + +#ifndef _RTOS_PRIVATE_CONTROL_H_ +#define _RTOS_PRIVATE_CONTROL_H_ + +#include "rtos_abstraction_layer.h" + +#include "common/ipc.h" + +#define AVB_CONTROL_EVENT_QUEUE_LENGTH 16 + +struct genavb_control_handle { + int id; + struct ipc_tx tx; + struct ipc_rx rx; + struct ipc_rx rx_sync; + + rtos_mqueue_t event_queue; + uint8_t event_queue_buffer[AVB_CONTROL_EVENT_QUEUE_LENGTH * sizeof(struct event)]; +}; + +#endif /* _RTOS_PRIVATE_CONTROL_H_ */ diff --git a/api/rtos/api_os/init.h b/api/rtos/api_os/init.h new file mode 100644 index 0000000..a05d0ec --- /dev/null +++ b/api/rtos/api_os/init.h @@ -0,0 +1,54 @@ +/* + * Copyright 2018-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file init.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2018-2024 NXP +*/ + +#ifndef _RTOS_PRIVATE_INIT_H_ +#define _RTOS_PRIVATE_INIT_H_ + +#include "rtos_abstraction_layer.h" + +#include "common/ipc.h" + +#define AVTP_EVENT_QUEUE_LENGTH 8 + +struct genavb_handle { + unsigned int flags; +#ifdef CONFIG_MANAGEMENT + void *management_handle; +#endif +#ifdef CONFIG_GPTP + void *gptp_handle; +#endif +#ifdef CONFIG_SRP + void *srp_handle; +#endif +#ifdef CONFIG_AVDECC + void *avdecc_handle; +#endif +#ifdef CONFIG_AVTP + void *avtp_handle; + struct ipc_tx avtp_tx; + struct ipc_rx avtp_rx; + rtos_mqueue_t event_queue; + uint8_t event_queue_buffer[AVTP_EVENT_QUEUE_LENGTH * sizeof(struct event)]; +#endif +#ifdef CONFIG_MAAP + void *maap_handle; +#endif +#ifdef CONFIG_HSR + void *hsr_handle; + struct ipc_tx hsr_ipc_tx; +#endif +}; + +#endif /* _RTOS_PRIVATE_INIT_H_ */ diff --git a/api/rtos/api_os/streaming.h b/api/rtos/api_os/streaming.h new file mode 100644 index 0000000..7b31fd8 --- /dev/null +++ b/api/rtos/api_os/streaming.h @@ -0,0 +1,29 @@ +/* +* Copyright 2018, 2020, 2023 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + \file streaming.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2018, 2020, 2023 NXP +*/ + +#ifndef _RTOS_PRIVATE_STREAMING_H_ +#define _RTOS_PRIVATE_STREAMING_H_ + +#include "rtos/media_queue.h" + +struct genavb_stream_handle { + struct genavb_handle *genavb; + struct genavb_stream_params params; + unsigned int max_payload_size; /* Transmit maximum packet payload size (in byte units) */ + unsigned int batch; /* Transmit batch (in packet units) */ + + struct media_queue mqueue; +}; + +#endif /* _RTOS_PRIVATE_STREAMING_H_ */ diff --git a/api/rtos/control.c b/api/rtos/control.c new file mode 100644 index 0000000..e7173c3 --- /dev/null +++ b/api/rtos/control.c @@ -0,0 +1,190 @@ +/* + * Copyright 2018-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file control.c + \brief control public API for rtos + \details + \copyright Copyright 2018-2021, 2023-2024 NXP +*/ + +#include + +#include "rtos_abstraction_layer.h" + +#include "common/ipc.h" + +#include "api/control.h" + +int avb_ipc_receive_sync(struct ipc_rx const *rx, unsigned int *msg_type, void *msg, unsigned int *msg_len, int timeout) +{ + rtos_mqueue_t *event_handle = (rtos_mqueue_t *)rx->priv; + struct event e; + int rc; + + if (event_handle) { + if (rtos_mqueue_receive(event_handle, &e, RTOS_MS_TO_TICKS(timeout)) < 0) + rc = -GENAVB_ERR_CTRL_TIMEOUT; + else + rc = avb_ipc_receive(rx, msg_type, msg, msg_len); + } else { + int wait_interval = 10; + + if (timeout >= 1000) + wait_interval = 100; + + while ((rc = avb_ipc_receive(rx, msg_type, msg, msg_len)) == -GENAVB_ERR_CTRL_RX) { + + if (timeout <= 0) { + rc = -GENAVB_ERR_CTRL_TIMEOUT; + break; + } + + if (wait_interval > timeout) + wait_interval = timeout; + + rtos_sleep(RTOS_MS_TO_TICKS(wait_interval)); + timeout -= wait_interval; + } + } + + return rc; +} + +int genavb_control_set_callback(struct genavb_control_handle *handle, int (*callback)(void *), void *data) +{ + int rc; + + if (ipc_rx_set_callback(&handle->rx, callback, data) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err; + } + + return GENAVB_SUCCESS; + +err: + return rc; +} + +int genavb_control_enable_callback(struct genavb_control_handle *handle) +{ + int rc; + + if (ipc_rx_enable_callback(&handle->rx) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err; + } + + return GENAVB_SUCCESS; + +err: + return rc; +} + +int genavb_control_open(struct genavb_handle const *genavb, struct genavb_control_handle **control, genavb_control_id_t id) +{ + int rc; + + /* + * allocate new stream + */ + + if (id >= GENAVB_CTRL_ID_MAX) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_invalid_id; + } + + *control = rtos_malloc(sizeof(struct genavb_control_handle)); + if (!*control) { + rc = (-GENAVB_ERR_NO_MEMORY); + goto err_alloc; + } + memset((*control), 0, sizeof(struct genavb_control_handle)); + + (*control)->id = id; + + if (rtos_mqueue_init(&(*control)->event_queue, AVB_CONTROL_EVENT_QUEUE_LENGTH, sizeof(struct event), (*control)->event_queue_buffer) < 0) { + rc = (-GENAVB_ERR_NO_MEMORY); + goto err_event_queue; + } + + if (ipc_rx_init_no_notify(&(*control)->rx, ipc_id[id][CTRL_RX]) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_control_rx_init; + } + + if (ipc_tx_init(&(*control)->tx, ipc_id[id][CTRL_TX]) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_control_tx_init; + } + + if (ipc_id[id][CTRL_RX_SYNC] != IPC_ID_NONE) { + if (ipc_rx_init(&(*control)->rx_sync, ipc_id[id][CTRL_RX_SYNC], NULL, (unsigned long)&(*control)->event_queue) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_control_rx_sync_init; + } + } + + switch (id) { + case GENAVB_CTRL_AVDECC_MEDIA_STACK: + case GENAVB_CTRL_AVDECC_CONTROLLED: + if (send_heartbeat(&(*control)->tx, &(*control)->rx, 0) < 0) { + rc = -GENAVB_ERR_STACK_NOT_READY; + goto err_tx_heartbeat; + } + + break; + + case GENAVB_CTRL_AVDECC_CONTROLLER: + if (send_heartbeat(&(*control)->tx, &(*control)->rx_sync, IPC_FLAGS_AVB_MSG_SYNC) < 0) { + rc = -GENAVB_ERR_STACK_NOT_READY; + goto err_tx_heartbeat; + } + + break; + + case GENAVB_CTRL_MSRP: + case GENAVB_CTRL_MVRP: + case GENAVB_CTRL_GPTP: + case GENAVB_CTRL_CLOCK_DOMAIN: + if (ipc_tx_connect(&(*control)->tx, &(*control)->rx) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_connect; + } + + if (ipc_tx_connect(&(*control)->tx, &(*control)->rx_sync) < 0) { + rc = (-GENAVB_ERR_CTRL_INIT); + goto err_connect; + } + + break; + + default: + break; + } + + return GENAVB_SUCCESS; + +err_connect: +err_tx_heartbeat: + if (ipc_id[id][CTRL_RX_SYNC] != IPC_ID_NONE) + ipc_rx_exit(&(*control)->rx_sync); + +err_control_rx_sync_init: + ipc_tx_exit(&(*control)->tx); + +err_control_tx_init: + ipc_rx_exit(&(*control)->rx); + +err_control_rx_init: +err_event_queue: + rtos_free(*control); + *control = NULL; + +err_alloc: +err_invalid_id: + return rc; +} diff --git a/api/rtos/dsa.c b/api/rtos/dsa.c new file mode 100644 index 0000000..9a5c3b5 --- /dev/null +++ b/api/rtos/dsa.c @@ -0,0 +1,21 @@ +/* + * Copyright 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "genavb/error.h" +#include "genavb/dsa.h" + +int genavb_port_dsa_add(unsigned int cpu_port, uint8_t *mac_addr, unsigned int slave_port) +{ + if (!mac_addr) + return -GENAVB_ERR_INVALID; + + return net_port_dsa_add(cpu_port, mac_addr, slave_port); +} + +int genavb_port_dsa_delete(unsigned int slave_port) +{ + return net_port_dsa_delete(slave_port); +} diff --git a/api/rtos/fdb.c b/api/rtos/fdb.c new file mode 100644 index 0000000..0383618 --- /dev/null +++ b/api/rtos/fdb.c @@ -0,0 +1,44 @@ +/* + * Copyright 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "genavb/fdb.h" + + +int genavb_fdb_update(uint8_t *address, uint16_t vid, struct genavb_fdb_port_map *map) +{ + int rc; + + rc = fdb_update(map->port_id, address, vid, false, map->control); + + return rc; +} + +int genavb_fdb_delete(uint8_t *address, uint16_t vid) +{ + int rc; + + rc = fdb_delete(address, vid, false); + + return rc; +} + +int genavb_fdb_read(uint8_t *address, uint16_t vid, bool *dynamic, struct genavb_fdb_port_map *map, genavb_fdb_status_t *status) +{ + int rc; + + rc = fdb_read(address, vid, dynamic, map, status); + + return rc; +} + +int genavb_fdb_dump(uint32_t *token, uint8_t *address, uint16_t *vid, bool *dynamic, struct genavb_fdb_port_map *map, genavb_fdb_status_t *status) +{ + int rc; + + rc = fdb_dump(token, address, vid, dynamic, map, status); + + return rc; +} diff --git a/api/rtos/frer.c b/api/rtos/frer.c new file mode 100644 index 0000000..da810f8 --- /dev/null +++ b/api/rtos/frer.c @@ -0,0 +1,98 @@ +/* +* Copyright 2023-2024 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +#include "genavb/frer.h" + + +int genavb_sequence_generation_update(uint32_t index, struct genavb_sequence_generation *entry) +{ + int rc; + + rc = sequence_generation_update(index, entry, SEQG_UPDATE_OPTION_CONFIG); + + return rc; +} + +int genavb_sequence_generation_reset(uint32_t index) +{ + int rc; + + rc = sequence_generation_update(index, NULL, SEQG_UPDATE_OPTION_RESET); + + return rc; +} + +int genavb_sequence_generation_delete(uint32_t index) +{ + int rc; + + rc = sequence_generation_delete(index); + + return rc; +} + +int genavb_sequence_generation_read(uint32_t index, struct genavb_sequence_generation *entry) +{ + int rc; + + rc = sequence_generation_read(index, entry); + + return rc; +} + +int genavb_sequence_recovery_update(uint32_t index, struct genavb_sequence_recovery *entry) +{ + int rc; + + rc = sequence_recovery_update(index, entry); + + return rc; +} + +int genavb_sequence_recovery_delete(uint32_t index) +{ + int rc; + + rc = sequence_recovery_delete(index); + + return rc; +} + +int genavb_sequence_recovery_read(uint32_t index, struct genavb_sequence_recovery *entry) +{ + int rc; + + rc = sequence_recovery_read(index, entry); + + return rc; +} + +int genavb_sequence_identification_update(unsigned int port_id, bool direction_out_facing, struct genavb_sequence_identification *entry) +{ + int rc; + + rc = sequence_identification_update(port_id, direction_out_facing, entry); + + return rc; +} + +int genavb_sequence_identification_delete(unsigned int port_id, bool direction_out_facing) +{ + int rc; + + rc = sequence_identification_delete(port_id, direction_out_facing); + + return rc; +} + +int genavb_sequence_identification_read(unsigned int port_id, bool direction_out_facing, struct genavb_sequence_identification *entry) +{ + int rc; + + rc = sequence_identification_read(port_id, direction_out_facing, entry); + + return rc; +} diff --git a/api/rtos/generic.c b/api/rtos/generic.c new file mode 100644 index 0000000..490dda2 --- /dev/null +++ b/api/rtos/generic.c @@ -0,0 +1,72 @@ +/* + * Copyright 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file generic.c + \brief generic public API for rtos + \details + \copyright Copyright 2020-2021, 2023 NXP +*/ + +#include "genavb/log.h" +#include "genavb/error.h" + +#include "common/log.h" +#include "os/net.h" + +int genavb_log_level_set(genavb_log_component_id_t id, genavb_log_level_t level) +{ + if (log_level_set(id, level) < 0) + return -GENAVB_ERR_INVALID; + + return GENAVB_SUCCESS; +} + +int genavb_port_stats_get_number(unsigned int port_id) +{ + int size; + + size = net_port_stats_get_number(port_id); + if (size < 0) + return -GENAVB_ERR_INVALID; + + return size; +} + +int genavb_port_stats_get_strings(unsigned int port_id, const char **buf, unsigned int buf_len) +{ + if (!buf) + return -GENAVB_ERR_INVALID; + + if (net_port_stats_get_strings(port_id, buf, buf_len) < 0) + return -GENAVB_ERR_INVALID; + + return GENAVB_SUCCESS; +} + +int genavb_port_stats_get(unsigned int port_id, uint64_t *buf, unsigned int buf_len) +{ + if (!buf) + return -GENAVB_ERR_INVALID; + + if (net_port_stats_get(port_id, buf, buf_len) < 0) + return -GENAVB_ERR_INVALID; + + return GENAVB_SUCCESS; +} + +int genavb_port_status_get(unsigned int port_id, bool *up, bool *duplex, unsigned int *rate) +{ + if (!up || !duplex || !rate) + return -GENAVB_ERR_INVALID; + + return net_port_status_get(port_id, up, duplex, rate); +} + +int genavb_port_set_max_frame_size(unsigned int port_id, uint16_t size) +{ + return net_port_set_max_frame_size(port_id, size); +} diff --git a/api/rtos/hsr.c b/api/rtos/hsr.c new file mode 100644 index 0000000..d36da74 --- /dev/null +++ b/api/rtos/hsr.c @@ -0,0 +1,40 @@ +/* +* Copyright 2023 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +#include "rtos/ipc.h" +#include "api/control.h" +#include "genavb/hsr.h" +#include "api/init.h" + +static int hsr_ipc_init(struct genavb_handle *genavb) +{ + int rc; + + if (ipc_tx_init(&genavb->hsr_ipc_tx, IPC_HSR_STACK) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + return rc; + } + + return 0; +} + +int genavb_hsr_operation_mode_set(struct genavb_handle *genavb, genavb_hsr_mode_t mode) +{ + genavb_hsr_mode_t params = mode; + int rc; + + if (!(genavb->flags & HSR_INITIALIZED)) { + rc = hsr_ipc_init(genavb); + if (rc < 0) + return rc; + + genavb->flags |= HSR_INITIALIZED; + } + + rc = avb_ipc_send(&genavb->hsr_ipc_tx, IPC_HSR_OPERATION_MODE_SET, ¶ms, sizeof(params), 0); + + return rc; +} diff --git a/api/rtos/init.c b/api/rtos/init.c new file mode 100644 index 0000000..5326879 --- /dev/null +++ b/api/rtos/init.c @@ -0,0 +1,393 @@ +/* + * Copyright 2018-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file init.c + \brief GenAVB public API initialization for rtos + \details API definition for the GenAVB library + \copyright Copyright 2018-2024 NXP +*/ +#include + +#include "api/init.h" + +#include "common/log.h" +#include "common/version.h" + +#include "rtos/stats_task.h" +#include "rtos/net_task.h" +#include "rtos/ipc.h" +#include "rtos/media_queue.h" +#include "rtos/media_clock.h" +#include "rtos/net_port.h" +#include "rtos/net_port_netc_1588.h" +#include "rtos/hr_timer.h" +#include "rtos/gpt.h" +#include "rtos/tpm.h" +#include "rtos/fqtss.h" +#include "rtos/clock.h" +#include "rtos/net_bridge.h" +#include "rtos/msgintr.h" + +extern void os_random_init(void); + +#ifdef CONFIG_MANAGEMENT +extern void *management_task_init(struct management_config *cfg); +extern void management_task_exit(void *handle); +extern struct management_config management_default_config; +static struct management_config *management_current_config = &management_default_config; +#endif +#ifdef CONFIG_GPTP +extern void *gptp_task_init(struct fgptp_config *cfg); +extern void gptp_task_exit(void *handle); +extern struct fgptp_config gptp_default_config; +static struct fgptp_config *gptp_current_config = &gptp_default_config; +#endif +#ifdef CONFIG_SRP +extern void *srp_task_init(struct srp_config *cfg); +extern void srp_task_exit(void *handle); +extern struct srp_config srp_default_config; +static struct srp_config *srp_current_config = &srp_default_config; +#endif +#ifdef CONFIG_AVTP +void streaming_exit(struct genavb_handle *genavb); +extern void *avtp_task_init(struct avtp_config *cfg); +extern void avtp_task_exit(void *task); +extern struct avtp_config avtp_default_config; +static struct avtp_config *avtp_current_config = &avtp_default_config; +#endif +#ifdef CONFIG_AVDECC +extern void *avdecc_task_init(struct avdecc_config *cfg); +extern void avdecc_task_exit(void *handle); +extern struct avdecc_config avdecc_default_config; +static struct avdecc_config *avdecc_current_config = &avdecc_default_config; +#endif +#ifdef CONFIG_MAAP +extern void *maap_task_init(struct maap_config *cfg); +extern void maap_task_exit(void *handle); +extern struct maap_config maap_default_config; +static struct maap_config *maap_current_config = &maap_default_config; +#endif +#ifdef CONFIG_HSR +extern void *hsr_task_init(struct hsr_config *cfg); +extern void hsr_task_exit(void *handle); +extern struct hsr_config hsr_default_config; +static struct hsr_config *hsr_current_config = &hsr_default_config; +#endif + +__init void genavb_get_default_config(struct genavb_config *config) +{ +#ifdef CONFIG_MANAGEMENT + memcpy(&config->management_config, &management_default_config, sizeof(struct management_config)); +#endif +#ifdef CONFIG_GPTP + memcpy(&config->fgptp_config, &gptp_default_config, sizeof(struct fgptp_config)); +#endif +#ifdef CONFIG_SRP + memcpy(&config->srp_config, &srp_default_config, sizeof(struct srp_config)); +#endif +#ifdef CONFIG_AVTP + memcpy(&config->avtp_config, &avtp_default_config, sizeof(struct avtp_config)); +#endif +#ifdef CONFIG_AVDECC + memcpy(&config->avdecc_config, &avdecc_default_config, sizeof(struct avdecc_config)); +#endif +#ifdef CONFIG_MAAP + memcpy(&config->maap_config, &maap_default_config, sizeof(struct maap_config)); +#endif +#ifdef CONFIG_HSR + memcpy(&config->hsr_config, &hsr_default_config, sizeof(struct hsr_config)); +#endif +} + +__init void genavb_set_config(struct genavb_config *config) +{ +#ifdef CONFIG_MANAGEMENT + management_current_config = &config->management_config; +#endif +#ifdef CONFIG_GPTP + gptp_current_config = &config->fgptp_config; +#endif +#ifdef CONFIG_SRP + srp_current_config = &config->srp_config; +#endif +#ifdef CONFIG_AVTP + avtp_current_config = &config->avtp_config; +#endif +#ifdef CONFIG_AVDECC + avdecc_current_config = &config->avdecc_config; +#endif +#ifdef CONFIG_MAAP + maap_current_config = &config->maap_config; +#endif +#ifdef CONFIG_HSR + hsr_current_config = &config->hsr_config; +#endif +} + +__init static int osal_init(void) +{ + if (stats_task_init() < 0) + goto err_stats; + + if (mclock_init() < 0) + goto err_mclock; + + if (media_queue_init() < 0) + goto err_media_queue; + + if (hw_timer_init() < 0) + goto err_hw_timer; + + if (msgintr_init() < 0) + goto err_msgintr; + + if (netc_1588_init() < 0) + goto err_netc_1588; + + if (gpt_driver_init() < 0) + goto err_gpt; + + if (tpm_driver_init() < 0) + goto err_tpm; + + if (port_init() < 0) + goto err_port; + + if (bridge_init() < 0) + goto err_bridge; + + if (os_clock_init() < 0) + goto err_clock; + + if (hr_timer_init() < 0) + goto err_timer; + + if (net_task_init() < 0) + goto err_net; + + if (port_post_init() < 0) + goto err_port_post; + + if (ipc_init() < 0) + goto err_ipc; + + if (fqtss_init() < 0) + goto err_fqtss; + + /* + * random global init. + */ + os_random_init(); + + return 0; + +err_fqtss: + ipc_exit(); + +err_ipc: + port_pre_exit(); + +err_port_post: + net_task_exit(); + +err_net: + hr_timer_exit(); + +err_timer: + os_clock_exit(); + +err_clock: + bridge_exit(); + +err_bridge: + port_exit(); + +err_port: + tpm_driver_exit(); + +err_tpm: + gpt_driver_exit(); + +err_gpt: + netc_1588_exit(); + +err_netc_1588: + msgintr_exit(); + +err_msgintr: + hw_timer_exit(); + +err_hw_timer: + media_queue_exit(); + +err_media_queue: + mclock_exit(); + +err_mclock: + stats_task_exit(); + +err_stats: + + return -1; +} + +__exit static void osal_exit(void) +{ + fqtss_exit(); + ipc_exit(); + port_pre_exit(); + net_task_exit(); + hr_timer_exit(); + os_clock_exit(); + bridge_exit(); + port_exit(); + tpm_driver_exit(); + gpt_driver_exit(); + netc_1588_exit(); + msgintr_exit(); + hw_timer_exit(); + media_queue_exit(); + mclock_exit(); + stats_task_exit(); +} + +__init int genavb_init(struct genavb_handle **genavb, unsigned int flags) +{ + int rc = -GENAVB_ERR_NO_MEMORY; + + log_level_set(api_COMPONENT_ID, LOG_INIT); + log_level_set(common_COMPONENT_ID, LOG_INIT); + log_level_set(os_COMPONENT_ID, LOG_INFO); + + os_log(LOG_INIT, "NXP's GenAVB/TSN stack version %s\n", GENAVB_VERSION); + + *genavb = rtos_malloc(sizeof(struct genavb_handle)); + if (!(*genavb)) { + rc = -GENAVB_ERR_NO_MEMORY; + goto err_handle; + } + + memset(*genavb, 0, sizeof(struct genavb_handle)); + + (*genavb)->flags = flags; + (*genavb)->flags &= ~AVTP_INITIALIZED; + + if (osal_init() < 0) + goto err_osal; + +#ifdef CONFIG_MANAGEMENT + (*genavb)->management_handle = management_task_init(management_current_config); + if (!(*genavb)->management_handle) + goto err_management; +#endif +#ifdef CONFIG_GPTP + (*genavb)->gptp_handle = gptp_task_init(gptp_current_config); + if (!(*genavb)->gptp_handle) + goto err_gptp; +#endif +#ifdef CONFIG_SRP + (*genavb)->srp_handle = srp_task_init(srp_current_config); + if (!(*genavb)->srp_handle) + goto err_srp; +#endif +#ifdef CONFIG_AVTP + (*genavb)->avtp_handle = avtp_task_init(avtp_current_config); + if (!(*genavb)->avtp_handle) + goto err_avtp; +#endif +#ifdef CONFIG_AVDECC + (*genavb)->avdecc_handle = avdecc_task_init(avdecc_current_config); + if (!(*genavb)->avdecc_handle) + goto err_avdecc; +#endif +#ifdef CONFIG_MAAP + (*genavb)->maap_handle = maap_task_init(maap_current_config); + if (!(*genavb)->maap_handle) + goto err_maap; +#endif +#ifdef CONFIG_HSR + (*genavb)->hsr_handle = hsr_task_init(hsr_current_config); + if (!(*genavb)->hsr_handle) + goto err_hsr; +#endif + return GENAVB_SUCCESS; + +#ifdef CONFIG_HSR +err_hsr: +#endif +#ifdef CONFIG_MAAP + maap_task_exit((*genavb)->maap_handle); +err_maap: +#endif +#ifdef CONFIG_AVDECC + avdecc_task_exit((*genavb)->avdecc_handle); +err_avdecc: +#endif +#ifdef CONFIG_AVTP + avtp_task_exit((*genavb)->avtp_handle); +err_avtp: +#endif +#if defined(CONFIG_SRP) && (defined(CONFIG_MAAP) || defined(CONFIG_AVDECC) || defined(CONFIG_AVTP)) || defined(CONFIG_HSR) + srp_task_exit((*genavb)->srp_handle); +#endif +#ifdef CONFIG_SRP +err_srp: +#endif +#ifdef CONFIG_GPTP + gptp_task_exit((*genavb)->gptp_handle); +err_gptp: +#endif +#if ((defined(CONFIG_MANAGEMENT) && defined(CONFIG_GPTP)) || defined(CONFIG_SRP) || defined(CONFIG_AVTP) || defined(CONFIG_AVDECC) || defined(CONFIG_MAAP) || defined(CONFIG_HSR)) + management_task_exit((*genavb)->management_handle); +#endif +#ifdef CONFIG_MANAGEMENT +err_management: +#endif + osal_exit(); + +err_osal: + rtos_free(*genavb); + +err_handle: + *genavb = NULL; + + return rc; +} + + +__exit int genavb_exit(struct genavb_handle *genavb) +{ +#ifdef CONFIG_HSR + hsr_task_exit(genavb->hsr_handle); +#endif +#ifdef CONFIG_AVTP + streaming_exit(genavb); +#endif +#ifdef CONFIG_AVDECC + avdecc_task_exit(genavb->avdecc_handle); +#endif +#ifdef CONFIG_AVTP + avtp_task_exit(genavb->avtp_handle); +#endif +#ifdef CONFIG_SRP + srp_task_exit(genavb->srp_handle); +#endif +#ifdef CONFIG_GPTP + gptp_task_exit(genavb->gptp_handle); +#endif +#ifdef CONFIG_MANAGEMENT + management_task_exit(genavb->management_handle); +#endif +#ifdef CONFIG_MAAP + maap_task_exit(genavb->maap_handle); +#endif + + osal_exit(); + rtos_free(genavb); + + return GENAVB_SUCCESS; +} diff --git a/api/rtos/psfp.c b/api/rtos/psfp.c new file mode 100644 index 0000000..53cbec3 --- /dev/null +++ b/api/rtos/psfp.c @@ -0,0 +1,151 @@ +/* + * Copyright 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "genavb/psfp.h" +#include "genavb/error.h" +#include "api/clock.h" + + +int genavb_stream_filter_update(uint32_t index, struct genavb_stream_filter_instance *instance) +{ + int rc; + + rc = stream_filter_update(index, instance); + + return rc; +} + +int genavb_stream_filter_delete(uint32_t index) +{ + int rc; + + rc = stream_filter_delete(index); + + return rc; +} + +int genavb_stream_filter_read(uint32_t index, struct genavb_stream_filter_instance *instance) +{ + int rc; + + rc = stream_filter_read(index, instance); + + return rc; +} + +unsigned int genavb_stream_filter_get_max_entries(void) +{ + return stream_filter_get_max_entries(); +} + +int genavb_stream_gate_update(uint32_t index, genavb_clock_id_t clk_id, struct genavb_stream_gate_instance *instance) +{ + os_clock_id_t os_clk_id; + int rc; + + if (clk_id >= GENAVB_CLOCK_MAX) { + rc = -GENAVB_ERR_CLOCK; + goto err; + } + + os_clk_id = genavb_clock_to_os_clock(clk_id); + if (os_clk_id >= OS_CLOCK_MAX) { + rc = -GENAVB_ERR_CLOCK; + goto err; + } + + rc = stream_gate_update(index, os_clk_id, instance, SG_UPDATE_OPTION_CONFIG); + +err: + return rc; +} + +int genavb_stream_gate_reset_rx_invalid(uint32_t index) +{ + int rc; + + rc = stream_gate_update(index, 0, NULL, SG_UPDATE_OPTION_RESET_IRX); + + return rc; +} + +int genavb_stream_gate_reset_octets_exceeded(uint32_t index) +{ + int rc; + + rc = stream_gate_update(index, 0, NULL, SG_UPDATE_OPTION_RESET_OEX); + + return rc; +} + +int genavb_stream_gate_delete(uint32_t index) +{ + int rc; + + rc = stream_gate_delete(index); + + return rc; +} + +int genavb_stream_gate_read(uint32_t index, genavb_sg_config_type_t type, struct genavb_stream_gate_instance *instance) +{ + int rc; + + rc = stream_gate_read(index, type, instance); + + return rc; +} + +unsigned int genavb_stream_gate_get_max_entries(void) +{ + return stream_gate_get_max_entries(); +} + +unsigned int genavb_stream_gate_control_get_max_entries(void) +{ + return stream_gate_control_get_max_entries(); +} + +int genavb_flow_meter_update(uint32_t index, struct genavb_flow_meter_instance *instance) +{ + int rc; + + rc = flow_meter_update(index, instance, FM_UPDATE_OPTION_CONFIG); + + return rc; +} + +int genavb_flow_meter_mark_red_reset(uint32_t index) +{ + int rc; + + rc = flow_meter_update(index, NULL, FM_UPDATE_OPTION_RESET_MR); + + return rc; +} + +int genavb_flow_meter_delete(uint32_t index) +{ + int rc; + + rc = flow_meter_delete(index); + + return rc; +} + +int genavb_flow_meter_read(uint32_t index, struct genavb_flow_meter_instance *instance) +{ + int rc; + + rc = flow_meter_read(index, instance); + + return rc; +} + +unsigned int genavb_flow_meter_get_max_entries(void) +{ + return flow_meter_get_max_entries(); +} \ No newline at end of file diff --git a/api/rtos/qos.c b/api/rtos/qos.c new file mode 100644 index 0000000..bd71981 --- /dev/null +++ b/api/rtos/qos.c @@ -0,0 +1,146 @@ +/* + * Copyright 2020-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file qos.c + \brief qos public API for rtos + \details + \copyright Copyright 2020-2021, 2023-2024 NXP +*/ + +#include "api/clock.h" +#include "genavb/qos.h" +#include "genavb/error.h" + +#include "os/net.h" +#include "os/qos.h" + +int genavb_st_set_admin_config(unsigned int port_id, genavb_clock_id_t clk_id, + struct genavb_st_config *config) +{ + os_clock_id_t os_id; + int rc = GENAVB_SUCCESS; + + if (!config) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (config->enable && + (config->list_length && !config->control_list)) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (clk_id >= GENAVB_CLOCK_MAX) { + rc = -GENAVB_ERR_CLOCK; + goto out; + } + + os_id = genavb_clock_to_os_clock(clk_id); + if (os_id >= OS_CLOCK_MAX) { + rc = -GENAVB_ERR_CLOCK; + goto out; + } + + if (qos_st_set_admin_config(port_id, os_id, config) < 0) { + rc = -GENAVB_ERR_ST; + goto out; + } + +out: + return rc; +} + +int genavb_st_get_config(unsigned int port_id, genavb_st_config_type_t type, + struct genavb_st_config *config, unsigned int list_length) +{ + int rc = GENAVB_SUCCESS; + + if (!config || !config->control_list) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (qos_st_get_config(port_id, type, config, list_length) < 0) { + rc = -GENAVB_ERR_ST; + goto out; + } + +out: + return rc; +} + +int genavb_st_set_max_sdu(unsigned int port_id, struct genavb_st_max_sdu *queue_max_sdu, unsigned int n) +{ + int rc; + + if (!queue_max_sdu) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + rc = qos_st_set_max_sdu(port_id, queue_max_sdu, n); + +out: + return rc; +} + +int genavb_st_get_max_sdu(unsigned int port_id, struct genavb_st_max_sdu *queue_max_sdu) +{ + int rc; + + if (!queue_max_sdu) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + rc = qos_st_get_max_sdu(port_id, queue_max_sdu); + +out: + return rc; +} + +int genavb_fp_set(unsigned int port_id, genavb_fp_config_type_t type, struct genavb_fp_config *config) +{ + int rc = GENAVB_SUCCESS; + + if (!config) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (qos_fp_set(port_id, type, config) < 0) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + +out: + return rc; +} + +int genavb_fp_get(unsigned int port_id, genavb_fp_config_type_t type, struct genavb_fp_config *config) +{ + int rc = GENAVB_SUCCESS; + + if (!config) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (qos_fp_get(port_id, type, config) < 0) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + +out: + return rc; +} + +unsigned int genavb_priority_to_traffic_class(unsigned int port_id, uint8_t priority) +{ + return net_port_priority_to_traffic_class(port_id, priority); +} diff --git a/api/rtos/socket.c b/api/rtos/socket.c new file mode 100644 index 0000000..1e54318 --- /dev/null +++ b/api/rtos/socket.c @@ -0,0 +1,160 @@ +/* + * Copyright 2018-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file socket.c + \brief control public API for rtos + \details + \copyright Copyright 2018-2021, 2023-2024 NXP +*/ + +#include "rtos_abstraction_layer.h" + +#include "common/ipc.h" +#include "rtos/net_port.h" +#include "rtos/net_socket.h" + +#include "api/socket.h" + +#define SOCKET_EVENT_QUEUE_LENGTH 16 + +int socket_rx_event_init(struct genavb_socket_rx *sock) +{ + rtos_mqueue_t *event_queue_h = NULL; + + if (sock->flags & GENAVB_SOCKF_NONBLOCK) { + goto exit; + } else { + event_queue_h = rtos_mqueue_alloc_init(SOCKET_EVENT_QUEUE_LENGTH, sizeof(struct event)); + if (!event_queue_h) { + goto err; + } + } + +exit: + sock->priv = (unsigned long)event_queue_h; + + return 0; + +err: + return -1; +} + +void socket_rx_event_exit(struct genavb_socket_rx *sock) +{ + if (sock->priv) + rtos_mqueue_destroy((rtos_mqueue_t *)sock->priv); +} + +int socket_rx_event_check(struct genavb_socket_rx *sock) +{ + if (sock->priv) { + struct event e; + + /* Blocking */ + if (rtos_mqueue_receive((rtos_mqueue_t *)sock->priv, &e, RTOS_WAIT_FOREVER) < 0) + goto err; + + if (e.type != EVENT_TYPE_NET_RX) + goto err; + } + + return 0; + +err: + return -1; +} + +void socket_rx_event_rearm(struct genavb_socket_rx *sock) +{ + if (sock->priv) + net_rx_enable_callback(&sock->net); +} + +int genavb_socket_rx_set_callback(struct genavb_socket_rx *sock, void (*callback)(void *), void *data) +{ + int rc; + + if (!sock) { + rc = -GENAVB_ERR_INVALID; + goto err; + } + + if (net_rx_set_callback(&sock->net, callback, data) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err; + } + + return GENAVB_SUCCESS; + +err: + return rc; +} + +int genavb_socket_rx_enable_callback(struct genavb_socket_rx *sock) +{ + int rc; + + if (!sock) { + rc = -GENAVB_ERR_INVALID; + goto err; + } + + if (net_rx_enable_callback(&sock->net) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err; + } + + return GENAVB_SUCCESS; + +err: + return rc; +} + +int genavb_socket_tx_set_callback(struct genavb_socket_tx *sock, void (*callback)(void *), void *data) +{ + int rc; + + if (!sock) { + rc = -GENAVB_ERR_INVALID; + goto err; + } + + if (net_tx_set_callback(&sock->net, callback, data) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err; + } + + return GENAVB_SUCCESS; + +err: + return rc; +} + +int genavb_socket_tx_enable_callback(struct genavb_socket_tx *sock) +{ + int rc; + + if (!sock) { + rc = -GENAVB_ERR_INVALID; + goto err; + } + + if (net_tx_enable_callback(&sock->net) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err; + } + + return GENAVB_SUCCESS; + +err: + return rc; +} + +int genavb_socket_get_hwaddr(unsigned int port_id, unsigned char *addr) +{ + return socket_get_hwaddr(port_id, addr); +} diff --git a/api/rtos/stream_identification.c b/api/rtos/stream_identification.c new file mode 100644 index 0000000..a7c3070 --- /dev/null +++ b/api/rtos/stream_identification.c @@ -0,0 +1,32 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file stream_identification.c + \brief GenAVB public API + \details 802.1CB stream identification + + \copyright Copyright 2023 NXP +*/ + +#include "genavb/stream_identification.h" +#include "os/stream_identification.h" + +int genavb_stream_identification_update(uint32_t index, struct genavb_stream_identity *entry) +{ + return stream_identity_update(index, entry); +} + +int genavb_stream_identification_delete(uint32_t index) +{ + return stream_identity_delete(index); +} + +int genavb_stream_identification_read(uint32_t index, struct genavb_stream_identity *entry) +{ + return stream_identity_read(index, entry); +} + diff --git a/api/rtos/streaming.c b/api/rtos/streaming.c new file mode 100644 index 0000000..2f2ddc6 --- /dev/null +++ b/api/rtos/streaming.c @@ -0,0 +1,250 @@ +/* + * Copyright 2018-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file streaming.c + \brief streaming public API for rtos + \details + \copyright Copyright 2018-2021, 2023-2024 NXP +*/ + +#include + +#include "api/control.h" +#include "api/streaming.h" + +#include "common/avdecc.h" + +#include "rtos/media_queue.h" + +__init int streaming_init(struct genavb_handle *genavb) +{ + int rc; + + if (rtos_mqueue_init(&genavb->event_queue, AVTP_EVENT_QUEUE_LENGTH, sizeof(struct event), genavb->event_queue_buffer) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err_event_queue; + } + + /* + * setup media stack to avtp ipc + */ + if (ipc_tx_init(&genavb->avtp_tx, IPC_MEDIA_STACK_AVTP) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err_ipc_tx_init; + } + + if (ipc_rx_init(&genavb->avtp_rx, IPC_AVTP_MEDIA_STACK, NULL, (unsigned long)&genavb->event_queue) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err_ipc_rx_init; + } + + if (ipc_tx_connect(&genavb->avtp_tx, &genavb->avtp_rx) < 0) { + rc = -GENAVB_ERR_CTRL_INIT; + goto err_ipc_tx_connect; + } + + return GENAVB_SUCCESS; + +err_ipc_tx_connect: + ipc_rx_exit(&genavb->avtp_rx); + +err_ipc_rx_init: + ipc_tx_exit(&genavb->avtp_tx); + +err_ipc_tx_init: +err_event_queue: + return rc; +} + +__exit void streaming_exit(struct genavb_handle *genavb) +{ + if (genavb->flags & AVTP_INITIALIZED) { + ipc_rx_exit(&genavb->avtp_rx); + ipc_tx_exit(&genavb->avtp_tx); + } +} + +int genavb_stream_create(struct genavb_handle *genavb, struct genavb_stream_handle **stream, + struct genavb_stream_params const *params, unsigned int *batch_size, genavb_stream_create_flags_t flags) +{ + struct media_queue_api_params api_params; + int rc; + + if (!genavb) { + rc = -GENAVB_ERR_INVALID; + goto err_genavb_handle_null; + } + + /* + * allocate new stream + */ + *stream = rtos_malloc(sizeof(struct genavb_stream_handle)); + if (!*stream) { + rc = -GENAVB_ERR_NO_MEMORY; + goto err_alloc; + } + + memset(*stream, 0, sizeof(struct genavb_stream_handle)); + + memcpy(&(*stream)->params, params, sizeof(struct genavb_stream_params)); + + /* + * connect avtp + */ + rc = connect_avtp(genavb, *stream); + if (rc != GENAVB_SUCCESS) { + goto err_connect; + } + + if (params->subtype == AVTP_SUBTYPE_CRF) { + (*stream)->mqueue.id = NULL; + *batch_size = 0; + } else { + if (params->direction == AVTP_DIRECTION_TALKER) { + /* Align batch size to AVTP thread batch size */ + + unsigned int min_batch_size = (*stream)->batch * (*stream)->max_payload_size; + + if (*batch_size > min_batch_size) + *batch_size = (*batch_size / min_batch_size) * min_batch_size; + + api_params.max_payload_size = (*stream)->max_payload_size; + } else + api_params.max_payload_size = avdecc_fmt_payload_size(¶ms->format, params->stream_class); + + api_params.port = params->port; + //FIXME use logical port to clock + api_params.clock_gptp = OS_CLOCK_GPTP_EP_0_0; + copy_64(api_params.stream_id, ¶ms->stream_id); + api_params.batch_size = *batch_size; + api_params.frame_stride = avdecc_fmt_sample_stride(¶ms->format); + api_params.frame_size = avdecc_fmt_sample_size(¶ms->format); + + if (media_api_open(&(*stream)->mqueue, &api_params, params->direction == AVTP_DIRECTION_TALKER) < 0) { + rc = -GENAVB_ERR_STREAM_API_OPEN; + goto err_open; + } + + *batch_size = api_params.batch_size; + (*stream)->max_payload_size = api_params.max_payload_size; + } + + (*stream)->genavb = genavb; + + return GENAVB_SUCCESS; + +err_open: + disconnect_avtp(genavb, params); + +err_connect: + rtos_free(*stream); + +err_alloc: +err_genavb_handle_null: + *stream = NULL; + + return rc; +} + +int genavb_stream_receive(struct genavb_stream_handle const *handle, void *data, + unsigned int data_len, struct genavb_event *event, unsigned int *event_len) +{ + struct genavb_iovec data_iov, event_iov; + int rc; + + data_iov.iov_base = data; + data_iov.iov_len = data_len; + + if (event && event_len) { + event_iov.iov_base = event; + event_iov.iov_len = *event_len; + + rc = media_api_read((struct media_queue *)&handle->mqueue, &data_iov, 1, &event_iov, 1, event_len); + } else { + rc = media_api_read((struct media_queue *)&handle->mqueue, &data_iov, 1, NULL, 0, NULL); + } + + if (rc < 0) + return (-GENAVB_ERR_STREAM_RX); + + return rc; +} + + +int genavb_stream_set_callback(struct genavb_stream_handle const *handle, int (*callback)(void *), void *data) +{ + if (!handle->mqueue.id) + return -GENAVB_ERR_STREAM_INVALID; + + media_api_set_callback((struct media_queue *)&handle->mqueue, callback, data); + + return GENAVB_SUCCESS; +} + +int genavb_stream_enable_callback(struct genavb_stream_handle const *handle) +{ + if (media_api_enable_callback((struct media_queue *)&handle->mqueue) < 0) + return -GENAVB_ERR_STREAM_NO_CALLBACK; + + return GENAVB_SUCCESS; +} + +int genavb_stream_send(struct genavb_stream_handle const *handle, void const *data, + unsigned int data_len, struct genavb_event const *event, unsigned int event_len) +{ + struct genavb_iovec data_iov; + int rc; + + if (unlikely(!handle)) { + rc = -GENAVB_ERR_STREAM_INVALID; + goto err; + } + + if (unlikely((!event && event_len) || (!data && data_len))) { + rc = -GENAVB_ERR_STREAM_TX; + goto err; + } + + data_iov.iov_base = (void *)data; /* We need to remove the const qualifier here, or we would need to define 2 separate iovec structures for tx/rx. */ + data_iov.iov_len = data_len; + + rc = media_api_write((struct media_queue *)&handle->mqueue, &data_iov, 1, event, event_len); + if (rc < 0) + return (-GENAVB_ERR_STREAM_TX); + +err: + return rc; +} + +static int __avb_stream_destroy(struct genavb_stream_handle *handle) +{ + if (handle->mqueue.id) + media_api_close(&handle->mqueue); + + disconnect_avtp(handle->genavb, &handle->params); + + rtos_free(handle); + + return GENAVB_SUCCESS; +} + +int genavb_stream_destroy(struct genavb_stream_handle *handle) +{ + int rc; + + if (!handle) { + rc = -GENAVB_ERR_STREAM_INVALID; + goto err_handle_null; + } + + rc = __avb_stream_destroy(handle); + + return rc; + +err_handle_null: + return rc; +} diff --git a/api/rtos/timer.c b/api/rtos/timer.c new file mode 100644 index 0000000..cbe9a64 --- /dev/null +++ b/api/rtos/timer.c @@ -0,0 +1,131 @@ +/* + * Copyright 2019-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file timer.c + \brief timer public API for rtos + \details + \copyright Copyright 2019-2021, 2023-2024 NXP +*/ + +#include + +#include "api/timer.h" +#include "api/clock.h" +#include "genavb/error.h" +#include "common/types.h" + +#include "rtos_abstraction_layer.h" + +static void genavb_timer_callback(struct os_timer *t, int count) +{ + struct genavb_timer *timer = container_of(t, struct genavb_timer, os_t); + + if (timer->callback) + timer->callback(timer->data, count); +} + +int genavb_timer_create(struct genavb_timer **t, genavb_clock_id_t id, genavb_timer_f_t flags) +{ + os_clock_id_t os_id; + int rc; + unsigned int flags_os = 0; + + if (!t) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (flags & GENAVB_TIMERF_PPS) + flags_os |= OS_TIMER_FLAGS_PPS; + + if (flags & GENAVB_TIMERF_PERIODIC) + flags_os |= OS_TIMER_FLAGS_PERIODIC; + + if (id >= GENAVB_CLOCK_MAX) { + rc = -GENAVB_ERR_CLOCK; + goto out_set_null; + } + + os_id = genavb_clock_to_os_clock(id); + if (os_id >= OS_CLOCK_MAX) { + rc = -GENAVB_ERR_CLOCK; + goto out_set_null; + } + + *t = rtos_malloc(sizeof(struct genavb_timer)); + if (!*t) { + rc = -GENAVB_ERR_NO_MEMORY; + goto out_set_null; + } + + memset(*t, 0, sizeof(struct genavb_timer)); + + if (os_timer_create(&(*t)->os_t, os_id, flags_os, genavb_timer_callback, 0) < 0) { + rc = -GENAVB_ERR_TIMER; + goto out_free_timer; + } + + return GENAVB_SUCCESS; + +out_free_timer: + rtos_free(*t); + +out_set_null: + *t = NULL; +out: + return rc; +} + +int genavb_timer_set_callback(struct genavb_timer *t, void (*callback)(void *, int), + void *data) +{ + if (!t) + return GENAVB_ERR_INVALID; + + rtos_spin_lock(&rtos_global_spinlock, &rtos_global_key); + + t->data = data; + t->callback = callback; + + rtos_spin_unlock(&rtos_global_spinlock, rtos_global_key); + + return GENAVB_SUCCESS; +} + +int genavb_timer_start(struct genavb_timer *t, uint64_t value, uint64_t interval, + genavb_timer_f_t flags) +{ + unsigned int flags_os = 0; + + if (!t) + return -GENAVB_ERR_INVALID; + + if (flags & GENAVB_TIMERF_ABS) + flags_os |= OS_TIMER_FLAGS_ABSOLUTE; + + if (flags & GENAVB_TIMERF_PPS) + flags_os |= OS_TIMER_FLAGS_PPS; + + if (os_timer_start(&t->os_t, value, interval, 1, flags_os) < 0) + return -GENAVB_ERR_TIMER; + + return GENAVB_SUCCESS; +} + +void genavb_timer_stop(struct genavb_timer *t) +{ + if (t) + os_timer_stop(&t->os_t); +} + +void genavb_timer_destroy(struct genavb_timer *t) +{ + if (t) { + os_timer_destroy(&t->os_t); + rtos_free(t); + } +} diff --git a/api/rtos/vlan.c b/api/rtos/vlan.c new file mode 100644 index 0000000..e3107f9 --- /dev/null +++ b/api/rtos/vlan.c @@ -0,0 +1,62 @@ +/* + * Copyright 2022-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "genavb/vlan.h" + + +int genavb_vlan_update(uint16_t vid, struct genavb_vlan_port_map *map) +{ + int rc; + + rc = vlan_update(vid, false, map); + + return rc; +} + +int genavb_vlan_delete(uint16_t vid) +{ + int rc; + + rc = vlan_delete(vid, false); + + return rc; +} + +int genavb_vlan_read(uint16_t vid, bool *dynamic, struct genavb_vlan_port_map *map) +{ + int rc; + + rc = vlan_read(vid, dynamic, map); + + return rc; +} + +int genavb_vlan_dump(uint32_t *token, uint16_t *vid, bool *dynamic, struct genavb_vlan_port_map *map) +{ + int rc; + + rc = vlan_dump(token, vid, dynamic, map); + + return rc; +} + +int genavb_vlan_set_port_default(unsigned int port_id, uint16_t vid) +{ + int rc; + + rc = vlan_port_set_default(port_id, vid); + + return rc; +} + +int genavb_vlan_get_port_default(unsigned int port_id, uint16_t *vid) +{ + int rc; + + rc = vlan_port_get_default(port_id, vid); + + return rc; +} diff --git a/api/socket.c b/api/socket.c new file mode 100644 index 0000000..8e1b330 --- /dev/null +++ b/api/socket.c @@ -0,0 +1,388 @@ +/* +* Copyright 2018, 2020-2021, 2023-2024 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + \file socket.c + \brief control public API + \details + \copyright Copyright 2018, 2020-2021, 2023-2024 NXP +*/ + +#include "api/socket.h" + +#if defined(CONFIG_SOCKET) +#include "genavb/error.h" +#include "os/stdlib.h" + +int genavb_socket_rx_open(struct genavb_socket_rx **sock, genavb_sock_f_t flags, + struct genavb_socket_rx_params *params) +{ + int rc = 0; + + if (!sock) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (flags & GENAVB_SOCKF_ZEROCOPY) { + rc = -GENAVB_ERR_SOCKET_PARAMS; + goto out; + } + + *sock = os_malloc(sizeof(struct genavb_socket_rx)); + if (!*sock) { + rc = -GENAVB_ERR_NO_MEMORY; + goto out_set_null; + } + + os_memset(*sock, 0, sizeof(struct genavb_socket_rx)); + + os_memcpy(&(*sock)->params, params, sizeof(struct genavb_socket_rx_params)); + (*sock)->flags = flags; + + if (params->addr.port >= CFG_MAX_LOGICAL_PORTS) { + rc = -GENAVB_ERR_SOCKET_PARAMS; + goto out_free_socket; + } + + if (socket_rx_event_init(*sock) < 0) { + rc = -GENAVB_ERR_SOCKET_INIT; + goto out_free_socket; + } + + if (net_rx_init(&(*sock)->net, ¶ms->addr, NULL, (*sock)->priv) < 0) { + rc = -GENAVB_ERR_SOCKET_INIT; + goto out_event_exit; + } + + if (MAC_IS_MCAST(params->addr.u.l2.dst_mac)) { + if (net_add_multi(&(*sock)->net, params->addr.port, + params->addr.u.l2.dst_mac) < 0) + goto out_rx_exit; + } + + return GENAVB_SUCCESS; + +out_rx_exit: + net_rx_exit(&(*sock)->net); + +out_event_exit: + socket_rx_event_exit(*sock); + +out_free_socket: + os_free(*sock); + +out_set_null: + *sock = NULL; + +out: + return rc; +} + +int genavb_socket_tx_open(struct genavb_socket_tx **sock, genavb_sock_f_t flags, + struct genavb_socket_tx_params *params) +{ + int rc; + struct net_address *addr; + + if (!sock) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if ((flags & GENAVB_SOCKF_ZEROCOPY) || (flags & GENAVB_SOCKF_NONBLOCK)) { + rc = -GENAVB_ERR_SOCKET_PARAMS; + goto out; + } + + *sock = os_malloc(sizeof(struct genavb_socket_tx)); + if (!*sock) { + rc = -GENAVB_ERR_NO_MEMORY; + goto out; + } + os_memset(*sock, 0, sizeof(struct genavb_socket_tx)); + + os_memcpy(&(*sock)->params, params, sizeof(struct genavb_socket_tx_params)); + (*sock)->flags = flags; + + if (params->addr.ptype == PTYPE_L2) { + if (!(flags & GENAVB_SOCKF_RAW)) { + /* + * Prepare L2 header, src MAC address + * is inserted in transmit path. + */ + if (params->addr.vlan_id != VLAN_VID_NONE) { + (*sock)->header_len = net_add_eth_header( + (*sock)->header_template, + params->addr.u.l2.dst_mac, + ETHERTYPE_VLAN); + + (*sock)->header_len += net_add_vlan_header( + (*sock)->header_template + (*sock)->header_len, + ntohs(params->addr.u.l2.protocol), + ntohs(params->addr.vlan_id), params->addr.priority, 0); + } else { + (*sock)->header_len = net_add_eth_header( + (*sock)->header_template, + params->addr.u.l2.dst_mac, + ntohs(params->addr.u.l2.protocol)); + } + } + addr = ¶ms->addr; + } else if (params->addr.ptype == PTYPE_PTP) { + if (!(flags & GENAVB_SOCKF_RAW)) { + /* use hardcoded PTP settings */ + const u8 ptp_dst_mac[6] = MC_ADDR_PTP; + + (*sock)->header_len = net_add_eth_header( + (*sock)->header_template, + ptp_dst_mac, + ETHERTYPE_PTP); + } + addr = ¶ms->addr; + } else { + rc = -GENAVB_ERR_INVALID; + goto out_free_socket; + } + + if (net_tx_init(&(*sock)->net, addr) < 0) { + rc = -GENAVB_ERR_SOCKET_INIT; + goto out_free_socket; + } + + if (flags & GENAVB_SOCKF_RAW) + net_tx_enable_raw(&(*sock)->net); + + return GENAVB_SUCCESS; + +out_free_socket: + os_free(*sock); + +out: + if (sock) + *sock = NULL; + + return rc; +} + +int genavb_socket_rx(struct genavb_socket_rx *sock, void *buf, unsigned int len, uint64_t *ts) +{ + struct net_rx_desc *desc; + int rc; + unsigned int data_len; + + if (!sock) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (!buf) { + rc = -GENAVB_ERR_SOCKET_FAULT; + goto out; + } + + if (socket_rx_event_check(sock) < 0) { + rc = -GENAVB_ERR_SOCKET_INTR; + goto out; + } + + desc = __net_rx(&sock->net); + if (!desc) { + rc = -GENAVB_ERR_SOCKET_AGAIN; + goto out_rearm; + } + + if (sock->flags & GENAVB_SOCKF_RAW) + data_len = desc->len; + else + data_len = desc->len - (desc->l3_offset - desc->l2_offset); + + if (len < data_len) { + rc = -GENAVB_ERR_SOCKET_BUFLEN; + goto out_free_desc; + } + + if (sock->flags & GENAVB_SOCKF_RAW) + os_memcpy(buf, (uint8_t *)desc + desc->l2_offset, data_len); + else + os_memcpy(buf, (uint8_t *)desc + desc->l3_offset, data_len); + + if (ts) + *ts = desc->ts64; + + net_rx_free(desc); + + socket_rx_event_rearm(sock); + + return data_len; + +out_free_desc: + net_rx_free(desc); + +out_rearm: + socket_rx_event_rearm(sock); + +out: + return rc; +} + +static int __genavb_socket_tx(struct genavb_socket_tx *sock, void *buf, unsigned int len, uint32_t ts_priv) +{ + struct net_tx_desc *desc; + int rc; + unsigned int data_len; + + if (!sock) { + rc = -GENAVB_ERR_INVALID; + goto out; + } + + if (!buf) { + rc = -GENAVB_ERR_SOCKET_FAULT; + goto out; + } + + if (sock->flags & GENAVB_SOCKF_RAW) + data_len = len; + else + data_len = len + sock->header_len; + + desc = net_tx_alloc(&sock->net, data_len); + if (!desc) { + rc = -GENAVB_ERR_NO_MEMORY; + goto out; + } + + if (ts_priv) { + desc->priv = ts_priv; + desc->flags = NET_TX_FLAGS_HW_TS; + } + + if (sock->flags & GENAVB_SOCKF_RAW) + os_memcpy((uint8_t *)desc + desc->l2_offset, buf, data_len); + else { + os_memcpy((uint8_t *)desc + desc->l2_offset, + sock->header_template, sock->header_len); + os_memcpy((uint8_t *)desc + desc->l2_offset + sock->header_len, + buf, len); + } + + desc->len = data_len; + desc->port = sock->params.addr.port; + + if (net_tx(&sock->net, desc) < 0) { + rc = -GENAVB_ERR_SOCKET_TX; + goto out_free_desc; + } + + return GENAVB_SUCCESS; + +out_free_desc: + net_tx_free(desc); + +out: + return rc; +} + +int genavb_socket_tx(struct genavb_socket_tx *sock, void *buf, unsigned int len) +{ + return __genavb_socket_tx(sock, buf, len, 0); +} + +int genavb_socket_tx_ts(struct genavb_socket_tx *sock, void *buf, unsigned int len, unsigned int ts_priv) +{ + return __genavb_socket_tx(sock, buf, len, ts_priv); +} + +int genavb_socket_tx_get_ts(struct genavb_socket_tx *sock, uint64_t *ts, unsigned int *ts_priv) +{ + int ret; + + ret = net_tx_ts_get(&sock->net, ts, ts_priv); + if (ret <= 0) + return -1; + + return GENAVB_SUCCESS; +} + +void genavb_socket_rx_close(struct genavb_socket_rx *sock) +{ + struct net_address *addr; + + if (!sock) + return; + + addr = &sock->params.addr; + + if (MAC_IS_MCAST(addr->u.l2.dst_mac)) { + net_del_multi(&sock->net, addr->port, + addr->u.l2.dst_mac); + } + + socket_rx_event_exit(sock); + + net_rx_exit(&sock->net); + + os_free(sock); + + return; +} + +void genavb_socket_tx_close(struct genavb_socket_tx *sock) +{ + if (!sock) + return; + + net_tx_exit(&sock->net); + + os_free(sock); + + return; +} + +#else /* CONFIG_SOCKET */ + +int genavb_socket_rx_open(struct genavb_socket_rx **sock, genavb_sock_f_t flags, struct genavb_socket_rx_params *params) +{ + return -1; +} + +int genavb_socket_tx_open(struct genavb_socket_tx **sock, genavb_sock_f_t flags, struct genavb_socket_tx_params *params) +{ + return -1; +} + +int genavb_socket_rx(struct genavb_socket_rx *sock, void *buf, unsigned int len, uint64_t *ts) +{ + return -1; +} + +int genavb_socket_tx(struct genavb_socket_tx *sock, void *buf, unsigned int len) +{ + return -1; +} + +int genavb_socket_tx_ts(struct genavb_socket_tx *sock, void *buf, unsigned int len, unsigned int ts_priv) +{ + return -1; +} + +int genavb_socket_tx_get_ts(struct genavb_socket_tx *sock, uint64_t *ts, unsigned int *ts_priv) +{ + return -1; +} + +void genavb_socket_rx_close(struct genavb_socket_rx *sock) +{ + return; +} + +void genavb_socket_tx_close(struct genavb_socket_tx *sock) +{ + return; +} + +#endif /* CONFIG_SOCKET */ diff --git a/api/socket.h b/api/socket.h new file mode 100644 index 0000000..546f8e9 --- /dev/null +++ b/api/socket.h @@ -0,0 +1,43 @@ +/* + * Copyright 2018, 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file socket.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2018, 2020-2021, 2023 NXP +*/ + +#ifndef _PRIVATE_SOCKET_H +#define _PRIVATE_SOCKET_H + +#include "common/net.h" +#include "include/genavb/socket.h" + +#define HEADER_TEMPLATE_SIZE 18 + +struct genavb_socket_rx { + genavb_sock_f_t flags; + struct net_rx net; + struct genavb_socket_rx_params params; + unsigned long priv; +}; + +struct genavb_socket_tx { + genavb_sock_f_t flags; + struct net_tx net; + struct genavb_socket_tx_params params; + uint8_t header_template[HEADER_TEMPLATE_SIZE]; + int header_len; +}; + +int socket_rx_event_init(struct genavb_socket_rx *sock); +void socket_rx_event_exit(struct genavb_socket_rx *sock); +int socket_rx_event_check(struct genavb_socket_rx *sock); +void socket_rx_event_rearm(struct genavb_socket_rx *sock); + +#endif /* _PRIVATE_SOCKET_H */ diff --git a/api/streaming.c b/api/streaming.c new file mode 100644 index 0000000..ed7a603 --- /dev/null +++ b/api/streaming.c @@ -0,0 +1,158 @@ +/* +* Copyright 2018, 2021, 2023-2024 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + \file streaming.c + \brief GenAVB public API for linux + \details API definition for the GenAVB library + \copyright Copyright 2018, 2021, 2023-2024 NXP +*/ + +#include "os/string.h" +#include "os/stdlib.h" +#include "common/ipc.h" +#include "common/avtp.h" + +#include "streaming.h" +#include "control.h" + +unsigned int genavb_stream_presentation_offset(const struct genavb_stream_handle *handle) +{ + return stream_presentation_offset(handle->params.talker.max_transit_time, handle->params.talker.latency); +} + +int streaming_init(struct genavb_handle *genavb); + +#define AVTP_TIMEOUT 3000 +int connect_avtp(struct genavb_handle *genavb, struct genavb_stream_handle *stream) +{ + struct genavb_stream_params *params = &stream->params; + int rc; + unsigned int msg_type, msg_len; + + if (!(genavb->flags & AVTP_INITIALIZED)) { + rc = streaming_init(genavb); + if (rc < 0) + goto exit; + + genavb->flags |= AVTP_INITIALIZED; + } + + /* + * Send connect to AVTP + */ + rc = avb_ipc_send(&genavb->avtp_tx, IPC_AVTP_CONNECT, params, sizeof(*params), 0); + if (rc != GENAVB_SUCCESS) + goto exit; + + if (params->direction == AVTP_DIRECTION_LISTENER) { + struct ipc_avtp_listener_connect_response response; + + msg_len = sizeof(response); + rc = avb_ipc_receive_sync(&genavb->avtp_rx, &msg_type, &response, &msg_len, AVTP_TIMEOUT); + if (rc != GENAVB_SUCCESS) + goto exit; + + if (msg_type != IPC_AVTP_LISTENER_CONNECT_RESPONSE) { + rc = -GENAVB_ERR_CTRL_RX; + goto exit; + } + + if (os_memcmp(&response.stream_id, params->stream_id, 8)) { + rc = -GENAVB_ERR_CTRL_RX; + goto exit; + } + + if (response.status != GENAVB_SUCCESS){ + rc = -response.status; + goto exit; + } + + rc = response.status; + + } else { + struct ipc_avtp_talker_connect_response response; + + msg_len = sizeof(response); + rc = avb_ipc_receive_sync(&genavb->avtp_rx, &msg_type, &response, &msg_len, AVTP_TIMEOUT); + if (rc != GENAVB_SUCCESS) + goto exit; + + if (msg_type != IPC_AVTP_TALKER_CONNECT_RESPONSE) { + rc = -GENAVB_ERR_CTRL_RX; + goto exit; + } + + if (os_memcmp(&response.stream_id, params->stream_id, 8)) { + rc = -GENAVB_ERR_CTRL_RX; + goto exit; + } + + if (response.status != GENAVB_SUCCESS){ + rc = -response.status; + goto exit; + } + + rc = response.status; + + params->talker.latency = response.latency; + stream->batch = response.batch; + stream->max_payload_size = response.max_payload_size; + } + +exit: + return rc; +} + +int disconnect_avtp(struct genavb_handle *genavb, struct genavb_stream_params const *params) +{ + struct ipc_avtp_disconnect avtp_disconnect; + int rc; + struct ipc_avtp_disconnect_response avtp_disconnect_response; + unsigned int msg_type, msg_len; + + if (!(genavb->flags & AVTP_INITIALIZED)) { + rc = -GENAVB_ERR_CTRL_TX; + goto exit; + } + + /* + * Send talker disconnect to AVTP + */ + os_memcpy(&avtp_disconnect.stream_id, params->stream_id, 8); + avtp_disconnect.stream_class = params->stream_class; + avtp_disconnect.port = params->port; + avtp_disconnect.direction = params->direction; + + rc = avb_ipc_send(&genavb->avtp_tx, IPC_AVTP_DISCONNECT, &avtp_disconnect, sizeof(struct ipc_avtp_disconnect), 0); + if (rc != GENAVB_SUCCESS) + goto exit; + + msg_len = sizeof(struct ipc_avtp_disconnect_response); + rc = avb_ipc_receive_sync(&genavb->avtp_rx, &msg_type, &avtp_disconnect_response, &msg_len, AVTP_TIMEOUT); + if (rc != GENAVB_SUCCESS) + goto exit; + + if (msg_type != IPC_AVTP_DISCONNECT_RESPONSE) { + rc = -GENAVB_ERR_CTRL_RX; + goto exit; + } + + if (os_memcmp(&avtp_disconnect_response.stream_id, params->stream_id, 8)) { + rc = -GENAVB_ERR_CTRL_RX; + goto exit; + } + + if (avtp_disconnect_response.status != GENAVB_SUCCESS){ + rc = -avtp_disconnect_response.status; + goto exit; + } + + rc = avtp_disconnect_response.status; + +exit: + return rc; +} diff --git a/api/streaming.h b/api/streaming.h new file mode 100644 index 0000000..b73614f --- /dev/null +++ b/api/streaming.h @@ -0,0 +1,27 @@ +/* + * Copyright 2018, 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file streaming.h + \brief GenAVB API private streaming API includes + \details private definitions for the GenAVB library streaming API + + \copyright Copyright 2018, 2020-2021, 2023 NXP +*/ + +#ifndef _PRIVATE_STREAMING_H_ +#define _PRIVATE_STREAMING_H_ + +#include "genavb/genavb.h" + +#include "api/init.h" +#include "api_os/streaming.h" + +int connect_avtp(struct genavb_handle *genavb, struct genavb_stream_handle *stream); + +int disconnect_avtp(struct genavb_handle *genavb, struct genavb_stream_params const *params); + +#endif /* _PRIVATE_STREAMING_H_ */ diff --git a/api/timer.h b/api/timer.h new file mode 100644 index 0000000..fe4e7bb --- /dev/null +++ b/api/timer.h @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file timer.h + \brief GenAVB API private includes + \details private definitions for the GenAVB library + + \copyright Copyright 2019-2021, 2023 NXP +*/ + +#ifndef _PRIVATE_TIMER_H_ +#define _PRIVATE_TIMER_H_ + +#include "os/timer.h" + +struct genavb_timer { + struct os_timer os_t; + void (*callback)(void *, int); + void *data; +}; + +#endif /* _PRIVATE_TIMER_H_ */ diff --git a/apps/common/aem-manager/aem-audio-entities.cmake b/apps/common/aem-manager/aem-audio-entities.cmake new file mode 100644 index 0000000..2132f7e --- /dev/null +++ b/apps/common/aem-manager/aem-audio-entities.cmake @@ -0,0 +1,12 @@ +list(APPEND AEM_ENTITIES + ${CMAKE_CURRENT_LIST_DIR}/listener_audio_single.c + ${CMAKE_CURRENT_LIST_DIR}/listener_audio_single_milan.c + ${CMAKE_CURRENT_LIST_DIR}/listener_talker_audio_single.c + ${CMAKE_CURRENT_LIST_DIR}/listener_talker_audio_single_milan.c + ${CMAKE_CURRENT_LIST_DIR}/talker_audio_single.c + ${CMAKE_CURRENT_LIST_DIR}/talker_audio_single_milan.c + ${CMAKE_CURRENT_LIST_DIR}/talker_listener_audio_multi.c + ${CMAKE_CURRENT_LIST_DIR}/talker_listener_audio_multi_aaf.c + ${CMAKE_CURRENT_LIST_DIR}/talker_listener_audio_multi_format.c + ${CMAKE_CURRENT_LIST_DIR}/talker_listener_audio_default.c +) diff --git a/apps/common/aem-manager/aem-audio-video-entities.cmake b/apps/common/aem-manager/aem-audio-video-entities.cmake new file mode 100644 index 0000000..421174e --- /dev/null +++ b/apps/common/aem-manager/aem-audio-video-entities.cmake @@ -0,0 +1,3 @@ +list(APPEND AEM_ENTITIES + ${CMAKE_CURRENT_LIST_DIR}/talker_audio_video.c +) diff --git a/apps/common/aem-manager/aem-avnu-vertification-entities.cmake b/apps/common/aem-manager/aem-avnu-vertification-entities.cmake new file mode 100644 index 0000000..0aa1de3 --- /dev/null +++ b/apps/common/aem-manager/aem-avnu-vertification-entities.cmake @@ -0,0 +1,3 @@ +list(APPEND AEM_ENTITIES + ${CMAKE_CURRENT_LIST_DIR}/avnu_certification.c +) diff --git a/apps/common/aem-manager/aem-controller-entities.cmake b/apps/common/aem-manager/aem-controller-entities.cmake new file mode 100644 index 0000000..4580830 --- /dev/null +++ b/apps/common/aem-manager/aem-controller-entities.cmake @@ -0,0 +1,3 @@ +list(APPEND AEM_ENTITIES + ${CMAKE_CURRENT_LIST_DIR}/avdecc_controller.c +) diff --git a/apps/common/aem-manager/aem-manager-helpers.cmake b/apps/common/aem-manager/aem-manager-helpers.cmake new file mode 100644 index 0000000..d78873e --- /dev/null +++ b/apps/common/aem-manager/aem-manager-helpers.cmake @@ -0,0 +1,5 @@ +list(APPEND AEM_MANAGER_HELPERS_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/aem_manager_helpers.c +) + +set(AEM_MANAGER_HELPERS_HEADER_DIR ${CMAKE_CURRENT_LIST_DIR}) diff --git a/apps/common/aem-manager/aem-video-entities.cmake b/apps/common/aem-manager/aem-video-entities.cmake new file mode 100644 index 0000000..cbc6be9 --- /dev/null +++ b/apps/common/aem-manager/aem-video-entities.cmake @@ -0,0 +1,6 @@ +list(APPEND AEM_ENTITIES + ${CMAKE_CURRENT_LIST_DIR}/listener_video_multi.c + ${CMAKE_CURRENT_LIST_DIR}/listener_video_single.c + ${CMAKE_CURRENT_LIST_DIR}/talker_video_multi.c + ${CMAKE_CURRENT_LIST_DIR}/talker_video_single.c +) diff --git a/apps/common/aem-manager/aem_manager_helpers.c b/apps/common/aem-manager/aem_manager_helpers.c new file mode 100644 index 0000000..dab73a2 --- /dev/null +++ b/apps/common/aem-manager/aem_manager_helpers.c @@ -0,0 +1,430 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file aem_manager_helpers.c + \brief AEM Manager helper functions + \details Helper functions to handle AEM Manager structures + + \copyright Copyright 2014-2016 Freescale Semiconductor, Inc. + \copyright Copyright 2023-2024 NXP + All Rights Reserved. +*/ + + +#include +#include +#include + +#include "genavb/types.h" +#include "genavb/net_types.h" + +#include "genavb/aem.h" +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" +#include "genavb/helpers.h" + +#include "aem_manager_helpers.h" + +#define DESC_MIN 0 +#define DESC_MAX 0xffff + +extern void aem_print_level(int level, const char *format, ...); + +static void default_desc_fixup(struct aem_desc_hdr *aem_desc) +{ +} + +static int default_desc_check(struct aem_desc_hdr *aem_desc) +{ + return 0; +} + +static void default_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max) +{ +} + +static void default_desc_update_name(struct aem_desc_hdr *aem_desc, char *name) +{ +} + +int entity_desc_handler_init(struct aem_desc_handler *desc_handler) +{ + int i; + + for (i = 0; i < AEM_NUM_DESC_TYPES; i++) { + if (!desc_handler[i].print) + desc_handler[i].print = default_desc_print; + + if (!desc_handler[i].fixup) + desc_handler[i].fixup = default_desc_fixup; + + if (!desc_handler[i].check) + desc_handler[i].check = default_desc_check; + + if (!desc_handler[i].update_name) + desc_handler[i].update_name = default_desc_update_name; + } + + return 0; +} + +int entity_desc_check(struct aem_desc_hdr *aem_desc) +{ + struct entity_descriptor *entity; + unsigned short len; + + entity = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_ENTITY, 0, &len); + if (!entity) + return -1; + + if (ntohs(entity->current_configuration) > ntohs(entity->configurations_count)) + return -1; + + return 0; +} + +int configuration_desc_check(struct aem_desc_hdr *aem_desc) +{ + struct configuration_descriptor *configuration; + int desc_counts_count, desc_num, desc_type, desc_max; + int cfg, cfg_max; + int rc = 0; + + cfg_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_CONFIGURATION); + + for (cfg = 0; cfg < cfg_max; cfg++) { + configuration = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_CONFIGURATION, cfg, NULL); + if (!configuration) + return -1; + + for (desc_counts_count = 0; desc_counts_count < ntohs(configuration->descriptor_counts_count); desc_counts_count++) { + desc_type = ntohs(configuration->descriptors_counts[2 * desc_counts_count]); + desc_num = ntohs(configuration->descriptors_counts[2 * desc_counts_count + 1]); + + if (desc_type >= AEM_NUM_DESC_TYPES) { + aem_print_level(0, "aem(%p) configuration(%u) error: unsupported descriptor type(%u) in descriptors_count\n", aem_desc, cfg, desc_type); + rc = -1; + goto out; + } + + desc_max = aem_get_descriptor_max(aem_desc, desc_type); + + if (desc_num > desc_max) { + aem_print_level(0, "aem(%p) configuration(%u) error: descriptor count(%u) exceeds maximum(%u) for descriptor type(%u)\n", aem_desc, cfg, desc_num, desc_max, desc_type); + rc = -1; + goto out; + } + } + } + +out: + return rc; +} + +void aem_entity_fixup(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc) +{ + int i; + + for (i = 0; i < AEM_NUM_DESC_TYPES; i++) { + if (desc_handler[i].fixup) + desc_handler[i].fixup(aem_desc); + } +} + +int aem_entity_check(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc) +{ + int i; + int rc = 0; + + for (i = 0; i < AEM_NUM_DESC_TYPES; i++) { + if (desc_handler[i].check) { + rc = desc_handler[i].check(aem_desc); + if (rc < 0) + goto out; + } + } + +out: + return rc; +} + +void entity_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max) +{ + struct entity_descriptor *entity; + char string[128]; + unsigned short len; + + entity = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_ENTITY, 0, &len); + if (!entity) + return; + + aem_print_level(level, "Entity id: %016"PRIx64"\n", ntohll(entity->entity_id)); + h_strncpy(string, (char *)entity->entity_name, 64); + aem_print_level(level, " name: %s\n", string); + aem_print_level(level, " sources: %u\n", ntohs(entity->talker_stream_sources)); + aem_print_level(level, " sinks: %u\n", ntohs(entity->listener_stream_sinks)); + + desc_handler[AEM_DESC_TYPE_CONFIGURATION].print(desc_handler, aem_desc, level + 1, DESC_MIN, DESC_MAX); +} + +void entity_desc_update_name(struct aem_desc_hdr *aem_desc, char *name) +{ + struct entity_descriptor *entity; + unsigned short len; + + entity = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_ENTITY, 0, &len); + if (!entity) + return; + + /* Clear destination */ + memset((char *)entity->entity_name, 0, 64); + + len = strlen(name); + if (len > 64) + len = 64; + + memcpy((char *)entity->entity_name, name, len); +} + +void configuration_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max) +{ + struct configuration_descriptor *configuration; + char string[128]; + unsigned short len; + int cfg, cfg_max; + int i, type; + + cfg_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_CONFIGURATION); + + if (cfg_max > max) + cfg_max = max; + + for (cfg = min; cfg < cfg_max; cfg++) { + configuration = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_CONFIGURATION, cfg, &len); + if (!configuration) + continue; + + aem_print_level(level, "Configuration: %d\n", cfg); + h_strncpy(string, (char *)configuration->object_name, 64); + aem_print_level(level, " name: %s\n", string); + aem_print_level(level, " descriptors: %d\n", ntohs(configuration->descriptor_counts_count)); + + for (i = 0; i < ntohs(configuration->descriptor_counts_count); i++) { + type = ntohs(configuration->descriptors_counts[2 * i]); + + desc_handler[type].print(desc_handler, aem_desc, level + 1, DESC_MIN, DESC_MAX); + } + } +} + +void audio_unit_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max) +{ + struct audio_unit_descriptor *audio_unit; + char string[128]; + unsigned short len; + int i, i_max; + int _min, _max; + + i_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_AUDIO_UNIT); + + if (i_max > max) + i_max = max; + + for (i = min; i < i_max; i++) { + audio_unit = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_AUDIO_UNIT, i, &len); + if (!audio_unit) + continue; + + aem_print_level(level, "Audio Unit: %u\n", i); + h_strncpy(string, (void *)audio_unit->object_name, 64); + aem_print_level(level, " name: %s\n", string); + aem_print_level(level, " stream input port: %u\n", ntohs(audio_unit->number_of_stream_input_ports)); + aem_print_level(level, " stream output port: %u\n", ntohs(audio_unit->number_of_stream_output_ports)); + aem_print_level(level, " external input port: %u\n", ntohs(audio_unit->number_of_external_input_ports)); + aem_print_level(level, " external output port: %u\n", ntohs(audio_unit->number_of_external_output_ports)); + aem_print_level(level, " internal input port: %u\n", ntohs(audio_unit->number_of_internal_input_ports)); + aem_print_level(level, " internal output port: %u\n", ntohs(audio_unit->number_of_internal_output_ports)); + + _min = ntohs(audio_unit->base_stream_input_port); + _max = _min + ntohs(audio_unit->number_of_stream_input_ports); + desc_handler[AEM_DESC_TYPE_STREAM_PORT_INPUT].print(desc_handler, aem_desc, level + 1, _min, _max); + + _min = ntohs(audio_unit->base_stream_output_port); + _max = _min + ntohs(audio_unit->number_of_stream_output_ports); + desc_handler[AEM_DESC_TYPE_STREAM_PORT_OUTPUT].print(desc_handler, aem_desc, level + 1, _min, _max); + } +} + +void video_unit_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max) +{ + struct video_unit_descriptor *video_unit; + char string[128]; + unsigned short len; + int i, i_max; + int _min, _max; + + i_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_VIDEO_UNIT); + + if (i_max > max) + i_max = max; + + for (i = min; i < i_max; i++) { + video_unit = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_VIDEO_UNIT, i, &len); + if (!video_unit) + continue; + + aem_print_level(level, "Video Unit: %u\n", i); + h_strncpy(string, (void *)video_unit->object_name, 64); + aem_print_level(level, " name: %s\n", string); + aem_print_level(level, " stream input port: %u\n", ntohs(video_unit->number_of_stream_input_ports)); + aem_print_level(level, " stream output port: %u\n", ntohs(video_unit->number_of_stream_output_ports)); + aem_print_level(level, " external input port: %u\n", ntohs(video_unit->number_of_external_input_ports)); + aem_print_level(level, " external output port: %u\n", ntohs(video_unit->number_of_external_output_ports)); + aem_print_level(level, " internal input port: %u\n", ntohs(video_unit->number_of_internal_input_ports)); + aem_print_level(level, " internal output port: %u\n", ntohs(video_unit->number_of_internal_output_ports)); + + _min = ntohs(video_unit->base_stream_input_port); + _max = _min + ntohs(video_unit->number_of_stream_input_ports); + desc_handler[AEM_DESC_TYPE_STREAM_PORT_INPUT].print(desc_handler, aem_desc, level + 1, _min, _max); + + _min = ntohs(video_unit->base_stream_output_port); + _max = _min + ntohs(video_unit->number_of_stream_output_ports); + desc_handler[AEM_DESC_TYPE_STREAM_PORT_OUTPUT].print(desc_handler, aem_desc, level + 1, _min, _max); + } +} + +void stream_input_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max) +{ + struct stream_descriptor *stream; + char string[128]; + unsigned short len; + int i, i_max; + + i_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_STREAM_INPUT); + + if (i_max > max) + i_max = max; + + for (i = min; i < i_max; i++) { + stream = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_STREAM_INPUT, i, &len); + if (!stream) + continue; + + aem_print_level(level, "Stream Input: %u\n", i); + h_strncpy(string, (void *)stream->object_name, 64); + aem_print_level(level, " name: %s\n", string); + } +} + +void stream_input_desc_update_name(struct aem_desc_hdr *aem_desc, char *names) +{ + struct stream_descriptor *stream; + char *string; + unsigned short len; + int i, i_max; + + i_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_STREAM_INPUT); + + string = strtok(names, ";"); + for (i = 0; i < i_max; i++) { + stream = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_STREAM_INPUT, i, &len); + if (!stream) + continue; + + if (!string) + break; + + /* Clear destination */ + memset((char *)stream->object_name, 0, 64); + + len = strlen(string); + if (len > 64) + len = 64; + + memcpy((void *)stream->object_name, string, len); + + string = strtok(NULL, ";"); + } +} + +void stream_output_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max) +{ + struct stream_descriptor *stream; + char string[128]; + unsigned short len; + int i, i_max; + + i_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_STREAM_OUTPUT); + + if (i_max > max) + i_max = max; + + for (i = min; i < i_max; i++) { + stream = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_STREAM_OUTPUT, i, &len); + if (!stream) + continue; + + aem_print_level(level, "Stream Output: %u\n", i); + h_strncpy(string, (void *)stream->object_name, 64); + aem_print_level(level, " name: %s\n", string); + } +} + +void stream_output_desc_update_name(struct aem_desc_hdr *aem_desc, char *names) +{ + struct stream_descriptor *stream; + char *string; + unsigned short len; + int i, i_max; + + i_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_STREAM_OUTPUT); + + string = strtok(names, ";"); + for (i = 0; i < i_max; i++) { + stream = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_STREAM_OUTPUT, i, &len); + if (!stream) + continue; + + if (!string) + break; + + /* Clear destination */ + memset((char *)stream->object_name, 0, 64); + + len = strlen(string); + if (len > 64) + len = 64; + + memcpy((void *)stream->object_name, string, len); + + string = strtok(NULL, ";"); + } +} + +void control_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max) +{ + struct control_descriptor *control; + char string[128]; + unsigned short len; + int i, i_max; + + i_max = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_CONTROL); + + if (i_max > max) + i_max = max; + + for (i = min; i < i_max; i++) { + control = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_CONTROL, i, &len); + if (!control) + continue; + + aem_print_level(level, "Control: %u\n", i); + h_strncpy(string, (void *)control->object_name, 64); + aem_print_level(level, " name: %s\n", string); + } +} diff --git a/apps/common/aem-manager/aem_manager_helpers.h b/apps/common/aem-manager/aem_manager_helpers.h new file mode 100644 index 0000000..03055e8 --- /dev/null +++ b/apps/common/aem-manager/aem_manager_helpers.h @@ -0,0 +1,48 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + \file aem_manager_helpers.h + \brief AEM Manager helper functions + \details Helper functions to handle AEM Manager structures + + \copyright Copyright 2014-2016 Freescale Semiconductor, Inc. + \copyright Copyright 2023-2024 NXP + All Rights Reserved. +*/ + +#ifndef _COMMON_AEM_MANAGER_HELPERS_H_ +#define _COMMON_AEM_MANAGER_HELPERS_H_ + +#include "genavb/aem.h" +#include "genavb/aem_helpers.h" + +struct aem_desc_handler { + void (*print)(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max); + void (*fixup)(struct aem_desc_hdr *aem_desc); + int (*check)(struct aem_desc_hdr *aem_desc); + void (*update_name)(struct aem_desc_hdr *aem_desc, char *name); +}; + +int entity_desc_handler_init(struct aem_desc_handler *desc_handler); +int entity_desc_check(struct aem_desc_hdr *aem_desc); +int configuration_desc_check(struct aem_desc_hdr *aem_desc); +void aem_entity_fixup(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc); +int aem_entity_check(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc); + +void entity_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max); +void entity_desc_update_name(struct aem_desc_hdr *aem_desc, char *name); +void configuration_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max); +void audio_unit_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max); +void video_unit_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max); +void stream_input_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max); +void stream_input_desc_update_name(struct aem_desc_hdr *aem_desc, char *names); +void stream_output_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max); +void stream_output_desc_update_name(struct aem_desc_hdr *aem_desc, char *names); +void control_desc_print(struct aem_desc_handler *desc_handler, struct aem_desc_hdr *aem_desc, int level, int min, int max); + +#endif /* _COMMON_AEM_MANAGER_HELPERS_H_ */ diff --git a/apps/common/aem-manager/avdecc_controller.c b/apps/common/aem-manager/avdecc_controller.c new file mode 100644 index 0000000..8436fb3 --- /dev/null +++ b/apps/common/aem-manager/avdecc_controller.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Controller entity + @details Controller AVDECC entity definition +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "avdecc_controller.h" + +AEM_ENTITY_STORAGE(); + +void controller_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/avdecc_controller.h b/apps/common/aem-manager/avdecc_controller.h new file mode 100644 index 0000000..b16f039 --- /dev/null +++ b/apps/common/aem-manager/avdecc_controller.h @@ -0,0 +1,76 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _CONTROLLER_H_ +#define _CONTROLLER_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000080001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB controller" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_AEM_SUPPORTED) /* FIXME Needed or not for a controller? */ +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES (ADP_CONTROLLER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + + +#include "genavb/aem_entity.h" + +#endif /* _CONTROLLER_H_ */ diff --git a/apps/common/aem-manager/avnu_certification.c b/apps/common/aem-manager/avnu_certification.c new file mode 100644 index 0000000..22b16a5 --- /dev/null +++ b/apps/common/aem-manager/avnu_certification.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AVNU certification entity + @details Talker + Listener AVDECC entity definition with eight input streams + eight output streams +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "avnu_certification.h" + +AEM_ENTITY_STORAGE(); + +void avnu_certification_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/avnu_certification.h b/apps/common/aem-manager/avnu_certification.h new file mode 100644 index 0000000..0bb261c --- /dev/null +++ b/apps/common/aem-manager/avnu_certification.h @@ -0,0 +1,1029 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _AVNU_CERTIFICATION_H_ +#define _AVNU_CERTIFICATION_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f00000a0001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB device" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 8 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 8 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x00A0020140000100 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_1 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_2 "Stream input 2" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_2 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_2 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_2 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_3 "Stream input 3" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_3 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_3 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_3 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_3 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_4 "Stream input 4" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_4 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_4 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_4 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_4 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_4 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_4 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_4 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_4 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_4 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_4 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_5 "Stream input 5" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_5 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_5 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_5 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_5 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_5 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_5 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_5 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_5 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_5 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_5 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_6 "Stream input 6" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_6 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_6 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_6 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_6 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_6 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_6 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_6 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_6 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_6 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_6 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_7 "Stream input 7" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_7 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_7 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_7 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_7 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_7 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_7 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_7 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_7 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_7 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_7 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS { \ + AEM_CFG_STREAM_INPUT_DESCRIPTOR(0), AEM_CFG_STREAM_INPUT_DESCRIPTOR(1), AEM_CFG_STREAM_INPUT_DESCRIPTOR(2), AEM_CFG_STREAM_INPUT_DESCRIPTOR(3), \ + AEM_CFG_STREAM_INPUT_DESCRIPTOR(4), AEM_CFG_STREAM_INPUT_DESCRIPTOR(5), AEM_CFG_STREAM_INPUT_DESCRIPTOR(6), AEM_CFG_STREAM_INPUT_DESCRIPTOR(7), \ + } + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x00A0020140000100 //7.3.2 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_2 "Stream output 2" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_2 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_2 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_2 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_3 "Stream output 3" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_3 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_3 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_3 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_3 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_4 "Stream output 4" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_4 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_4 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_4 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_4 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_4 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_4 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_4 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_4 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_4 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_4 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_5 "Stream output 5" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_5 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_5 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_5 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_5 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_5 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_5 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_5 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_5 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_5 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_5 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_6 "Stream output 6" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_6 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_6 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_6 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_6 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_6 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_6 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_6 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_6 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_6 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_6 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_7 "Stream output 7" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_7 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_7 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_7 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_7 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_7 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_7 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_7 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_7 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_7 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_7 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS { \ + AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(2), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(4), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(5), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(6), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(7) \ + } + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input0" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_INPUT_NAME_1 "Jack input1" +#define AEM_CFG_JACK_INPUT_LOC_DESC_1 7 +#define AEM_CFG_JACK_INPUT_FLAGS_1 0 +#define AEM_CFG_JACK_INPUT_TYPE_1 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_1 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_1 0 + +#define AEM_CFG_JACK_INPUT_NAME_2 "Jack input2" +#define AEM_CFG_JACK_INPUT_LOC_DESC_2 7 +#define AEM_CFG_JACK_INPUT_FLAGS_2 0 +#define AEM_CFG_JACK_INPUT_TYPE_2 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_2 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_2 0 + +#define AEM_CFG_JACK_INPUT_NAME_3 "Jack input3" +#define AEM_CFG_JACK_INPUT_LOC_DESC_3 7 +#define AEM_CFG_JACK_INPUT_FLAGS_3 0 +#define AEM_CFG_JACK_INPUT_TYPE_3 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_3 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_3 0 + +#define AEM_CFG_JACK_INPUT_NAME_4 "Jack input4" +#define AEM_CFG_JACK_INPUT_LOC_DESC_4 7 +#define AEM_CFG_JACK_INPUT_FLAGS_4 0 +#define AEM_CFG_JACK_INPUT_TYPE_4 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_4 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_4 0 + +#define AEM_CFG_JACK_INPUT_NAME_5 "Jack input5" +#define AEM_CFG_JACK_INPUT_LOC_DESC_5 7 +#define AEM_CFG_JACK_INPUT_FLAGS_5 0 +#define AEM_CFG_JACK_INPUT_TYPE_5 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_5 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_5 0 + +#define AEM_CFG_JACK_INPUT_NAME_6 "Jack input6" +#define AEM_CFG_JACK_INPUT_LOC_DESC_6 7 +#define AEM_CFG_JACK_INPUT_FLAGS_6 0 +#define AEM_CFG_JACK_INPUT_TYPE_6 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_6 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_6 0 + +#define AEM_CFG_JACK_INPUT_NAME_7 "Jack input7" +#define AEM_CFG_JACK_INPUT_LOC_DESC_7 7 +#define AEM_CFG_JACK_INPUT_FLAGS_7 0 +#define AEM_CFG_JACK_INPUT_TYPE_7 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_7 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_7 0 + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS { \ + AEM_CFG_JACK_INPUT_DESCRIPTOR(0), AEM_CFG_JACK_INPUT_DESCRIPTOR(1), AEM_CFG_JACK_INPUT_DESCRIPTOR(2), AEM_CFG_JACK_INPUT_DESCRIPTOR(3), \ + AEM_CFG_JACK_INPUT_DESCRIPTOR(4), AEM_CFG_JACK_INPUT_DESCRIPTOR(5), AEM_CFG_JACK_INPUT_DESCRIPTOR(6), AEM_CFG_JACK_INPUT_DESCRIPTOR(7) \ + } + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output0" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_OUTPUT_NAME_1 "Jack output1" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_1 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_1 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_1 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_1 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_1 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_2 "Jack output2" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_2 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_2 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_2 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_2 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_2 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_3 "Jack output3" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_3 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_3 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_3 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_3 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_3 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_4 "Jack output4" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_4 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_4 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_4 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_4 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_4 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_5 "Jack output5" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_5 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_5 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_5 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_5 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_5 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_6 "Jack output6" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_6 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_6 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_6 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_6 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_6 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_7 "Jack output7" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_7 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_7 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_7 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_7 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_7 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS { \ + AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(1), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(2), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_JACK_OUTPUT_DESCRIPTOR(4), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(5), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(6), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(7), \ + } + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 (AEM_AVB_FLAGS_GPTP_SUPPORTED | AEM_AVB_FLAGS_SRP_SUPPORTED | AEM_AVB_FLAGS_GPTP_GRANDMASTER_SUPPORTED) +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 8 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_2 "Audio cluster 2" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_2 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_2 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_2 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_2 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_2 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_3 "Audio cluster 3" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_3 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_3 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_3 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_3 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_3 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_4 "Audio cluster 4" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_4 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_4 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_4 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_4 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_4 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_5 "Audio cluster 5" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_5 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_5 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_5 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_5 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_5 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_6 "Audio cluster 6" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_6 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_6 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_6 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_6 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_6 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_7 "Audio cluster 7" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_7 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_7 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_7 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_7 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_7 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_8 "Audio cluster 8" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_8 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_8 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_8 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_8 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_8 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_9 "Audio cluster 9" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_9 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_9 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_9 1 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_9 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_9 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_10 "Audio cluster 10" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_10 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_10 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_10 2 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_10 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_10 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_11 "Audio cluster 11" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_11 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_11 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_11 3 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_11 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_11 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_12 "Audio cluster 12" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_12 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_12 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_12 4 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_12 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_12 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_13 "Audio cluster 13" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_13 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_13 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_13 5 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_13 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_13 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_14 "Audio cluster 14" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_14 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_14 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_14 6 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_14 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_14 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_15 "Audio cluster 15" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_15 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_15 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_15 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_15 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_15 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS { \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(2), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(3), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(4), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(5), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(6), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(7), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(8), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(9), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(10), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(11), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(12), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(13), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(14), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(15) \ + } + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 16 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0004),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0004),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0004),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0004),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0005),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0005),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0005),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0005),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0006),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0006),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0006),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0006),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0007),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0007),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0007),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0007),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_1 1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_1 1 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_2 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_2 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_2 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_2 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_2 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_2 2 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_2 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_2 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_2 2 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_3 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_3 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_3 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_3 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_3 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_3 3 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_3 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_3 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_3 3 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_4 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_4 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_4 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_4 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_4 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_4 4 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_4 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_4 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_4 4 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_5 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_5 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_5 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_5 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_5 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_5 5 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_5 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_5 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_5 5 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_6 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_6 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_6 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_6 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_6 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_6 6 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_6 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_6 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_6 6 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_7 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_7 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_7 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_7 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_7 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_7 7 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_7 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_7 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_7 7 + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS { \ + AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(1), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(2), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(4), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(5), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(6), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(7) \ + } + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_1 1 + + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_2 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_2 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_2 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_2 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_2 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_2 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_2 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_2 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_2 2 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_3 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_3 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_3 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_3 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_3 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_3 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_3 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_3 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_3 3 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_4 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_4 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_4 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_4 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_4 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_4 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_4 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_4 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_4 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_5 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_5 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_5 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_5 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_5 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_5 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_5 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_5 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_5 1 + + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_6 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_6 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_6 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_6 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_6 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_6 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_6 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_6 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_6 2 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_7 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_7 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_7 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_7 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_7 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_7 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_7 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_7 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_7 3 + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS { \ + AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(1), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(2), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(3), \ + AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(4), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(5), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(6), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(7) \ + } + +#include "genavb/aem_entity.h" + +#endif /* _AVNU_CERTIFICATION_H_ */ diff --git a/apps/common/aem-manager/listener_audio_single.c b/apps/common/aem-manager/listener_audio_single.c new file mode 100644 index 0000000..390bfa9 --- /dev/null +++ b/apps/common/aem-manager/listener_audio_single.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Listener entity + @details Listener AVDECC entity definition with a single input stream +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "listener_audio_single.h" + +AEM_ENTITY_STORAGE(); + +void listener_audio_single_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/listener_audio_single.h b/apps/common/aem-manager/listener_audio_single.h new file mode 100644 index 0000000..a916111 --- /dev/null +++ b/apps/common/aem-manager/listener_audio_single.h @@ -0,0 +1,257 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _LISTENER_AUDIO_SINGLE_H_ +#define _LISTENER_AUDIO_SINGLE_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000010001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB listener" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED | ADP_ENTITY_ASSOCIATION_ID_SUPPORTED | ADP_ENTITY_ASSOCIATION_ID_VALID) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS {AEM_CFG_STREAM_INPUT_DESCRIPTOR(0)} + + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS {AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0)} + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0)} + + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _LISTENER_AUDIO_SINGLE_H_ */ diff --git a/apps/common/aem-manager/listener_audio_single_milan.c b/apps/common/aem-manager/listener_audio_single_milan.c new file mode 100644 index 0000000..94d499f --- /dev/null +++ b/apps/common/aem-manager/listener_audio_single_milan.c @@ -0,0 +1,22 @@ +/* + * Copyright 2021 NXP. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Listener entity + @details Listener AVDECC Milan entity definition with single input stream +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "listener_audio_single_milan.h" + +AEM_ENTITY_STORAGE(); + +void listener_audio_single_milan_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/listener_audio_single_milan.h b/apps/common/aem-manager/listener_audio_single_milan.h new file mode 100644 index 0000000..50c4021 --- /dev/null +++ b/apps/common/aem-manager/listener_audio_single_milan.h @@ -0,0 +1,272 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _LISTENER_AUDIO_SINGLE_MILAN_H_ +#define _LISTENER_AUDIO_SINGLE_MILAN_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f00000c0001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB Milan Listener" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED | ADP_ENTITY_VENDOR_UNIQUE_SUPPORTED /*| ADP_ENTITY_AEM_IDENTIFY_CONTROL_INDEX_VALID*/ | ADP_ENTITY_AEM_INTERFACE_INDEX_VALID) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 2 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 2 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x0205022000806000 //7.3.2 AAF 2chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 4000000 //32 buffers as socket queue size +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS {AEM_CFG_STREAM_INPUT_DESCRIPTOR(0)} + + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS {AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source 0" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 2 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 1 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 1 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1)} + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_1 1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_1 0 + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(1)} + + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control Channel 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + +#define AEM_CFG_CONTROL_NAME_1 "Volume Control Channel 1" +#define AEM_CFG_CONTROL_LOC_DESC_1 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_1 0 +#define AEM_CFG_CONTROL_CTRL_LAT_1 200 +#define AEM_CFG_CONTROL_DOMAIN_1 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_1 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_1 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_1 0 +#define AEM_CFG_CONTROL_NB_VALUES_1 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_1 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_1 1 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_1 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0), AEM_CFG_CONTROL_DESCRIPTOR(1)} + +#include "genavb/aem_entity.h" + +#endif /* _LISTENER_AUDIO_SINGLE_MILAN_H_ */ diff --git a/apps/common/aem-manager/listener_talker_audio_single.c b/apps/common/aem-manager/listener_talker_audio_single.c new file mode 100644 index 0000000..e6fb48a --- /dev/null +++ b/apps/common/aem-manager/listener_talker_audio_single.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Listener + Talker entity + @details Listener and Talker AVDECC entity definition with single input/output streams +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "listener_talker_audio_single.h" + +AEM_ENTITY_STORAGE(); + +void listener_talker_audio_single_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/listener_talker_audio_single.h b/apps/common/aem-manager/listener_talker_audio_single.h new file mode 100644 index 0000000..af7f184 --- /dev/null +++ b/apps/common/aem-manager/listener_talker_audio_single.h @@ -0,0 +1,347 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _LISTENER_TALKER_AUDIO_SINGLE_H_ +#define _LISTENER_TALKER_AUDIO_SINGLE_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f00000e0001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB device" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS {AEM_CFG_STREAM_INPUT_DESCRIPTOR(0)} + + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0)} + + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS {AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0)} + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 1 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 1 + + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1)} + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + + +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_1 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_1 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0), AEM_CFG_AUDIO_MAP_DESCRIPTOR(1)} + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0)} + + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _LISTENER_TALKER_AUDIO_SINGLE_H_ */ diff --git a/apps/common/aem-manager/listener_talker_audio_single_milan.c b/apps/common/aem-manager/listener_talker_audio_single_milan.c new file mode 100644 index 0000000..b8c9177 --- /dev/null +++ b/apps/common/aem-manager/listener_talker_audio_single_milan.c @@ -0,0 +1,22 @@ +/* + * Copyright 2021 NXP. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Listener + Talker entity + @details Listener and Talker AVDECC Milan entity definition with single input/output streams +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "listener_talker_audio_single_milan.h" + +AEM_ENTITY_STORAGE(); + +void listener_talker_audio_single_milan_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/listener_talker_audio_single_milan.h b/apps/common/aem-manager/listener_talker_audio_single_milan.h new file mode 100644 index 0000000..a57ad48 --- /dev/null +++ b/apps/common/aem-manager/listener_talker_audio_single_milan.h @@ -0,0 +1,443 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _LISTENER_TALKER_AUDIO_SINGLE_MILAN_H_ +#define _LISTENER_TALKER_AUDIO_SINGLE_MILAN_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f00000b0001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB Milan device" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED | ADP_ENTITY_VENDOR_UNIQUE_SUPPORTED /*| ADP_ENTITY_AEM_IDENTIFY_CONTROL_INDEX_VALID*/ | ADP_ENTITY_AEM_INTERFACE_INDEX_VALID) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 2 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 2 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 2 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0 (Audio)" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A) +#define AEM_CFG_STREAM_INPUT_FORMAT_0 0x0205022000406000 //7.3.2 AAF 1chan 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_INPUT_FORMAT_1 0x0205022000806000 //7.3.2 AAF 2chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_INPUT_FORMAT_2 0x0205022001006000 //7.3.2 AAF 4chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_INPUT_FORMAT_3 0x0205022001806000 //7.3.2 AAF 6chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_INPUT_FORMAT_4 0x0205022002006000 //7.3.2 AAF 8chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 AEM_CFG_STREAM_INPUT_FORMAT_1 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 5 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 4000000 // 32 buffers as socket queue size +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { \ + htonll(AEM_CFG_STREAM_INPUT_FORMAT_0), htonll(AEM_CFG_STREAM_INPUT_FORMAT_1), htonll(AEM_CFG_STREAM_INPUT_FORMAT_2), \ + htonll(AEM_CFG_STREAM_INPUT_FORMAT_3), htonll(AEM_CFG_STREAM_INPUT_FORMAT_4), \ + } + +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1 (Clock)" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 (AEM_STREAM_FLAG_CLASS_A) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 0x041060010000BB80 //7.3.2 CRF, 48kHz, 96 timestamp interval, 1 timestamp/packet +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 64000000 // 32 buffers as socket queue size +#define AEM_CFG_STREAM_INPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1) } + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS {AEM_CFG_STREAM_INPUT_DESCRIPTOR(0), AEM_CFG_STREAM_INPUT_DESCRIPTOR(1)} + + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0 (Audio)" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A) +#define AEM_CFG_STREAM_OUTPUT_FORMAT_0 0x0205022000406000 //7.3.2 AAF 1chan 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_OUTPUT_FORMAT_1 0x0205022000806000 //7.3.2 AAF 2chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_OUTPUT_FORMAT_2 0x0205022001006000 //7.3.2 AAF 4chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_OUTPUT_FORMAT_3 0x0205022001806000 //7.3.2 AAF 6chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_OUTPUT_FORMAT_4 0x0205022002006000 //7.3.2 AAF 8chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 AEM_CFG_STREAM_OUTPUT_FORMAT_1 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 5 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { \ + htonll(AEM_CFG_STREAM_OUTPUT_FORMAT_0), htonll(AEM_CFG_STREAM_OUTPUT_FORMAT_1), htonll(AEM_CFG_STREAM_OUTPUT_FORMAT_2), \ + htonll(AEM_CFG_STREAM_OUTPUT_FORMAT_3), htonll(AEM_CFG_STREAM_OUTPUT_FORMAT_4), \ + } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1 (Clock)" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 (AEM_STREAM_FLAG_CLASS_A) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 0x041060010000BB80 //7.3.2 CRF, 48kHz, 96 timestamp interval, 1 timestamp/packet +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1) } + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1)} + + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS {AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0)} + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source 0" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INTERNAL +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + +#define AEM_CFG_CLK_SOURCE_NAME_1 "Clock source 1" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_1 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_1 AEM_CLOCK_SOURCE_FLAGS_STREAM_ID +#define AEM_CFG_CLK_SOURCE_TYPE_1 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_1 1 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_1 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_1 1 + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0), AEM_CFG_CLK_SOURCE_DESCRIPTOR(1)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 2 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 2 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 2 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 1 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 1 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_2 "Audio cluster 2" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_2 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_2 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_2 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_2 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_2 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_2 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_2 1 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_2 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_3 "Audio cluster 3" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_3 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_3 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_3 1 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_3 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_3 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_3 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_3 1 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_3 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(2), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(3)} + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0000)\ + }\ + } + + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_1 0 + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(1)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_1 1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_1 0 + + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(1)} + + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control Channel 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + +#define AEM_CFG_CONTROL_NAME_1 "Volume Control Channel 1" +#define AEM_CFG_CONTROL_LOC_DESC_1 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_1 0 +#define AEM_CFG_CONTROL_CTRL_LAT_1 200 +#define AEM_CFG_CONTROL_DOMAIN_1 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_1 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_1 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_1 0 +#define AEM_CFG_CONTROL_NB_VALUES_1 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_1 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_1 1 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_1 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0), AEM_CFG_CONTROL_DESCRIPTOR(1)} + +#include "genavb/aem_entity.h" + +#endif /* _LISTENER_TALKER_AUDIO_SINGLE_MILAN_H_ */ diff --git a/apps/common/aem-manager/listener_video_multi.c b/apps/common/aem-manager/listener_video_multi.c new file mode 100644 index 0000000..dd2aebc --- /dev/null +++ b/apps/common/aem-manager/listener_video_multi.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Listener entity + @details Listener AVDECC entity definition with four input streams +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "listener_video_multi.h" + +AEM_ENTITY_STORAGE(); + +void listener_video_multi_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/listener_video_multi.h b/apps/common/aem-manager/listener_video_multi.h new file mode 100644 index 0000000..5479e35 --- /dev/null +++ b/apps/common/aem-manager/listener_video_multi.h @@ -0,0 +1,491 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _LISTENER_VIDEO_MULTI_H_ +#define _LISTENER_VIDEO_MULTI_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000030001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB listener" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_VIDEO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Video unit config */ +#define AEM_CFG_VIDEO_UNIT_NAME_0 "Video unit" +#define AEM_CFG_VIDEO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_OUT_PORT_0 8 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROL_BLOCK_0 0 + + +#define AEM_CFG_VIDEO_UNIT_DESCRIPTORS {AEM_CFG_VIDEO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x00C0000000000000 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(0x00C0000000000000) } + +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_1 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_2 "Stream input 2" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_2 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_2 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_2 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_3 "Stream input 3" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_3 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_3 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_3 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_3 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS {AEM_CFG_STREAM_INPUT_DESCRIPTOR(0),AEM_CFG_STREAM_INPUT_DESCRIPTOR(1),AEM_CFG_STREAM_INPUT_DESCRIPTOR(2),AEM_CFG_STREAM_INPUT_DESCRIPTOR(3)} + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Video cluster config */ +#define AEM_CFG_VIDEO_CLUSTER_NAME_0 "Video cluster 0" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_0 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_NAME_1 "Video cluster 1" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 1000000 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_1 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_NAME_2 "Video cluster 2" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_2 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_2 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_2 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_2 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_2 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_2 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_2 AEM_CFG_VIDEO_CLUSTER_FORMAT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_3 "Video cluster 3" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_3 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_3 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_3 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_3 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_3 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_3 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_3 AEM_CFG_VIDEO_CLUSTER_FORMAT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_4 "Video cluster 4" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_4 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_4 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_4 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_4 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_4 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_4 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_4 AEM_CFG_VIDEO_CLUSTER_FORMAT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_5 "Video cluster 5" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_5 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_5 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_5 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_5 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_5 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_5 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_5 AEM_CFG_VIDEO_CLUSTER_FORMAT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_6 "Video cluster 6" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_6 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_6 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_6 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_6 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_6 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_6 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_6 AEM_CFG_VIDEO_CLUSTER_FORMAT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_7 "Video cluster 7" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_7 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_7 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_7 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_7 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_7 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_7 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_7 AEM_CFG_VIDEO_CLUSTER_FORMAT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 + +#define AEM_CFG_VIDEO_CLUSTER_DESCRIPTORS {AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(0),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(1),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(2),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(3),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(4),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(5),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(6),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(7)} + + +/* Video map config */ +#define AEM_CFG_VIDEO_MAP_NB_MAPPINGS_0 8 +#define AEM_CFG_VIDEO_MAP_MAPPINGS_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0002),\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0003),\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0004),\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0005),\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0006),\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0007),\ + }\ + } + +#define AEM_CFG_VIDEO_MAP_DESCRIPTORS {AEM_CFG_VIDEO_MAP_DESCRIPTOR(0)} + + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _LISTENER_VIDEO_MULTI_H_ */ diff --git a/apps/common/aem-manager/listener_video_single.c b/apps/common/aem-manager/listener_video_single.c new file mode 100644 index 0000000..094ddc9 --- /dev/null +++ b/apps/common/aem-manager/listener_video_single.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Listener entity + @details Listener AVDECC entity definition with a single input stream +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "listener_video_single.h" + +AEM_ENTITY_STORAGE(); + +void listener_video_single_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/listener_video_single.h b/apps/common/aem-manager/listener_video_single.h new file mode 100644 index 0000000..3ce1e25 --- /dev/null +++ b/apps/common/aem-manager/listener_video_single.h @@ -0,0 +1,284 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _LISTENER_VIDEO_SINGLE_H_ +#define _LISTENER_VIDEO_SINGLE_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000030001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB listener" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED | ADP_ENTITY_ASSOCIATION_ID_SUPPORTED | ADP_ENTITY_ASSOCIATION_ID_VALID) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_VIDEO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Video unit config */ +#define AEM_CFG_VIDEO_UNIT_NAME_0 "Video unit" +#define AEM_CFG_VIDEO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_OUT_PORT_0 2 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROL_BLOCK_0 0 + + +#define AEM_CFG_VIDEO_UNIT_DESCRIPTORS {AEM_CFG_VIDEO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x00C0000000000000 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 0x0302010000000000 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1) } + + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS {AEM_CFG_STREAM_INPUT_DESCRIPTOR(0), AEM_CFG_STREAM_INPUT_DESCRIPTOR(1)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 2 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Video cluster config */ +#define AEM_CFG_VIDEO_CLUSTER_NAME_0 "Video cluster 0" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_0 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_NAME_1 "Video cluster 1" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 1000000 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_1 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_DESCRIPTORS {AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(0),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(1)} + + +/* Video map config */ +#define AEM_CFG_VIDEO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_VIDEO_MAP_MAPPINGS_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + }\ + } + +#define AEM_CFG_VIDEO_MAP_DESCRIPTORS {AEM_CFG_VIDEO_MAP_DESCRIPTOR(0)} + + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _LISTENER_VIDEO_SINGLE_H_ */ diff --git a/apps/common/aem-manager/talker_audio_single.c b/apps/common/aem-manager/talker_audio_single.c new file mode 100644 index 0000000..69be0c2 --- /dev/null +++ b/apps/common/aem-manager/talker_audio_single.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker entity + @details Talker AVDECC entity definition with a single output stream +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_audio_single.h" + +AEM_ENTITY_STORAGE(); + +void talker_audio_single_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_audio_single.h b/apps/common/aem-manager/talker_audio_single.h new file mode 100644 index 0000000..1d86539 --- /dev/null +++ b/apps/common/aem-manager/talker_audio_single.h @@ -0,0 +1,241 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TALKER_AUDIO_SINGLE_H_ +#define _TALKER_AUDIO_SINGLE_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000020001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB talker" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(0x00A0020240000200) } + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0)} + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0)} + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0)} + + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_AUDIO_SINGLE_H_ */ diff --git a/apps/common/aem-manager/talker_audio_single_milan.c b/apps/common/aem-manager/talker_audio_single_milan.c new file mode 100644 index 0000000..1721c3c --- /dev/null +++ b/apps/common/aem-manager/talker_audio_single_milan.c @@ -0,0 +1,22 @@ +/* + * Copyright 2021 NXP. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker entity + @details Talker AVDECC Milan entity definition with single output stream +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_audio_single_milan.h" + +AEM_ENTITY_STORAGE(); + +void talker_audio_single_milan_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_audio_single_milan.h b/apps/common/aem-manager/talker_audio_single_milan.h new file mode 100644 index 0000000..c15c901 --- /dev/null +++ b/apps/common/aem-manager/talker_audio_single_milan.h @@ -0,0 +1,259 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020-2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TALKER_AUDIO_SINGLE_MILAN_H_ +#define _TALKER_AUDIO_SINGLE_MILAN_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f00000d0001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB Milan talker" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED | ADP_ENTITY_VENDOR_UNIQUE_SUPPORTED /*| ADP_ENTITY_AEM_IDENTIFY_CONTROL_INDEX_VALID*/ | ADP_ENTITY_AEM_INTERFACE_INDEX_VALID) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 2 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x0205022000806000 //7.3.2 AAF 2chans 32/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0)} + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source 0" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INTERNAL +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 2 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 1 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 1 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 1 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1)} + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0000)\ + }\ + } + + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_1 0 + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(1)} + + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_AUDIO_SINGLE_MILAN_H_ */ diff --git a/apps/common/aem-manager/talker_audio_video.c b/apps/common/aem-manager/talker_audio_video.c new file mode 100644 index 0000000..ec31421 --- /dev/null +++ b/apps/common/aem-manager/talker_audio_video.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker entity + @details Talker AVDECC entity definition with one audio and one video streams +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_audio_video.h" + +AEM_ENTITY_STORAGE(); + +void talker_audio_video_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_audio_video.h b/apps/common/aem-manager/talker_audio_video.h new file mode 100644 index 0000000..393ae1f --- /dev/null +++ b/apps/common/aem-manager/talker_audio_video.h @@ -0,0 +1,473 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TALKER_AUDIO_VIDEO_H_ +#define _TALKER_AUDIO_VIDEO_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000050001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB talker" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED | ADP_ENTITY_ASSOCIATION_ID_SUPPORTED | ADP_ENTITY_ASSOCIATION_ID_VALID) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_VIDEO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + +/* Video unit config */ +#define AEM_CFG_VIDEO_UNIT_NAME_0 "Video unit" +#define AEM_CFG_VIDEO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_OUT_PORT_0 1 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_IN_PORT_0 1 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_IN_PORT_0 1 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROLS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROL_BLOCK_0 0 + + +#define AEM_CFG_VIDEO_UNIT_DESCRIPTORS {AEM_CFG_VIDEO_UNIT_DESCRIPTOR(0)} + + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 0x00C0000000000000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 { htonll(0x00C0000000000000) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_2 "Stream output 2" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_2 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_2 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_2 (AEM_STREAM_FLAG_CLASS_A |AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2 0x0302010000000000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_2 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_2 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_2 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_2 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_2 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_2 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_2 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_2 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_2 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_2 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_2 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_2 { htonll(0x0302010000000000) } + + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(2)} + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input0" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_INPUT_NAME_1 "Jack input1" +#define AEM_CFG_JACK_INPUT_LOC_DESC_1 7 +#define AEM_CFG_JACK_INPUT_FLAGS_1 0 +#define AEM_CFG_JACK_INPUT_TYPE_1 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_1 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_1 0 + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0), AEM_CFG_JACK_INPUT_DESCRIPTOR(1)} + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_1 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_1 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_1 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_1 2 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_1 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_1 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_1 0 + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0),AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(1)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0)} + +/* Video cluster config */ +#define AEM_CFG_VIDEO_CLUSTER_NAME_0 "Video cluster 0" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_0 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_NAME_1 "Video cluster 1" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_1 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_DESCRIPTORS {AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(0),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(1)} + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + +/* Video map config */ +#define AEM_CFG_VIDEO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_VIDEO_MAP_MAPPINGS_0 {\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + }\ + } + + +#define AEM_CFG_VIDEO_MAP_DESCRIPTORS {AEM_CFG_VIDEO_MAP_DESCRIPTOR(0)} + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_1 1 + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(1)} + +/* Control for volume, in percent */ +#define AEM_CFG_CONTROL_NAME_0 "Volume Control" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + +/* Control for Enable/Disable, used for media play/stop */ +#define AEM_CFG_CONTROL_NAME_1 "Play/Stop" +#define AEM_CFG_CONTROL_LOC_DESC_1 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_1 0 +#define AEM_CFG_CONTROL_CTRL_LAT_1 200 +#define AEM_CFG_CONTROL_DOMAIN_1 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_1 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_1 AEM_CONTROL_TYPE_ENABLE +#define AEM_CFG_CONTROL_RESET_TIME_1 0 +#define AEM_CFG_CONTROL_NB_VALUES_1 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_1 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_1 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_1 {\ + .linear_int8 = {{0, 255, 255, 0, 0, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_UNITLESS)), 0}}} + +/* Control for media track number (0 to 255) */ +#define AEM_CFG_CONTROL_NAME_2 "Media track" +#define AEM_CFG_CONTROL_LOC_DESC_2 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_2 0 +#define AEM_CFG_CONTROL_CTRL_LAT_2 200 +#define AEM_CFG_CONTROL_DOMAIN_2 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_2 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_2 AEM_CONTROL_TYPE_MEDIA_TRACK +#define AEM_CFG_CONTROL_RESET_TIME_2 0 +#define AEM_CFG_CONTROL_NB_VALUES_2 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_2 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_2 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_2 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_2 {\ + .linear_int8 = {{0, 255, 1, 0, 0, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_COUNT)), 0}}} + +/* Control for media track number (0 to 255) */ +#define AEM_CFG_CONTROL_NAME_3 "Media track name\0" +#define AEM_CFG_CONTROL_LOC_DESC_3 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_3 0 +#define AEM_CFG_CONTROL_CTRL_LAT_3 200 +#define AEM_CFG_CONTROL_DOMAIN_3 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_3 AEM_CONTROL_SET_VALUE_TYPE(1, 0, AEM_CONTROL_UTF8) +#define AEM_CFG_CONTROL_TYPE_3 AEM_CONTROL_TYPE_MEDIA_TRACK_NAME +#define AEM_CFG_CONTROL_RESET_TIME_3 0 +#define AEM_CFG_CONTROL_NB_VALUES_3 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_3 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_3 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_3 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_3 {\ + .utf8.string = "Undefined\0" } + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0), AEM_CFG_CONTROL_DESCRIPTOR(1), AEM_CFG_CONTROL_DESCRIPTOR(2), AEM_CFG_CONTROL_DESCRIPTOR(3)} + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_AUDIO_VIDEO_H_ */ diff --git a/apps/common/aem-manager/talker_listener_audio_default.c b/apps/common/aem-manager/talker_listener_audio_default.c new file mode 100644 index 0000000..fe8478a --- /dev/null +++ b/apps/common/aem-manager/talker_listener_audio_default.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker + Listener entity + @details Talker + Listener AVDECC entity definition with eight input streams + eight output streams and IEC 61883-6 format +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_listener_audio_default.h" + +AEM_ENTITY_STORAGE(); + +void talker_listener_audio_default_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_listener_audio_default.h b/apps/common/aem-manager/talker_listener_audio_default.h new file mode 100644 index 0000000..4e4ae82 --- /dev/null +++ b/apps/common/aem-manager/talker_listener_audio_default.h @@ -0,0 +1,424 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AECP AEM entity common definitions +*/ + +#ifndef _TALKER_LISTENER_AUDIO_DEFAULT_H_ +#define _TALKER_LISTENER_AUDIO_DEFAULT_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049fff00000001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB device" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_STREAM_SOURCES 3 +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_STREAM_SINKS 3 +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + +#define AEM_CFG_ENTITY_DESCRIPTORS { AEM_CFG_ENTITY_DESCRIPTOR } + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x0205021800806000 // AAF 2chans 24/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1) } + +#define AEM_CFG_STREAM_INPUT_NAME_2 "Stream input 2" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_2 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_2 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_2 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2) } + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS {AEM_CFG_STREAM_INPUT_DESCRIPTOR(0), AEM_CFG_STREAM_INPUT_DESCRIPTOR(1), AEM_CFG_STREAM_INPUT_DESCRIPTOR(2)} + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x0205021800806000 // AAF 2chans 24/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_2 "Stream output 2" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_2 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_2 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_2 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2) } + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(2)} + + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS {AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0)} + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 1 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 1 + + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1)} + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + + +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_1 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_1 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0), AEM_CFG_AUDIO_MAP_DESCRIPTOR(1)} + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0)} + + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_LISTENER_AUDIO_DEFAULT_H_ */ diff --git a/apps/common/aem-manager/talker_listener_audio_multi.c b/apps/common/aem-manager/talker_listener_audio_multi.c new file mode 100644 index 0000000..eafd4f7 --- /dev/null +++ b/apps/common/aem-manager/talker_listener_audio_multi.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker + Listener entity + @details Talker + Listener AVDECC entity definition with eight input streams + eight output streams and IEC 61883-6 format +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_listener_audio_multi.h" + +AEM_ENTITY_STORAGE(); + +void talker_listener_audio_multi_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_listener_audio_multi.h b/apps/common/aem-manager/talker_listener_audio_multi.h new file mode 100644 index 0000000..64cd22a --- /dev/null +++ b/apps/common/aem-manager/talker_listener_audio_multi.h @@ -0,0 +1,1047 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TALKER_LISTENER_AUDIO_MULTI_H_ +#define _TALKER_LISTENER_AUDIO_MULTI_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000070001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB device" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 8 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 8 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_1 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_2 "Stream input 2" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_2 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_2 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_2 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_3 "Stream input 3" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_3 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_3 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_3 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_3 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_4 "Stream input 4" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_4 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_4 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_4 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_4 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_4 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_4 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_4 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_4 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_4 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_4 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_5 "Stream input 5" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_5 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_5 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_5 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_5 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_5 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_5 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_5 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_5 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_5 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_5 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_6 "Stream input 6" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_6 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_6 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_6 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_6 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_6 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_6 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_6 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_6 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_6 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_6 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_7 "Stream input 7" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_7 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_7 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_7 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_7 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_7 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_7 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_7 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_7 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_7 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_7 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS { \ + AEM_CFG_STREAM_INPUT_DESCRIPTOR(0), AEM_CFG_STREAM_INPUT_DESCRIPTOR(1), AEM_CFG_STREAM_INPUT_DESCRIPTOR(2), AEM_CFG_STREAM_INPUT_DESCRIPTOR(3), \ + AEM_CFG_STREAM_INPUT_DESCRIPTOR(4), AEM_CFG_STREAM_INPUT_DESCRIPTOR(5), AEM_CFG_STREAM_INPUT_DESCRIPTOR(6), AEM_CFG_STREAM_INPUT_DESCRIPTOR(7), \ + } + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_2 "Stream output 2" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_2 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_2 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_2 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_3 "Stream output 3" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_3 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_3 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_3 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_3 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_4 "Stream output 4" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_4 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_4 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_4 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_4 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_4 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_4 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_4 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_4 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_4 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_4 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_5 "Stream output 5" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_5 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_5 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_5 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_5 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_5 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_5 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_5 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_5 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_5 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_5 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_6 "Stream output 6" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_6 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_6 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_6 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_6 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_6 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_6 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_6 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_6 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_6 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_6 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_7 "Stream output 7" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_7 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_7 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_7 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_7 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_7 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_7 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_7 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_7 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_7 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_7 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS { \ + AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(2), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(4), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(5), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(6), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(7) \ + } + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input0" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_INPUT_NAME_1 "Jack input1" +#define AEM_CFG_JACK_INPUT_LOC_DESC_1 7 +#define AEM_CFG_JACK_INPUT_FLAGS_1 0 +#define AEM_CFG_JACK_INPUT_TYPE_1 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_1 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_1 0 + +#define AEM_CFG_JACK_INPUT_NAME_2 "Jack input2" +#define AEM_CFG_JACK_INPUT_LOC_DESC_2 7 +#define AEM_CFG_JACK_INPUT_FLAGS_2 0 +#define AEM_CFG_JACK_INPUT_TYPE_2 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_2 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_2 0 + +#define AEM_CFG_JACK_INPUT_NAME_3 "Jack input3" +#define AEM_CFG_JACK_INPUT_LOC_DESC_3 7 +#define AEM_CFG_JACK_INPUT_FLAGS_3 0 +#define AEM_CFG_JACK_INPUT_TYPE_3 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_3 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_3 0 + +#define AEM_CFG_JACK_INPUT_NAME_4 "Jack input4" +#define AEM_CFG_JACK_INPUT_LOC_DESC_4 7 +#define AEM_CFG_JACK_INPUT_FLAGS_4 0 +#define AEM_CFG_JACK_INPUT_TYPE_4 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_4 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_4 0 + +#define AEM_CFG_JACK_INPUT_NAME_5 "Jack input5" +#define AEM_CFG_JACK_INPUT_LOC_DESC_5 7 +#define AEM_CFG_JACK_INPUT_FLAGS_5 0 +#define AEM_CFG_JACK_INPUT_TYPE_5 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_5 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_5 0 + +#define AEM_CFG_JACK_INPUT_NAME_6 "Jack input6" +#define AEM_CFG_JACK_INPUT_LOC_DESC_6 7 +#define AEM_CFG_JACK_INPUT_FLAGS_6 0 +#define AEM_CFG_JACK_INPUT_TYPE_6 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_6 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_6 0 + +#define AEM_CFG_JACK_INPUT_NAME_7 "Jack input7" +#define AEM_CFG_JACK_INPUT_LOC_DESC_7 7 +#define AEM_CFG_JACK_INPUT_FLAGS_7 0 +#define AEM_CFG_JACK_INPUT_TYPE_7 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_7 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_7 0 + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS { \ + AEM_CFG_JACK_INPUT_DESCRIPTOR(0), AEM_CFG_JACK_INPUT_DESCRIPTOR(1), AEM_CFG_JACK_INPUT_DESCRIPTOR(2), AEM_CFG_JACK_INPUT_DESCRIPTOR(3), \ + AEM_CFG_JACK_INPUT_DESCRIPTOR(4), AEM_CFG_JACK_INPUT_DESCRIPTOR(5), AEM_CFG_JACK_INPUT_DESCRIPTOR(6), AEM_CFG_JACK_INPUT_DESCRIPTOR(7) \ + } + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output0" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_OUTPUT_NAME_1 "Jack output1" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_1 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_1 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_1 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_1 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_1 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_2 "Jack output2" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_2 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_2 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_2 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_2 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_2 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_3 "Jack output3" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_3 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_3 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_3 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_3 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_3 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_4 "Jack output4" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_4 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_4 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_4 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_4 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_4 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_5 "Jack output5" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_5 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_5 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_5 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_5 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_5 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_6 "Jack output6" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_6 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_6 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_6 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_6 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_6 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_7 "Jack output7" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_7 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_7 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_7 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_7 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_7 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS { \ + AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(1), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(2), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_JACK_OUTPUT_DESCRIPTOR(4), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(5), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(6), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(7), \ + } + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 8 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_2 "Audio cluster 2" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_2 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_2 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_2 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_2 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_2 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_3 "Audio cluster 3" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_3 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_3 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_3 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_3 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_3 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_4 "Audio cluster 4" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_4 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_4 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_4 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_4 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_4 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_5 "Audio cluster 5" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_5 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_5 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_5 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_5 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_5 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_6 "Audio cluster 6" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_6 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_6 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_6 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_6 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_6 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_7 "Audio cluster 7" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_7 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_7 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_7 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_7 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_7 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_8 "Audio cluster 8" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_8 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_8 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_8 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_8 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_8 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_9 "Audio cluster 9" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_9 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_9 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_9 1 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_9 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_9 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_10 "Audio cluster 10" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_10 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_10 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_10 2 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_10 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_10 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_11 "Audio cluster 11" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_11 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_11 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_11 3 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_11 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_11 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_12 "Audio cluster 12" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_12 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_12 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_12 4 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_12 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_12 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_13 "Audio cluster 13" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_13 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_13 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_13 5 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_13 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_13 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_14 "Audio cluster 14" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_14 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_14 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_14 6 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_14 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_14 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_15 "Audio cluster 15" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_15 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_15 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_15 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_15 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_15 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS { \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(2), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(3), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(4), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(5), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(6), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(7), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(8), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(9), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(10), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(11), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(12), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(13), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(14), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(15) \ + } + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 16 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0004),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0004),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0004),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0004),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0005),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0005),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0005),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0005),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0006),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0006),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0006),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0006),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0007),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0007),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0007),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0007),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_1 1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_1 1 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_2 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_2 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_2 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_2 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_2 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_2 2 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_2 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_2 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_2 2 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_3 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_3 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_3 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_3 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_3 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_3 3 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_3 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_3 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_3 3 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_4 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_4 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_4 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_4 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_4 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_4 4 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_4 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_4 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_4 4 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_5 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_5 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_5 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_5 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_5 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_5 5 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_5 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_5 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_5 5 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_6 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_6 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_6 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_6 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_6 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_6 6 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_6 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_6 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_6 6 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_7 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_7 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_7 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_7 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_7 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_7 7 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_7 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_7 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_7 7 + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS { \ + AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(1), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(2), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(4), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(5), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(6), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(7) \ + } + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_1 1 + + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_2 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_2 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_2 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_2 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_2 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_2 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_2 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_2 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_2 2 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_3 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_3 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_3 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_3 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_3 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_3 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_3 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_3 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_3 3 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_4 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_4 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_4 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_4 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_4 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_4 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_4 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_4 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_4 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_5 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_5 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_5 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_5 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_5 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_5 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_5 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_5 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_5 1 + + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_6 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_6 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_6 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_6 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_6 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_6 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_6 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_6 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_6 2 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_7 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_7 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_7 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_7 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_7 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_7 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_7 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_7 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_7 3 + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS { \ + AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(1), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(2), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(3), \ + AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(4), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(5), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(6), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(7) \ + } + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_LISTENER_AUDIO_MULTI_H_ */ diff --git a/apps/common/aem-manager/talker_listener_audio_multi_aaf.c b/apps/common/aem-manager/talker_listener_audio_multi_aaf.c new file mode 100644 index 0000000..1616c77 --- /dev/null +++ b/apps/common/aem-manager/talker_listener_audio_multi_aaf.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker + Listener entity + @details Talker + Listener AVDECC entity definition with eight input streams + eight output streams and AAF format +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_listener_audio_multi_aaf.h" + +AEM_ENTITY_STORAGE(); + +void talker_listener_audio_multi_aaf_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_listener_audio_multi_aaf.h b/apps/common/aem-manager/talker_listener_audio_multi_aaf.h new file mode 100644 index 0000000..64ae4df --- /dev/null +++ b/apps/common/aem-manager/talker_listener_audio_multi_aaf.h @@ -0,0 +1,1047 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TALKER_LISTENER_AUDIO_MULTI_AAF_H_ +#define _TALKER_LISTENER_AUDIO_MULTI_AAF_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000090001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB device" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 8 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 8 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x0205021800806000 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_1 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_2 "Stream input 2" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_2 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_2 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_2 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_3 "Stream input 3" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_3 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_3 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_3 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_3 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_4 "Stream input 4" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_4 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_4 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_4 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_4 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_4 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_4 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_4 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_4 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_4 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_4 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_5 "Stream input 5" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_5 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_5 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_5 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_5 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_5 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_5 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_5 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_5 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_5 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_5 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_6 "Stream input 6" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_6 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_6 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_6 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_6 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_6 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_6 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_6 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_6 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_6 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_6 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_NAME_7 "Stream input 7" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_7 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_7 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_7 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_7 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_7 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_7 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_7 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_7 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_7 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_7 AEM_CFG_STREAM_INPUT_FORMATS_0 + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS { \ + AEM_CFG_STREAM_INPUT_DESCRIPTOR(0), AEM_CFG_STREAM_INPUT_DESCRIPTOR(1), AEM_CFG_STREAM_INPUT_DESCRIPTOR(2), AEM_CFG_STREAM_INPUT_DESCRIPTOR(3), \ + AEM_CFG_STREAM_INPUT_DESCRIPTOR(4), AEM_CFG_STREAM_INPUT_DESCRIPTOR(5), AEM_CFG_STREAM_INPUT_DESCRIPTOR(6), AEM_CFG_STREAM_INPUT_DESCRIPTOR(7), \ + } + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x0205021800806000 //7.3.2 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_2 "Stream output 2" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_2 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_2 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_2 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_3 "Stream output 3" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_3 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_3 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_3 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_3 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_4 "Stream output 4" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_4 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_4 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_4 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_4 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_4 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_4 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_4 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_4 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_4 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_4 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_5 "Stream output 5" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_5 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_5 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_5 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_5 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_5 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_5 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_5 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_5 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_5 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_5 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_6 "Stream output 6" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_6 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_6 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_6 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_6 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_6 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_6 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_6 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_6 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_6 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_6 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_7 "Stream output 7" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_7 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_7 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_7 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_7 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_7 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_7 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_7 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_7 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_7 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_7 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS { \ + AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(2), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(4), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(5), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(6), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(7) \ + } + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input0" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_INPUT_NAME_1 "Jack input1" +#define AEM_CFG_JACK_INPUT_LOC_DESC_1 7 +#define AEM_CFG_JACK_INPUT_FLAGS_1 0 +#define AEM_CFG_JACK_INPUT_TYPE_1 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_1 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_1 0 + +#define AEM_CFG_JACK_INPUT_NAME_2 "Jack input2" +#define AEM_CFG_JACK_INPUT_LOC_DESC_2 7 +#define AEM_CFG_JACK_INPUT_FLAGS_2 0 +#define AEM_CFG_JACK_INPUT_TYPE_2 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_2 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_2 0 + +#define AEM_CFG_JACK_INPUT_NAME_3 "Jack input3" +#define AEM_CFG_JACK_INPUT_LOC_DESC_3 7 +#define AEM_CFG_JACK_INPUT_FLAGS_3 0 +#define AEM_CFG_JACK_INPUT_TYPE_3 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_3 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_3 0 + +#define AEM_CFG_JACK_INPUT_NAME_4 "Jack input4" +#define AEM_CFG_JACK_INPUT_LOC_DESC_4 7 +#define AEM_CFG_JACK_INPUT_FLAGS_4 0 +#define AEM_CFG_JACK_INPUT_TYPE_4 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_4 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_4 0 + +#define AEM_CFG_JACK_INPUT_NAME_5 "Jack input5" +#define AEM_CFG_JACK_INPUT_LOC_DESC_5 7 +#define AEM_CFG_JACK_INPUT_FLAGS_5 0 +#define AEM_CFG_JACK_INPUT_TYPE_5 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_5 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_5 0 + +#define AEM_CFG_JACK_INPUT_NAME_6 "Jack input6" +#define AEM_CFG_JACK_INPUT_LOC_DESC_6 7 +#define AEM_CFG_JACK_INPUT_FLAGS_6 0 +#define AEM_CFG_JACK_INPUT_TYPE_6 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_6 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_6 0 + +#define AEM_CFG_JACK_INPUT_NAME_7 "Jack input7" +#define AEM_CFG_JACK_INPUT_LOC_DESC_7 7 +#define AEM_CFG_JACK_INPUT_FLAGS_7 0 +#define AEM_CFG_JACK_INPUT_TYPE_7 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_7 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_7 0 + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS { \ + AEM_CFG_JACK_INPUT_DESCRIPTOR(0), AEM_CFG_JACK_INPUT_DESCRIPTOR(1), AEM_CFG_JACK_INPUT_DESCRIPTOR(2), AEM_CFG_JACK_INPUT_DESCRIPTOR(3), \ + AEM_CFG_JACK_INPUT_DESCRIPTOR(4), AEM_CFG_JACK_INPUT_DESCRIPTOR(5), AEM_CFG_JACK_INPUT_DESCRIPTOR(6), AEM_CFG_JACK_INPUT_DESCRIPTOR(7) \ + } + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output0" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_OUTPUT_NAME_1 "Jack output1" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_1 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_1 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_1 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_1 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_1 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_2 "Jack output2" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_2 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_2 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_2 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_2 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_2 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_3 "Jack output3" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_3 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_3 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_3 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_3 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_3 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_4 "Jack output4" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_4 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_4 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_4 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_4 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_4 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_5 "Jack output5" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_5 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_5 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_5 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_5 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_5 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_6 "Jack output6" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_6 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_6 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_6 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_6 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_6 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_7 "Jack output7" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_7 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_7 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_7 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_7 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_7 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS { \ + AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(1), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(2), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_JACK_OUTPUT_DESCRIPTOR(4), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(5), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(6), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(7), \ + } + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 8 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_2 "Audio cluster 2" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_2 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_2 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_2 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_2 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_2 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_3 "Audio cluster 3" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_3 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_3 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_3 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_3 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_3 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_4 "Audio cluster 4" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_4 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_4 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_4 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_4 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_4 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_5 "Audio cluster 5" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_5 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_5 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_5 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_5 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_5 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_6 "Audio cluster 6" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_6 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_6 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_6 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_6 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_6 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_7 "Audio cluster 7" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_7 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_7 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_7 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_7 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_7 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_8 "Audio cluster 8" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_8 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_8 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_8 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_8 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_8 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_9 "Audio cluster 9" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_9 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_9 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_9 1 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_9 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_9 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_10 "Audio cluster 10" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_10 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_10 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_10 2 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_10 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_10 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_11 "Audio cluster 11" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_11 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_11 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_11 3 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_11 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_11 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_12 "Audio cluster 12" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_12 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_12 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_12 4 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_12 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_12 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_13 "Audio cluster 13" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_13 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_13 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_13 5 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_13 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_13 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_14 "Audio cluster 14" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_14 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_14 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_14 6 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_14 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_14 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_15 "Audio cluster 15" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_15 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_15 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_15 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_15 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_15 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS { \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(2), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(3), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(4), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(5), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(6), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(7), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(8), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(9), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(10), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(11), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(12), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(13), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(14), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(15) \ + } + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 16 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0004),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0004),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0004),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0004),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0005),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0005),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0005),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0005),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0006),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0006),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0006),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0006),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0007),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0007),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0007),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0007),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_1 1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_1 1 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_2 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_2 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_2 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_2 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_2 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_2 2 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_2 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_2 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_2 2 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_3 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_3 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_3 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_3 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_3 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_3 3 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_3 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_3 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_3 3 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_4 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_4 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_4 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_4 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_4 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_4 4 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_4 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_4 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_4 4 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_5 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_5 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_5 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_5 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_5 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_5 5 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_5 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_5 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_5 5 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_6 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_6 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_6 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_6 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_6 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_6 6 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_6 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_6 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_6 6 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_7 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_7 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_7 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_7 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_7 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_7 7 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_7 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_7 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_7 7 + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS { \ + AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(1), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(2), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(4), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(5), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(6), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(7) \ + } + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_1 1 + + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_2 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_2 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_2 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_2 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_2 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_2 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_2 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_2 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_2 2 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_3 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_3 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_3 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_3 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_3 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_3 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_3 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_3 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_3 3 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_4 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_4 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_4 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_4 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_4 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_4 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_4 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_4 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_4 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_5 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_5 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_5 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_5 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_5 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_5 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_5 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_5 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_5 1 + + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_6 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_6 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_6 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_6 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_6 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_6 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_6 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_6 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_6 2 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_7 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_7 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_7 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_7 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_7 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_7 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_7 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_7 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_7 3 + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS { \ + AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(1), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(2), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(3), \ + AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(4), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(5), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(6), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(7) \ + } + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_LISTENER_AUDIO_MULTI_AAF_H_ */ diff --git a/apps/common/aem-manager/talker_listener_audio_multi_format.c b/apps/common/aem-manager/talker_listener_audio_multi_format.c new file mode 100644 index 0000000..afa5db1 --- /dev/null +++ b/apps/common/aem-manager/talker_listener_audio_multi_format.c @@ -0,0 +1,22 @@ +/* + * Copyright 2016-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker + Listener test entity + @details Talker + Listener AVDECC entity definition with eight input streams + eight output streams with several formats and frequencies +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_listener_audio_multi_format.h" + +AEM_ENTITY_STORAGE(); + +void talker_listener_audio_multi_format_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_listener_audio_multi_format.h b/apps/common/aem-manager/talker_listener_audio_multi_format.h new file mode 100644 index 0000000..36c7faa --- /dev/null +++ b/apps/common/aem-manager/talker_listener_audio_multi_format.h @@ -0,0 +1,1046 @@ +/* + * Copyright 2016-2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TALKER_LISTENER_AUDIO_MULTI_FORMAT_H_ +#define _TALKER_LISTENER_AUDIO_MULTI_FORMAT_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000070001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB device" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 8 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 8 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" // 61883-6 - 48000Hz +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1" // 61883-6 - 96000Hz +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 0x00A0040240000200 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1) } + +#define AEM_CFG_STREAM_INPUT_NAME_2 "Stream input 2" // AAF - 48000Hz - SR CLASS A +#define AEM_CFG_STREAM_INPUT_LOC_DESC_2 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2 0x0205021800806000 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_2 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_2 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2) } + +#define AEM_CFG_STREAM_INPUT_NAME_3 "Stream input 3" // AAF - 48000Hz - SR CLASS B +#define AEM_CFG_STREAM_INPUT_LOC_DESC_3 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_3 0x020502180080C000 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_3 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_3 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_3) } + +#define AEM_CFG_STREAM_INPUT_NAME_4 "Stream input 4" // AAF - 48000Hz - SR CLASS C +#define AEM_CFG_STREAM_INPUT_LOC_DESC_4 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_4 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_4 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_4 0x0205021800840000 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_4 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_4 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_4 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_4 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_4 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_4 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_4 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_4) } + +#define AEM_CFG_STREAM_INPUT_NAME_5 "Stream input 5" // AAF - 48000Hz - SR CLASS E +#define AEM_CFG_STREAM_INPUT_LOC_DESC_5 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_5 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_5 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_5 0x0205021800830000 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_5 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_5 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_5 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_5 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_5 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_5 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_5 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_5) } + +#define AEM_CFG_STREAM_INPUT_NAME_6 "Stream input 6" // AAF - 96000Hz - SR CLASS A +#define AEM_CFG_STREAM_INPUT_LOC_DESC_6 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_6 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_6 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_6 0x020702180080C000 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_6 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_6 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_6 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_6 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_6 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_6 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_6 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_6) } + +#define AEM_CFG_STREAM_INPUT_NAME_7 "Stream input 7" // AAF - 192000Hz - SR CLASS A +#define AEM_CFG_STREAM_INPUT_LOC_DESC_7 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_7 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_7 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_7 0x0209021800818000 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_7 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_7 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_7 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_7 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_7 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_7 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_7 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_7) } + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS { \ + AEM_CFG_STREAM_INPUT_DESCRIPTOR(0), AEM_CFG_STREAM_INPUT_DESCRIPTOR(1), AEM_CFG_STREAM_INPUT_DESCRIPTOR(2), AEM_CFG_STREAM_INPUT_DESCRIPTOR(3), \ + AEM_CFG_STREAM_INPUT_DESCRIPTOR(4), AEM_CFG_STREAM_INPUT_DESCRIPTOR(5), AEM_CFG_STREAM_INPUT_DESCRIPTOR(6), AEM_CFG_STREAM_INPUT_DESCRIPTOR(7), \ + } + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" // 61883-6 - 48000Hz +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x00A0020240000200 //7.3.2 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1" // 61883-6 - 96000Hz +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 0x00A0040240000200 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_2 "Stream output 2" // AAF - 48000Hz - SR CLASS A +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_2 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2 0x0205021800806000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_2 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_2 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_3 "Stream output 3" // AAF - 48000Hz - SR CLASS B +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_3 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_3 0x020502180080C000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_3 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_3 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_3) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_4 "Stream output 4" // AAF - 48000Hz - SR CLASS C +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_4 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_4 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_4 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_4 0x0205021800840000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_4 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_4 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_4 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_4 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_4 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_4 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_4 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_4) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_5 "Stream output 5" // AAF - 48000Hz - SR CLASS E +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_5 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_5 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_5 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_5 0x0205021800830000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_5 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_5 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_5 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_5 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_5 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_5 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_5 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_5) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_6 "Stream output 6" // AAF - 96000Hz - SR CLASS A +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_6 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_6 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_6 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_6 0x020702180080C000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_6 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_6 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_6 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_6 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_6 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_6 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_6 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_6) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_7 "Stream output 7" // AAF - 192000Hz - SR CLASS A +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_7 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_7 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_7 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_7 0x0209021800818000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_7 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_7 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_7 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_7 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_7 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_7 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_7 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_7) } + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS { \ + AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(2), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(4), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(5), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(6), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(7), \ + } + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input0" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_INPUT_NAME_1 "Jack input1" +#define AEM_CFG_JACK_INPUT_LOC_DESC_1 7 +#define AEM_CFG_JACK_INPUT_FLAGS_1 0 +#define AEM_CFG_JACK_INPUT_TYPE_1 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_1 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_1 0 + +#define AEM_CFG_JACK_INPUT_NAME_2 "Jack input2" +#define AEM_CFG_JACK_INPUT_LOC_DESC_2 7 +#define AEM_CFG_JACK_INPUT_FLAGS_2 0 +#define AEM_CFG_JACK_INPUT_TYPE_2 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_2 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_2 0 + +#define AEM_CFG_JACK_INPUT_NAME_3 "Jack input3" +#define AEM_CFG_JACK_INPUT_LOC_DESC_3 7 +#define AEM_CFG_JACK_INPUT_FLAGS_3 0 +#define AEM_CFG_JACK_INPUT_TYPE_3 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_3 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_3 0 + +#define AEM_CFG_JACK_INPUT_NAME_4 "Jack input4" +#define AEM_CFG_JACK_INPUT_LOC_DESC_4 7 +#define AEM_CFG_JACK_INPUT_FLAGS_4 0 +#define AEM_CFG_JACK_INPUT_TYPE_4 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_4 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_4 0 + +#define AEM_CFG_JACK_INPUT_NAME_5 "Jack input5" +#define AEM_CFG_JACK_INPUT_LOC_DESC_5 7 +#define AEM_CFG_JACK_INPUT_FLAGS_5 0 +#define AEM_CFG_JACK_INPUT_TYPE_5 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_5 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_5 0 + +#define AEM_CFG_JACK_INPUT_NAME_6 "Jack input6" +#define AEM_CFG_JACK_INPUT_LOC_DESC_6 7 +#define AEM_CFG_JACK_INPUT_FLAGS_6 0 +#define AEM_CFG_JACK_INPUT_TYPE_6 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_6 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_6 0 + +#define AEM_CFG_JACK_INPUT_NAME_7 "Jack input7" +#define AEM_CFG_JACK_INPUT_LOC_DESC_7 7 +#define AEM_CFG_JACK_INPUT_FLAGS_7 0 +#define AEM_CFG_JACK_INPUT_TYPE_7 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_7 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_7 0 + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS { \ + AEM_CFG_JACK_INPUT_DESCRIPTOR(0), AEM_CFG_JACK_INPUT_DESCRIPTOR(1), AEM_CFG_JACK_INPUT_DESCRIPTOR(2), AEM_CFG_JACK_INPUT_DESCRIPTOR(3), \ + AEM_CFG_JACK_INPUT_DESCRIPTOR(4), AEM_CFG_JACK_INPUT_DESCRIPTOR(5), AEM_CFG_JACK_INPUT_DESCRIPTOR(6), AEM_CFG_JACK_INPUT_DESCRIPTOR(7) \ + } + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output0" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + +#define AEM_CFG_JACK_OUTPUT_NAME_1 "Jack output1" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_1 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_1 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_1 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_1 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_1 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_2 "Jack output2" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_2 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_2 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_2 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_2 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_2 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_3 "Jack output3" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_3 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_3 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_3 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_3 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_3 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_4 "Jack output4" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_4 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_4 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_4 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_4 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_4 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_5 "Jack output5" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_5 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_5 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_5 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_5 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_5 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_6 "Jack output6" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_6 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_6 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_6 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_6 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_6 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_NAME_7 "Jack output7" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_7 AEM_CFG_JACK_OUTPUT_LOC_DESC_0 +#define AEM_CFG_JACK_OUTPUT_FLAGS_7 AEM_CFG_JACK_OUTPUT_FLAGS_0 +#define AEM_CFG_JACK_OUTPUT_TYPE_7 AEM_CFG_JACK_OUTPUT_TYPE_0 +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_7 AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_7 AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS { \ + AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(1), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(2), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_JACK_OUTPUT_DESCRIPTOR(4), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(5), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(6), AEM_CFG_JACK_OUTPUT_DESCRIPTOR(7), \ + } + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 8 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_2 "Audio cluster 2" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_2 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_2 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_2 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_2 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_2 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_2 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_3 "Audio cluster 3" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_3 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_3 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_3 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_3 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_3 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_3 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_4 "Audio cluster 4" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_4 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_4 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_4 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_4 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_4 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_4 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_5 "Audio cluster 5" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_5 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_5 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_5 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_5 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_5 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_5 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_6 "Audio cluster 6" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_6 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_6 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_6 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_6 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_6 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_6 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_7 "Audio cluster 7" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_7 AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_7 AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_7 AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_7 AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_7 AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_7 AEM_CFG_AUDIO_CLUSTER_FORMAT_0 + +#define AEM_CFG_AUDIO_CLUSTER_NAME_8 "Audio cluster 8" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_8 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_8 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_8 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_8 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_8 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_8 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_9 "Audio cluster 9" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_9 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_9 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_9 1 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_9 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_9 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_9 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_10 "Audio cluster 10" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_10 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_10 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_10 2 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_10 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_10 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_10 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_11 "Audio cluster 11" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_11 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_11 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_11 3 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_11 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_11 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_11 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_12 "Audio cluster 12" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_12 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_12 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_12 4 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_12 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_12 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_12 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_13 "Audio cluster 13" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_13 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_13 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_13 5 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_13 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_13 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_13 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_14 "Audio cluster 14" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_14 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_14 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_14 6 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_14 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_14 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_14 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_15 "Audio cluster 15" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_15 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_15 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_15 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_15 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_15 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_15 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS { \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(2), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(3), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(4), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(5), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(6), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(7), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(8), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(9), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(10), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(11), \ + AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(12), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(13), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(14), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(15) \ + } + + +/* Audio map config */ +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 16 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0000),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0002),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0003),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0004),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0004),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0004),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0004),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0005),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0005),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0005),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0005),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0006),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0006),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0006),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0006),\ + .mapping_cluster_channel = htons(0x0001)\ + },\ + { .mapping_stream_index = htons(0x0007),\ + .mapping_stream_channel = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0007),\ + .mapping_cluster_channel = htons(0x0000)\ + },\ + { .mapping_stream_index = htons(0x0007),\ + .mapping_stream_channel = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0007),\ + .mapping_cluster_channel = htons(0x0001)\ + }\ + } + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_1 1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_1 1 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_2 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_2 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_2 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_2 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_2 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_2 2 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_2 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_2 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_2 2 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_3 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_3 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_3 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_3 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_3 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_3 3 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_3 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_3 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_3 3 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_4 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_4 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_4 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_4 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_4 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_4 4 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_4 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_4 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_4 4 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_5 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_5 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_5 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_5 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_5 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_5 5 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_5 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_5 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_5 5 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_6 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_6 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_6 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_6 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_6 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_6 6 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_6 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_6 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_6 6 + +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_7 AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_1 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_7 AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_1 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_7 AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_7 AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_7 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_1 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_7 7 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_7 AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_7 AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_1 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_7 7 + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS { \ + AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(1), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(2), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(3), \ + AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(4), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(5), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(6), AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(7) \ + } + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_1 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_1 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_1 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_1 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_1 1 + + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_2 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_2 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_2 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_2 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_2 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_2 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_2 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_2 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_2 2 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_3 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_3 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_3 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_3 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_3 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_3 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_3 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_3 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_3 3 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_4 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_4 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_4 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_4 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_4 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_4 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_4 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_4 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_4 0 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_5 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_5 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_5 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_5 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_5 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_5 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_5 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_5 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_5 1 + + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_6 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_6 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_6 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_6 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_6 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_6 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_6 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_6 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_6 2 + +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_7 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_7 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_7 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_7 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_7 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_7 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_7 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_7 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_7 3 + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS { \ + AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(1), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(2), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(3), \ + AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(4), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(5), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(6), AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(7) \ + } + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_LISTENER_AUDIO_MULTI_H_ */ diff --git a/apps/common/aem-manager/talker_video_multi.c b/apps/common/aem-manager/talker_video_multi.c new file mode 100644 index 0000000..a8b7115 --- /dev/null +++ b/apps/common/aem-manager/talker_video_multi.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker entity + @details Talker AVDECC entity definition with four output streams +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_video_multi.h" + +AEM_ENTITY_STORAGE(); + +void talker_video_multi_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_video_multi.h b/apps/common/aem-manager/talker_video_multi.h new file mode 100644 index 0000000..1f4064e --- /dev/null +++ b/apps/common/aem-manager/talker_video_multi.h @@ -0,0 +1,503 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TALKER_VIDEO_MULTI_H_ +#define _TALKER_VIDEO_MULTI_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000060001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB talker" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_VIDEO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Video unit config */ +#define AEM_CFG_VIDEO_UNIT_NAME_0 "Video unit" +#define AEM_CFG_VIDEO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_OUT_PORT_0 4 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_IN_PORT_0 4 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROLS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROL_BLOCK_0 0 + + +#define AEM_CFG_VIDEO_UNIT_DESCRIPTORS {AEM_CFG_VIDEO_UNIT_DESCRIPTOR(0)} + + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x00C0000000000000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_2 "Stream output 2" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_2 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_2 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_2 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_NAME_3 "Stream output 3" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_3 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_3 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_3 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_3 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_3 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_3 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_3 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_3 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_3 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_3 AEM_CFG_STREAM_OUTPUT_FORMATS_0 + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0),AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1),AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(2),AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(3)} + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 8 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Video cluster config */ +#define AEM_CFG_VIDEO_CLUSTER_NAME_0 "Video cluster 0" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_0 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_NAME_1 "Video cluster 1" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_1 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_NAME_2 "Video cluster 2" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_2 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_2 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_2 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_2 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_2 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_2 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_2 AEM_CFG_VIDEO_CLUSTER_FORMAT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_2 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_2 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_3 "Video cluster 3" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_3 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_3 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_3 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_3 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_3 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_3 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_3 AEM_CFG_VIDEO_CLUSTER_FORMAT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_3 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_3 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_4 "Video cluster 4" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_4 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_4 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_4 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_4 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_4 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_4 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_4 AEM_CFG_VIDEO_CLUSTER_FORMAT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_4 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_4 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_5 "Video cluster 5" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_5 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_5 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_5 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_5 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_5 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_5 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_5 AEM_CFG_VIDEO_CLUSTER_FORMAT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_5 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_5 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_6 "Video cluster 6" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_6 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_6 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_6 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_6 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_6 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_6 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_6 AEM_CFG_VIDEO_CLUSTER_FORMAT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_6 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_6 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 + +#define AEM_CFG_VIDEO_CLUSTER_NAME_7 "Video cluster 7" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_7 AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_7 AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_7 AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_7 AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_7 AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_7 AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_7 AEM_CFG_VIDEO_CLUSTER_FORMAT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_7 AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_7 AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 + +#define AEM_CFG_VIDEO_CLUSTER_DESCRIPTORS {AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(0),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(1),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(2),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(3),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(4),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(5),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(6),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(7)} + + +/* Video map config */ +#define AEM_CFG_VIDEO_MAP_NB_MAPPINGS_0 8 +#define AEM_CFG_VIDEO_MAP_MAPPINGS_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0002),\ + },\ + { .mapping_stream_index = htons(0x0001),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0003),\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0004),\ + },\ + { .mapping_stream_index = htons(0x0002),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0005),\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0006),\ + },\ + { .mapping_stream_index = htons(0x0003),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0007),\ + }\ + } + + +#define AEM_CFG_VIDEO_MAP_DESCRIPTORS {AEM_CFG_VIDEO_MAP_DESCRIPTOR(0)} + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0)} + + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_VIDEO_MULTI_H_ */ diff --git a/apps/common/aem-manager/talker_video_single.c b/apps/common/aem-manager/talker_video_single.c new file mode 100644 index 0000000..0bdfa2e --- /dev/null +++ b/apps/common/aem-manager/talker_video_single.c @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief Talker entity + @details Talker AVDECC entity definition with a single output stream +*/ + +#include "genavb/adp.h" +#include "genavb/aem_helpers.h" + +#include "talker_video_single.h" + +AEM_ENTITY_STORAGE(); + +void talker_video_single_init(struct aem_desc_hdr *aem_desc) +{ + AEM_ENTITY_INIT(aem_desc); +} diff --git a/apps/common/aem-manager/talker_video_single.h b/apps/common/aem-manager/talker_video_single.h new file mode 100644 index 0000000..53b8946 --- /dev/null +++ b/apps/common/aem-manager/talker_video_single.h @@ -0,0 +1,326 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TALKER_VIDEO_SINGLE_H_ +#define _TALKER_VIDEO_SINGLE_H_ + +#define AEM_ENTITY_MODEL_ID 0x00049f0000040001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB talker" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED | ADP_ENTITY_ASSOCIATION_ID_SUPPORTED | ADP_ENTITY_ASSOCIATION_ID_VALID) +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_VIDEO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + + +/* Video unit config */ +#define AEM_CFG_VIDEO_UNIT_NAME_0 "Video unit" +#define AEM_CFG_VIDEO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_VIDEO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_IN_PORT_0 1 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_EXT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROLS_0 3 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_VIDEO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_VIDEO_UNIT_BASE_CONTROL_BLOCK_0 0 + + +#define AEM_CFG_VIDEO_UNIT_DESCRIPTORS {AEM_CFG_VIDEO_UNIT_DESCRIPTOR(0)} + + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x00C0000000000000 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(0x00C0000000000000) } + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0)} + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 2 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Video cluster config */ +#define AEM_CFG_VIDEO_CLUSTER_NAME_0 "Video cluster 0" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_0 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_0 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_0 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_0 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_0 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_0 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_NAME_1 "Video cluster 1" +#define AEM_CFG_VIDEO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_PATH_LAT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_VIDEO_CLUSTER_FORMAT_1 AEM_VIDEO_CLUSTER_FORMAT_MPEG_PES +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_FORMAT_SPECIFIC_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SAMPLING_RATE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_ASPECT_RATIO_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_SIZE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_CURRENT_COLOR_SPACE_1 0 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_COUNT_1 1 +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_FORMAT_SPECIFICS_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SAMPLING_RATES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_ASPECT_RATIOS_1 { htons(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_SIZES_1 { htonl(0) } +#define AEM_CFG_VIDEO_CLUSTER_SUPPORTED_COLOR_SPACES_1 { htons(0) } + +#define AEM_CFG_VIDEO_CLUSTER_DESCRIPTORS {AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(0),AEM_CFG_VIDEO_CLUSTER_DESCRIPTOR(1)} + + +/* Video map config */ +#define AEM_CFG_VIDEO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_VIDEO_MAP_MAPPINGS_0 {\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0000),\ + .mapping_cluster_offset = htons(0x0000),\ + },\ + { .mapping_stream_index = htons(0x0000),\ + .mapping_program_stream = htons(0x0000),\ + .mapping_elementary_stream = htons(0x0001),\ + .mapping_cluster_offset = htons(0x0001),\ + }\ + } + + +#define AEM_CFG_VIDEO_MAP_DESCRIPTORS {AEM_CFG_VIDEO_MAP_DESCRIPTOR(0)} + + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0)} + +/* Control for volume, in percent */ +#define AEM_CFG_CONTROL_NAME_0 "Volume Control" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + +/* Control for Enable/Disable, used for media play/stop */ +#define AEM_CFG_CONTROL_NAME_1 "Play/Stop" +#define AEM_CFG_CONTROL_LOC_DESC_1 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_1 0 +#define AEM_CFG_CONTROL_CTRL_LAT_1 200 +#define AEM_CFG_CONTROL_DOMAIN_1 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_1 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_1 AEM_CONTROL_TYPE_ENABLE +#define AEM_CFG_CONTROL_RESET_TIME_1 0 +#define AEM_CFG_CONTROL_NB_VALUES_1 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_1 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_1 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_1 {\ + .linear_int8 = {{0, 255, 255, 0, 0, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_UNITLESS)), 0}}} + +/* Control for media track number (0 to 255) */ +#define AEM_CFG_CONTROL_NAME_2 "Media track" +#define AEM_CFG_CONTROL_LOC_DESC_2 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_2 0 +#define AEM_CFG_CONTROL_CTRL_LAT_2 200 +#define AEM_CFG_CONTROL_DOMAIN_2 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_2 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_2 AEM_CONTROL_TYPE_MEDIA_TRACK +#define AEM_CFG_CONTROL_RESET_TIME_2 0 +#define AEM_CFG_CONTROL_NB_VALUES_2 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_2 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_2 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_2 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_2 {\ + .linear_int8 = {{0, 255, 1, 0, 0, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_COUNT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0), AEM_CFG_CONTROL_DESCRIPTOR(1), AEM_CFG_CONTROL_DESCRIPTOR(2)} + + +#include "genavb/aem_entity.h" + +#endif /* _TALKER_VIDEO_SINGLE_H_ */ diff --git a/apps/linux/CMakeLists.txt b/apps/linux/CMakeLists.txt new file mode 100644 index 0000000..5aa9559 --- /dev/null +++ b/apps/linux/CMakeLists.txt @@ -0,0 +1,61 @@ +set(apps management-app) + +if(CONFIG_SOCKET) + list(APPEND apps tsn-app) +endif() + +if(CONFIG_SRP) + list(APPEND apps msrp-ctrl-app) +endif() + +if(CONFIG_MAAP) + list(APPEND apps maap-ctrl-app) +endif() + +if(CONFIG_AVDECC) + list(APPEND apps genavb-controls-app genavb-controller-app aem-manager) +endif() + +if(CONFIG_AVTP) + list(APPEND apps simple-audio-app alsa-audio-app genavb-multi-stream-app genavb-video-player-app genavb-video-server-app genavb-media-app salsacamctrl simple-acf-app) +endif() + +set(APPS_INSTALL_DIR ${CMAKE_BINARY_DIR}/apps/target) + +foreach(app IN LISTS apps) + set(app_source_dir ${CMAKE_SOURCE_DIR}/apps/linux/${app}) + set(app_build_dir ${CMAKE_BINARY_DIR}/apps/build/${app}) + + # run cmake + add_custom_command( + OUTPUT ${app_build_dir} + COMMAND ${CMAKE_COMMAND} + ${app_source_dir} + -B${app_build_dir} + -DGENAVB_INCLUDE_DIR=${CMAKE_BINARY_DIR}/include # target includes prepared by include-prep + -DGENAVB_LIB_DIR=${CMAKE_BINARY_DIR}/lib # libgenavb.so location + -DCMAKE_INSTALL_PREFIX=${APPS_INSTALL_DIR} + -DCMAKE_INSTALL_MESSAGE=LAZY + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + COMMENT "Generate ${app}" + ) + + # build app + add_custom_target(${app} + COMMAND $(MAKE) -C ${app_build_dir} DESTDIR="" install + DEPENDS ${app_build_dir} ${genavb} include-prep + COMMENT "Build ${app}" + ) +endforeach() + +add_custom_target(apps ALL DEPENDS ${apps}) + +# install apps binaries/scripts/outputs into main target directory +install(DIRECTORY ${APPS_INSTALL_DIR}/usr/bin/ DESTINATION usr/bin USE_SOURCE_PERMISSIONS) +install(DIRECTORY ${APPS_INSTALL_DIR}/etc/ DESTINATION etc USE_SOURCE_PERMISSIONS OPTIONAL) + +if(${CMAKE_VERSION} GREATER_EQUAL "3.15.0") + set_property(TARGET apps APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${APPS_INSTALL_DIR}) +else() + set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${APPS_INSTALL_DIR}) +endif() diff --git a/apps/linux/aem-manager/CMakeLists.txt b/apps/linux/aem-manager/CMakeLists.txt new file mode 100644 index 0000000..eecfdd9 --- /dev/null +++ b/apps/linux/aem-manager/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.10) + +# Program is compiled and executed on host machine +# We use custom_commands to avoid yocto toolchain cross-compile settings + +project(aem-manager) + +# suppress cmake warning +set(unused_var ${GENAVB_LIB_DIR}) + +if(NOT DEFINED GENAVB_INCLUDE_DIR) + set(GENAVB_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../include") + set(GENAVB_INCLUDE_OS_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../include/linux") + set(COMPILE_FLAGS -I${GENAVB_INCLUDE_OS_DIR}) +endif() +set(COMPILE_FLAGS ${COMPILE_FLAGS} -I${GENAVB_INCLUDE_DIR}) + +find_program(GCC "gcc" NO_CMAKE_FIND_ROOT_PATH) +if(GCC STREQUAL "GCC-NOTFOUND") + message(FATAL_ERROR "gcc not found") +endif() + +include(${CMAKE_SOURCE_DIR}/../../common/aem-manager/aem-audio-entities.cmake) +include(${CMAKE_SOURCE_DIR}/../../common/aem-manager/aem-audio-video-entities.cmake) +include(${CMAKE_SOURCE_DIR}/../../common/aem-manager/aem-avnu-vertification-entities.cmake) +include(${CMAKE_SOURCE_DIR}/../../common/aem-manager/aem-controller-entities.cmake) +include(${CMAKE_SOURCE_DIR}/../../common/aem-manager/aem-video-entities.cmake) + +include(${CMAKE_SOURCE_DIR}/../../common/aem-manager/aem-manager-helpers.cmake) + +set(SOURCES + ${AEM_ENTITIES} + ${AEM_MANAGER_HELPERS_SOURCES} + ${CMAKE_SOURCE_DIR}/main.c + ${CMAKE_SOURCE_DIR}/../../../public/aem_helpers.c + ${CMAKE_SOURCE_DIR}/../../../public/linux/aem_helpers.c + ${CMAKE_SOURCE_DIR}/../../../public/helpers.c +) + +set(COMPILE_FLAGS ${COMPILE_FLAGS} -O2 -Wall -Werror -g) + +# compile +add_custom_command( + OUTPUT ${PROJECT_NAME} + COMMAND ${GCC} ${COMPILE_FLAGS} ${SOURCES} -o ${PROJECT_NAME} -I ${AEM_MANAGER_HELPERS_HEADER_DIR} + DEPENDS ${SOURCES} + COMMENT "Build ${PROJECT_NAME}" +) + +# once compiled, run the program to generate entities binaries +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/entities + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME} -c > ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.log + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/entities + COMMAND mv ${CMAKE_CURRENT_BINARY_DIR}/*.aem ${CMAKE_CURRENT_BINARY_DIR}/entities + COMMAND touch ${CMAKE_CURRENT_BINARY_DIR}/entities + DEPENDS ${PROJECT_NAME} + COMMENT "Generate aem entities" +) +add_custom_target(gen_entities ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/entities) + +if(${CMAKE_VERSION} VERSION_LESS "3.15.0") + set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.log + ) +else() + set_property(TARGET gen_entities APPEND PROPERTY ADDITIONAL_CLEAN_FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.log + ) +endif() + +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/entities/ DESTINATION etc/genavb FILES_MATCHING PATTERN "*.aem") diff --git a/apps/linux/aem-manager/main.c b/apps/linux/aem-manager/main.c new file mode 100644 index 0000000..8ff513a --- /dev/null +++ b/apps/linux/aem-manager/main.c @@ -0,0 +1,304 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AECP common code + @details Handles AECP stack +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "genavb/aem.h" +#include "genavb/helpers.h" + +#include "aem_manager_helpers.h" + +static struct aem_desc_handler desc_handler[AEM_NUM_DESC_TYPES] = { + [AEM_DESC_TYPE_ENTITY] = { + .fixup = aem_entity_desc_fixup, + .check = entity_desc_check, + .print = entity_desc_print, + .update_name = entity_desc_update_name, + }, + + [AEM_DESC_TYPE_CONFIGURATION] = { + .fixup = aem_configuration_desc_fixup, + .check = configuration_desc_check, + .print = configuration_desc_print, + }, + + [AEM_DESC_TYPE_AUDIO_UNIT] = { + .print = audio_unit_desc_print, + }, + + [AEM_DESC_TYPE_VIDEO_UNIT] = { + .print = video_unit_desc_print, + }, + + [AEM_DESC_TYPE_STREAM_INPUT] = { + .print = stream_input_desc_print, + .update_name = stream_input_desc_update_name, + }, + + [AEM_DESC_TYPE_STREAM_OUTPUT] = { + .print = stream_output_desc_print, + .update_name = stream_output_desc_update_name, + }, + + [AEM_DESC_TYPE_VIDEO_CLUSTER] = { + .fixup = aem_video_cluster_desc_fixup, + }, + + [AEM_DESC_TYPE_CONTROL] = { + .print = control_desc_print, + }, + +}; + +void aem_print_level(int level, const char *format, ...) +{ + va_list ap; + int i; + + for (i = 0; i < level; i++) + printf("\t"); + + va_start(ap, format); + + vprintf(format, ap); + + va_end(ap); +} + +static int aem_entity_dump_to_file(const char *name, struct aem_desc_hdr *aem_desc, unsigned int overwrite) +{ + int i; + int fd; + + if (overwrite) + fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + else + fd = open(name, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + + if (fd < 0) { + printf("open(%s) failed: %s\n", name, strerror(errno)); + goto err; + } + + for (i = 0; i < AEM_NUM_DESC_TYPES; i++) { + if (write(fd, &aem_desc[i].total, sizeof(avb_u16)) != sizeof(avb_u16)) + goto err_write; + + if (write(fd, &aem_desc[i].size, sizeof(avb_u16)) != sizeof(avb_u16)) + goto err_write; + } + + for (i = 0; i < AEM_NUM_DESC_TYPES; i++) { + int size = aem_desc[i].size * aem_desc[i].total; + + if (write(fd, aem_desc[i].ptr, size) != size) + goto err_write; + } + + close(fd); + + return 0; + +err_write: + close(fd); +err: + return -1; +} + + +static void __aem_entity_print(struct aem_desc_hdr *aem_desc) +{ + desc_handler[AEM_DESC_TYPE_ENTITY].print(desc_handler, aem_desc, 0, 0, 0xffff); +} + +/* Return value: < 0 when error, 0 otherwise. */ +static int aem_entity_print(const char *filename) +{ + struct aem_desc_hdr *aem_desc; + + aem_desc = aem_entity_load_from_file(filename); + if (!aem_desc) + return -1; + + __aem_entity_print(aem_desc); + return 0; +} + +int aem_entity_create(const char *name, void (*entity_init)(struct aem_desc_hdr *aem_desc)) +{ + struct aem_desc_hdr aem_desc[AEM_NUM_DESC_TYPES] = {{0, }}; + int rc = 0; + + entity_init(aem_desc); + + aem_entity_fixup(desc_handler, aem_desc); + + if (aem_entity_check(desc_handler, aem_desc) < 0) { + printf("aem_desc(%p) failed to create entity %s\n", aem_desc, name); + rc = -1; + goto err; + } + + __aem_entity_print(aem_desc); + + aem_entity_dump_to_file(name, aem_desc, 0); + +err: + return rc; +} + +/* Return value: < 0 when error, 0 otherwise. */ +static int aem_entity_update_name(const char *filename, int type, char *names) +{ + struct aem_desc_hdr *aem_desc; + + aem_desc = aem_entity_load_from_file(filename); + if (!aem_desc) + return -1; + + if (!desc_handler[type].update_name) + return -1; + + desc_handler[type].update_name(aem_desc, names); + + __aem_entity_print(aem_desc); + + aem_entity_dump_to_file(filename, aem_desc, 1); + return 0; +} + +extern void listener_audio_single_init(struct aem_desc_hdr *aem_desc); +extern void listener_audio_single_milan_init(struct aem_desc_hdr *aem_desc); +extern void talker_audio_single_init(struct aem_desc_hdr *aem_desc); +extern void talker_audio_single_milan_init(struct aem_desc_hdr *aem_desc); +extern void listener_talker_audio_single_init(struct aem_desc_hdr *aem_desc); +extern void listener_talker_audio_single_milan_init(struct aem_desc_hdr *aem_desc); +extern void listener_video_single_init(struct aem_desc_hdr *aem_desc); +extern void listener_video_multi_init(struct aem_desc_hdr *aem_desc); +extern void talker_video_single_init(struct aem_desc_hdr *aem_desc); +extern void talker_video_multi_init(struct aem_desc_hdr *aem_desc); +extern void talker_audio_video_init(struct aem_desc_hdr *aem_desc); +extern void talker_listener_audio_multi_init(struct aem_desc_hdr *aem_desc); +extern void talker_listener_audio_multi_aaf_init(struct aem_desc_hdr *aem_desc); +extern void talker_listener_audio_multi_format_init(struct aem_desc_hdr *aem_desc); +extern void controller_init(struct aem_desc_hdr *aem_desc); +extern void avnu_certification_init(struct aem_desc_hdr *aem_desc); + + +const char *filename = "listener_talker_audio_single.aem"; + +void print_usage (void) +{ + printf("\nUsage:\n aem-manager [options]\n"); + printf("\nOptions:\n" + "\t-h prints this help text\n" + "\t-c create binary file for each defined entity\n" + "\t-f filename name of the entity binary file to use (use with p/e/i/o options)\n" + "\t-p print entity to stdout\n" + "\t-e name modifies the entity name in the binary file\n" + "\t-i name1;name2 modifies the input stream names in the binary file\n" + "\t-o name1;name2 modifies the output stream names in the binary file\n\n" + "Examples: ./aem-manager -c\n" + " ./aem-manager -f talker_listener_audio_multi.aem -p\n" + " ./aem-manager -f talker_listener_audio_multi.aem -e \"New entity name\"\n"); +} + +int main(int argc, char *argv[]) +{ + int option, nb_options = 0; + + entity_desc_handler_init(desc_handler); + + while ((option = getopt(argc, argv,"f:pce:i:o:h")) != -1) { + + nb_options += 1; + + switch (option) { + case 'f': + filename = optarg; + break; + + case 'p': + if (aem_entity_print(filename) < 0) + goto err; + + break; + + case 'e': + if (aem_entity_update_name(filename, AEM_DESC_TYPE_ENTITY, optarg) < 0) + goto err; + + break; + + case 'i': + if (aem_entity_update_name(filename, AEM_DESC_TYPE_STREAM_INPUT, optarg) < 0) + goto err; + + break; + + case 'o': + if (aem_entity_update_name(filename, AEM_DESC_TYPE_STREAM_OUTPUT, optarg) < 0) + goto err; + + break; + + case 'c': + if ((aem_entity_create("listener_audio_single.aem", listener_audio_single_init) < 0) + || (aem_entity_create("listener_audio_single_milan.aem", listener_audio_single_milan_init) < 0) + || (aem_entity_create("talker_audio_single.aem", talker_audio_single_init) < 0) + || (aem_entity_create("talker_audio_single_milan.aem", talker_audio_single_milan_init) < 0) + || (aem_entity_create("listener_talker_audio_single.aem", listener_talker_audio_single_init) < 0) + || (aem_entity_create("listener_talker_audio_single_milan.aem", listener_talker_audio_single_milan_init) < 0) + || (aem_entity_create("listener_video_single.aem", listener_video_single_init) < 0) + || (aem_entity_create("listener_video_multi.aem", listener_video_multi_init) < 0) + || (aem_entity_create("talker_video_single.aem", talker_video_single_init) < 0) + || (aem_entity_create("talker_video_multi.aem", talker_video_multi_init) < 0) + || (aem_entity_create("talker_audio_video.aem", talker_audio_video_init) < 0) + || (aem_entity_create("talker_listener_audio_multi.aem", talker_listener_audio_multi_init) < 0) + || (aem_entity_create("talker_listener_audio_multi_aaf.aem", talker_listener_audio_multi_aaf_init) < 0) + || (aem_entity_create("talker_listener_audio_multi_format.aem", talker_listener_audio_multi_format_init) < 0) + || (aem_entity_create("controller.aem", controller_init) < 0) + || (aem_entity_create("avnu_certification.aem", avnu_certification_init) < 0)) { + + fprintf(stderr, "%s: entity generation failed\n", argv[0]); + goto err; + } + + break; + + case 'h': + default: + print_usage(); + break; + } + } + + if (!nb_options) + print_usage(); + + return 0; + +err: + fprintf(stderr, "%s: error!\n", argv[0]); + return 1; +} diff --git a/apps/linux/alsa-audio-app/CMakeLists.txt b/apps/linux/alsa-audio-app/CMakeLists.txt new file mode 100644 index 0000000..33b2e9a --- /dev/null +++ b/apps/linux/alsa-audio-app/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.10) + +project(alsa-audio-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + ../common/alsa.c + ../common/stats.c + ../common/time.c + ../common/msrp.c + ../common/log.c + ../common/common.c + ../common/avdecc.c +) + +target_compile_definitions(${PROJECT_NAME} PUBLIC WL_BUILD) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) +target_link_libraries(${PROJECT_NAME} asound) +target_link_libraries(${PROJECT_NAME} m) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/alsa-audio-app/main.c b/apps/linux/alsa-audio-app/main.c new file mode 100644 index 0000000..1e989af --- /dev/null +++ b/apps/linux/alsa-audio-app/main.c @@ -0,0 +1,686 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief GenAVB alsa application + @details + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../common/alsa.h" +#include "../common/avdecc.h" +#include "../common/msrp.h" + +/* Application main modes */ +#define MODE_AVDECC 0 /* the application relies on avdecc indication from avb stack*/ +#define MODE_LISTENER 1 /* acting as media files server if avdecc is not used*/ +#define MODE_TALKER 2 /* acting as media files server if avdecc is not used*/ + +#define PROCESS_PRIORITY 60 /* RT_FIFO priority to be used for the process */ + +#define BATCH_SIZE 512 + +#define DEFAULT_ALSA_DEVICE "plughw:0,0" + +static int signal_terminate = 0; + +/* application main context */ +struct avb_app { + unsigned int mode; + unsigned int config; + int media_fd; + struct avb_stream_params stream_params; + unsigned int stream_batch_size; + unsigned int stream_flags; + struct avb_control_handle *ctrl_h; + struct avb_control_handle *controlled_h; + char *binding_file_name; + char *alsa_device; + int connected_stream_index; +}; + +struct avb_app app; + +struct avb_stream_params default_stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_61883_IIDC, + .stream_class = SR_CLASS_A, + .clock_domain = AVB_MEDIA_CLOCK_DOMAIN_STREAM, + .flags = AVB_STREAM_FLAGS_MCR, + .format.u.s = { + .v = 0, + .subtype = AVTP_SUBTYPE_61883_IIDC, + .subtype_u.iec61883 = { + .sf = IEC_61883_SF_61883, + .fmt = IEC_61883_CIP_FMT_6, + .r = 0, + .format_u.iec61883_6 = { + .fdf_u.fdf = { + .evt = IEC_61883_6_FDF_EVT_AM824, + .sfc = IEC_61883_6_FDF_SFC_48000, + }, + .dbs = 2, + .b = 0, + .nb = 1, + .rsvd = 0, + .label_iec_60958_cnt = 0, + .label_mbla_cnt = 2, + .label_midi_cnt = 0, + .label_smptecnt = 0, + }, + }, + }, + .port = 0, + .stream_id = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 }, + .dst_mac = { 0x91, 0xE0, 0xF0, 0x00, 0xeb, 0x15 } +}; + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\nOptions:\n" + "\t-m application mode: avdecc(default), listener\n" + "\t-h print this help text\n"); + +} + + +static void set_avb_config(unsigned int *avb_flags) +{ + *avb_flags = 0; +} + + +static int apply_config(struct avb_stream_params *stream_params) +{ + memcpy(&app.stream_params, stream_params, sizeof(struct avb_stream_params)); + + app.stream_batch_size = BATCH_SIZE; + + app.stream_flags = AVTP_NONBLOCK; + + /* display the whole configuration */ + printf("stream ID: %02x%02x%02x%02x%02x%02x%02x%02x\n", stream_params->stream_id[0],stream_params->stream_id[1],stream_params->stream_id[2],stream_params->stream_id[3], + stream_params->stream_id[4],stream_params->stream_id[5],stream_params->stream_id[6],stream_params->stream_id[7]); + + printf("mode: "); + if (app.mode == MODE_LISTENER) + printf("LISTENER\n"); + else if (app.mode == MODE_TALKER) + printf("TALKER\n"); + else + printf("AVDECC %s\n", (app.stream_params.direction == AVTP_DIRECTION_LISTENER)? "LISTENER":"TALKER"); + + return 0; +} + +static int handle_avdecc_event(struct avb_control_handle *ctrl_h, unsigned int *msg_type, const char *binding_file_name, bool *is_audio_stream) +{ + union avb_media_stack_msg msg; + unsigned int msg_len = sizeof(union avb_media_stack_msg); + int rc; + + *is_audio_stream = false; + + rc = avb_control_receive(ctrl_h, msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) + goto receive_error; + + switch (*msg_type) { + case GENAVB_MSG_MEDIA_STACK_CONNECT: + if (!(avdecc_format_is_61883_6(&msg.media_stack_connect.stream_params.format) || avdecc_format_is_aaf_pcm(&msg.media_stack_connect.stream_params.format))) { + printf("\nIgnoring stream formats other than 61883_6 or AAF\n"); + goto exit; + } + + *is_audio_stream = true; + + printf("GENAVB_MSG_MEDIA_STACK_CONNECT\n"); + apply_config(&msg.media_stack_connect.stream_params); + app.connected_stream_index = msg.media_stack_connect.stream_index; + break; + + case GENAVB_MSG_MEDIA_STACK_DISCONNECT: + if (msg.media_stack_disconnect.stream_index != app.connected_stream_index) { + printf("\nIgnoring stream with a different id than the current connected stream\n"); + goto exit; + } + + *is_audio_stream = true; + + printf("GENAVB_MSG_MEDIA_STACK_DISCONNECT\n"); + app.connected_stream_index = -1; + break; + + case GENAVB_MSG_MEDIA_STACK_BIND: + printf("GENAVB_MSG_MEDIA_STACK_BIND: Controller (%016"PRIx64") bound listener stream (%016"PRIx64", %u, %s) to talker stream (%016"PRIx64", %u) \n", + msg.media_stack_bind.controller_entity_id, + msg.media_stack_bind.entity_id, msg.media_stack_bind.listener_stream_index, (msg.media_stack_bind.started == ACMP_LISTENER_STREAM_STARTED) ? "STARTED" : "STOPPED", + msg.media_stack_bind.talker_entity_id, msg.media_stack_bind.talker_stream_index); + + if (binding_file_name) + avdecc_nvm_bindings_update(binding_file_name, &msg.media_stack_bind); + + break; + + case GENAVB_MSG_MEDIA_STACK_UNBIND: + printf("GENAVB_MSG_MEDIA_STACK_UNBIND: listener stream (%016"PRIx64", %u) has been unbound\n", + msg.media_stack_bind.entity_id, msg.media_stack_bind.listener_stream_index); + + if (binding_file_name) + avdecc_nvm_bindings_remove(binding_file_name, &msg.media_stack_unbind); + + break; + + default: + break; + } + +exit: +receive_error: + return rc; +} + +static int handle_avdecc_controlled_event(struct avb_control_handle *controlled_h) +{ + unsigned int msg_len = sizeof(union avb_controlled_msg); + unsigned short status = AECP_AEM_SUCCESS; + avb_msg_type_t msg_type; + union avb_controlled_msg msg; + struct aecp_aem_pdu *pdu; + avb_u16 cmd_type; + int rc; + + rc = avb_control_receive(controlled_h, &msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) { + printf("%s: WARNING: Got error message %d(%s) while trying to receive AECP command.\n", __func__, rc, avb_strerror(rc)); + goto receive_error; + } + + switch (msg_type) { + case AVB_MSG_AECP: + printf("AVB_MSG_AECP\n"); + + pdu = (struct aecp_aem_pdu *)msg.aecp.buf; + + cmd_type = AECP_AEM_GET_CMD_TYPE(pdu); + printf("aecp command type (0x%x) seq_id (%d)\n", cmd_type, ntohs(pdu->sequence_id)); + + switch (cmd_type) { + case AECP_AEM_CMD_GET_AUDIO_MAP: + { + /* GET_AUDIO_MAP not fully supported by alsa-audio-app, simply respond with empty audio mappings */ + struct aecp_aem_get_audio_map_rsp_pdu *audio_map_rsp = (struct aecp_aem_get_audio_map_rsp_pdu *)(pdu + 1); + + audio_map_rsp->number_of_maps = htons(0); + audio_map_rsp->number_of_mappings = htons(0); + audio_map_rsp->reserved = htons(0); + + /* Set the AECP Response PDU length to match the aecp_aem_get_audio_map_rsp_pdu to be sent back */ + msg.aecp.len = sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_get_audio_map_rsp_pdu); + + break; + } + default: + printf("Command type (0x%x) not handled in this app, skip\n", cmd_type); + status = AECP_AEM_NOT_IMPLEMENTED; + + /* Multiple apps can be listening/handling controlled commands, + * if this app do not handle it, do not answer the stack and let other apps handle that. */ + goto exit; + } + + msg.aecp.msg_type = AECP_AEM_RESPONSE; + msg.aecp.status = status; + + rc = avb_control_send(controlled_h, msg_type, &msg, msg_len); + if (rc != AVB_SUCCESS) { + printf("%s: WARNING: Got error message %d(%s) while trying to send response to AECP command.\n", __func__, rc, avb_strerror(rc)); + } + break; + default: + printf("%s: WARNING: Received unsupported AVDECC message type %d.\n", __func__, msg_type); + break; + } + +exit: +receive_error: + return rc; +} + + +static int run_listener(struct avb_stream_handle *stream_h, int stream_fd, unsigned int batch_size, void *alsa_h) +{ + struct pollfd poll_fds[3]; + int rc = 0; + int ctrl_rx_fd = -1; + int controlled_rx_fd = -1; + int ready, i, n, nfds; + unsigned int event_type; + bool is_audio_stream; + + /* + * setup ALSA to receive samples + */ + + alsa_h = alsa_tx_init((void*)&app.stream_params.stream_id, &app.stream_params.format, batch_size, app.alsa_device); + if (!alsa_h) { + printf("Couldn't initialize alsa tx, aborting...\n"); + rc = -1; + goto err_alsa_init; + } + + printf("Starting listener loop, non-blocking mode\n"); + /* + * listen to read event from the stack + */ + + poll_fds[0].fd = stream_fd; + poll_fds[0].events = POLLIN; + poll_fds[0].revents = 0; + + nfds = 1; + + if (app.ctrl_h) { + ctrl_rx_fd = avb_control_rx_fd(app.ctrl_h); + poll_fds[1].fd = ctrl_rx_fd; + poll_fds[1].events = POLLIN; + poll_fds[1].revents = 0; + + nfds++; + } else { + poll_fds[1].fd = -1; + poll_fds[1].events = 0; + poll_fds[1].revents = 0; + } + + if (app.controlled_h) { + controlled_rx_fd = avb_control_rx_fd(app.controlled_h); + poll_fds[2].fd = controlled_rx_fd; + poll_fds[2].events = POLLIN; + poll_fds[2].revents = 0; + + nfds++; + } else { + poll_fds[2].fd = -1; + poll_fds[2].events = 0; + poll_fds[2].revents = 0; + } + + while (1) { + if ((ready = poll(poll_fds, nfds, -1)) == -1) { + if (errno == EINTR) { + if (signal_terminate) { + printf("processing terminate signal\n"); + signal_terminate = 0; + rc = -1; + goto exit; + } + } else { + printf("poll(%d) failed while processing listener\n", stream_fd); + rc = -1; + goto exit; + } + } + + if (ready > 0) { + for (n = 0, i = 0; i < nfds && n < ready; i++) { + if (poll_fds[i].revents & POLLIN) { + if (poll_fds[i].fd == ctrl_rx_fd) { + n++; + + /* + * read control event from avdecc + */ + if (handle_avdecc_event(app.ctrl_h, &event_type, app.binding_file_name, &is_audio_stream) == AVB_SUCCESS) { + if (event_type == GENAVB_MSG_MEDIA_STACK_DISCONNECT && is_audio_stream) { + rc = 0; + goto exit; /* disconnected, stop stream processing */ + } + } + } else if (poll_fds[i].fd == stream_fd) { + n++; + + /* + * send samples to alsa + */ + if (alsa_tx(alsa_h, stream_h, &app.stream_params) < 0) { + printf("Error writing data to alsa...\n"); + rc = -1; + } + } else if (poll_fds[i].fd == controlled_rx_fd) { + n++; + + /* + * read controlled event from avdecc + */ + if (handle_avdecc_controlled_event(app.controlled_h) != AVB_SUCCESS) { + printf("handle_avdecc_controlled_event error\n"); + rc = -1; + } + } + } + } + } + } + +exit: + alsa_tx_exit(alsa_h); + +err_alsa_init: + return rc; + +} + +void signal_terminate_handler (int signal_num) +{ + signal_terminate = 1; +} + +int main(int argc, char *argv[]) +{ + struct avb_handle *avb_h; + struct avb_stream_handle* stream_h; + unsigned int avb_flags; + int stream_fd, option; + unsigned int event_type; + int ctrl_rx_fd = -1; + int controlled_rx_fd = -1; + int nfds, ready, i, n; + struct pollfd ctrl_poll[2]; + int rc = 0; + void *alsa_h; + struct sched_param param = { + .sched_priority = PROCESS_PRIORITY, + }; + struct sigaction action; + bool is_audio_stream; + + /* + * Increase process priority to match the AVTP thread priority + */ + if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) { + printf("sched_setscheduler(), %s\n", strerror(errno)); + rc = -1; + goto exit; + } + + setlinebuf(stdout); + + printf("NXP's GenAVB ALSA reference application\n"); + + + /* + * retrieve user's configuration parameters + */ + + app.mode = MODE_AVDECC; app.config = 0; /*default is AVDECC, NON-BLOCKING, SINGLE BUFFER */ + app.alsa_device = DEFAULT_ALSA_DEVICE; + app.connected_stream_index = -1; + + while ((option = getopt(argc, argv,"m:b:d:h")) != -1) { + switch (option) { + case 'm': + if (!strcasecmp(optarg, "listener")) + app.mode = MODE_LISTENER; + else if(!strcasecmp(optarg, "avdecc")) + app.mode = MODE_AVDECC; + else { + usage(); + goto exit; + } + break; + + case 'b': + app.binding_file_name = optarg; + break; + + case 'd': + app.alsa_device = optarg; + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + + /* + * set signals handler + */ + action.sa_handler = signal_terminate_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGTERM, &action, NULL) < 0) /* Termination signal */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGQUIT, &action, NULL) < 0) /* Quit from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGINT, &action, NULL) < 0) /* Interrupt from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + /* + * Setup the avb stack + */ + set_avb_config(&avb_flags); + + rc = avb_init(&avb_h, avb_flags); + if (rc != AVB_SUCCESS) { + printf("avb_init() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + if (app.mode == MODE_AVDECC) { + /* + * Open control channel for AVDECC events. + */ + rc = avb_control_open(avb_h, &app.ctrl_h, AVB_CTRL_AVDECC_MEDIA_STACK); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() failed on control chanel: %s\n", avb_strerror(rc)); + goto error_control_open; + } + + ctrl_rx_fd = avb_control_rx_fd(app.ctrl_h); + ctrl_poll[0].fd = ctrl_rx_fd; + ctrl_poll[0].events = POLLIN; + nfds = 1; + + /* + * Open controlled channel for AVDECC events. + */ + rc = avb_control_open(avb_h, &app.controlled_h, AVB_CTRL_AVDECC_CONTROLLED); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() failed on controlled chanel: %s\n", avb_strerror(rc)); + goto error_controlled_open; + } + + controlled_rx_fd = avb_control_rx_fd(app.controlled_h); + ctrl_poll[1].fd = controlled_rx_fd; + ctrl_poll[1].events = POLLIN; + nfds++; + + /* If we have a filename for non-volatile binding params parse it and init the avdecc stack with its parameters. */ + if (app.binding_file_name) { + if (avdecc_nvm_bindings_init(app.ctrl_h, app.binding_file_name) < 0) { + printf("failed to parse binding file %s\n", app.binding_file_name); + rc = -1; + goto err_bind; + } + } + } else { + rc = msrp_init(avb_h); + if (rc < 0) + goto err_msrp_init; + } + +wait_new_stream: + if (app.mode == MODE_AVDECC) { + + printf("\nwait for new stream...\n"); + + /* + * Listen to AVDECC events to get stream parameters + */ + while (1) { + if ((ready = poll(ctrl_poll, nfds, -1)) == -1) { + printf("poll failed on waiting for connect\n"); + rc = -1; + goto error_ctrl_poll; + } + + if (ready > 0) { + for (n = 0, i = 0; i < nfds && n < ready; i++) { + if (ctrl_poll[i].revents & POLLIN) { + if (ctrl_poll[i].fd == ctrl_rx_fd) { + n++; + + /* + * read control event from avdecc + */ + if (handle_avdecc_event(app.ctrl_h, &event_type, app.binding_file_name, &is_audio_stream) == AVB_SUCCESS) { + if (event_type == GENAVB_MSG_MEDIA_STACK_CONNECT && is_audio_stream) + goto start_stream; /* connected, start stream processing */ + } + + } else if (ctrl_poll[i].fd == controlled_rx_fd) { + n++; + + /* + * read controlled event from avdecc + */ + handle_avdecc_controlled_event(app.controlled_h); + } + } + } + } + } + } else { + /* + * no avdecc, static configuration used + */ + + if (app.mode == MODE_TALKER) + default_stream_params.direction = AVTP_DIRECTION_TALKER; + else + default_stream_params.direction = AVTP_DIRECTION_LISTENER; + + apply_config(&default_stream_params); + + if (app.mode == MODE_TALKER) { + rc = msrp_talker_register(&default_stream_params); + if (rc != AVB_SUCCESS) { + printf("msrp_talker_register error, rc = %d\n", rc); + goto err_msrp; + } + } else if (app.mode == MODE_LISTENER) { + rc = msrp_listener_register(&default_stream_params); + if (rc != AVB_SUCCESS) { + printf("msrp_listener_register error, rc = %d\n", rc); + goto err_msrp; + } + } + } + +start_stream: + /* + * setup the stream + */ + rc = avb_stream_create (avb_h, &stream_h, &app.stream_params, &app.stream_batch_size, app.stream_flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", app.stream_batch_size); + + + /* + * retrieve the file descriptor associated to the stream + */ + + stream_fd = avb_stream_fd(stream_h); + if (stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(stream_fd)); + rc = -1; + goto error_stream_fd; + } + + /* + * run listener/talker main processing function + */ + + if (app.stream_params.direction & AVTP_DIRECTION_TALKER) { + printf("Talker direction not supported yet.\n"); + rc = -1; + } + else + rc = run_listener(stream_h, stream_fd, app.stream_batch_size, alsa_h); + + if (rc < 0) { + printf("Loop function exited with error code %d\n", rc); + } else { + printf("Loop function exited upon avdecc disconnect\n"); + + avb_stream_destroy(stream_h); + + goto wait_new_stream; + } + + /* + * destroy the stream, disconnect for avb stack + */ +error_stream_fd: + avb_stream_destroy(stream_h); + +error_stream_create: + if (app.mode == MODE_TALKER) + msrp_talker_deregister(&default_stream_params); + else if (app.mode == MODE_LISTENER) + msrp_listener_deregister(&default_stream_params); + +err_msrp: +error_ctrl_poll: +err_bind: + if (app.controlled_h) + avb_control_close(app.controlled_h); + +error_controlled_open: + if (app.ctrl_h) + avb_control_close(app.ctrl_h); + +error_control_open: + if (app.mode != MODE_AVDECC) + msrp_exit(); +err_msrp_init: + avb_exit(avb_h); + +error_avb_init: +exit: + return rc; + +} diff --git a/apps/linux/common/acmp.c b/apps/linux/common/acmp.c new file mode 100644 index 0000000..7f849dd --- /dev/null +++ b/apps/linux/common/acmp.c @@ -0,0 +1,127 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2019-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include + +/** Sends an ACMP command to the stack, and optionally return the response. + * Note: the parameters of the function are an exact match to the fields of an ACMP command PDU. + * + * \return AVB return code (AVB_SUCCESS or negative error code). + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param msg_type Type of ACMP message to send. + * \param talker_entity_id [Network Order] Entity ID of the talker affected by the command, if any. + * \param talker_unique_id [Network Order] Unique ID of the stream output being targeted on the talker, if any. + * \param listener_entity_id [Network Order] Entity ID of the listener affected by the command, if any. + * \param listener_unique_id [Network Order] Unique ID of the stream input being targeted on the listener, if any. + * \param connection_count [Network Order] Index of the connection to get information about, for a ACMP_GET_TX_CONNECTION_COMMAND. Will be ignored by the stack for other commands. + * \param flags [Network Order] Stream attributes. + * \param acmp_rsp If non-NULL, the function will wait for an ACMP response (after sending the command) and acmp_rsp will point to it on return. + */ +int acmp_send_command(struct avb_control_handle *ctrl_h, acmp_message_type_t msg_type, avb_u64 talker_entity_id, avb_u16 talker_unique_id, avb_u64 listener_entity_id, avb_u16 listener_unique_id, avb_u16 connection_count, avb_u16 flags, struct avb_acmp_response *acmp_rsp) +{ + int rc; + struct avb_acmp_command cmd; + + cmd.message_type = msg_type; + cmd.talker_entity_id = talker_entity_id; + cmd.talker_unique_id = talker_unique_id; + cmd.listener_entity_id = listener_entity_id; + cmd.listener_unique_id = listener_unique_id; + cmd.connection_count = connection_count; + cmd.flags = flags; + + if (acmp_rsp) { + unsigned int avb_msg_type = AVB_MSG_ACMP_COMMAND; + unsigned int avb_msg_len = sizeof(struct avb_acmp_response); + + rc = avb_control_send_sync(ctrl_h, &avb_msg_type, &cmd, sizeof(struct avb_acmp_command), acmp_rsp, &avb_msg_len, -1); + if ((rc == AVB_SUCCESS) && (avb_msg_type == AVB_MSG_ACMP_RESPONSE)) + rc = acmp_rsp->status; + } + else + rc = avb_control_send(ctrl_h, AVB_MSG_ACMP_COMMAND, &cmd, sizeof(struct avb_acmp_command)); + + + return rc; +} + + +/** Connects a stream output of a talker to a stream input of a listener. + * + * \return AVB return code (AVB_SUCCESS or negative error code). + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param talker_entity_id [Network Order] Entity ID of the talker affected by the command. + * \param talker_unique_id [Network Order] Unique ID of the stream output to use on the talker. + * \param listener_entity_id [Network Order] Entity ID of the listener affected by the command. + * \param listener_unique_id [Network Order] Unique ID of the stream input to use on the listener. + * \param flags [Network Order] Stream attributes. + * \param acmp_rsp If non-NULL, the function will wait for an ACMP response (after sending the command) and acmp_rsp will point to it on return. + */ +int acmp_connect_stream(struct avb_control_handle *ctrl_h, avb_u64 talker_entity_id, avb_u16 talker_unique_id, avb_u64 listener_entity_id, avb_u16 listener_unique_id, avb_u16 flags, struct avb_acmp_response *acmp_rsp) +{ + return acmp_send_command(ctrl_h, ACMP_CONNECT_RX_COMMAND, talker_entity_id, talker_unique_id, listener_entity_id, listener_unique_id, 0, flags, acmp_rsp); +} + +/** Disconnects a stream output of a talker to a stream input of a listener. + * + * \return AVB return code (AVB_SUCCESS or negative error code). + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param talker_entity_id [Network Order] Entity ID of the talker affected by the command. + * \param talker_unique_id [Network Order] Unique ID of the stream output to use on the talker. + * \param listener_entity_id [Network Order] Entity ID of the listener affected by the command. + * \param listener_unique_id [Network Order] Unique ID of the stream input to use on the listener. + * \param acmp_rsp If non-NULL, the function will wait for an ACMP response (after sending the command) and acmp_rsp will point to it on return. + */ +int acmp_disconnect_stream(struct avb_control_handle *ctrl_h, avb_u64 talker_entity_id, avb_u16 talker_unique_id, avb_u64 listener_entity_id, avb_u16 listener_unique_id, struct avb_acmp_response *acmp_rsp) +{ + return acmp_send_command(ctrl_h, ACMP_DISCONNECT_RX_COMMAND, talker_entity_id, talker_unique_id, listener_entity_id, listener_unique_id, 0, 0, acmp_rsp); +} + +/** Returns information about a given stream input of a listener entity. + * + * \return AVB return code (AVB_SUCCESS or negative error code). + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param listener_entity_id [Network Order] Entity ID of the listener affected by the command. + * \param listener_unique_id [Network Order] Unique ID of the stream input to gather information about. + * \param acmp_rsp If non-NULL, the function will wait for an ACMP response (after sending the command) and acmp_rsp will point to it on return. + */ +int acmp_get_rx_state(struct avb_control_handle *ctrl_h, avb_u64 listener_entity_id, avb_u16 listener_unique_id, struct avb_acmp_response *acmp_rsp) +{ + return acmp_send_command(ctrl_h, ACMP_GET_RX_STATE_COMMAND, 0, 0, listener_entity_id, listener_unique_id, 0, 0, acmp_rsp); +} + +/** Returns information about a given stream output of a talker entity. + * + * \return AVB return code (AVB_SUCCESS or negative error code). + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param talker_entity_id [Network Order] Entity ID of the talker affected by the command. + * \param talker_unique_id [Network Order] Unique ID of the stream output to gather information about. + * \param acmp_rsp If non-NULL, the function will wait for an ACMP response (after sending the command) and acmp_rsp will point to it on return. + */ +int acmp_get_tx_state(struct avb_control_handle *ctrl_h, avb_u64 talker_entity_id, avb_u16 talker_unique_id, struct avb_acmp_response *acmp_rsp) +{ + return acmp_send_command(ctrl_h, ACMP_GET_TX_STATE_COMMAND, talker_entity_id, talker_unique_id, 0, 0, 0, 0, acmp_rsp); +} + +/** Returns information about a specific stream. + * + * \return AVB return code (AVB_SUCCESS or negative error code). + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param talker_entity_id [Network Order] Entity ID of the talker affected by the command. + * \param talker_unique_id [Network Order] Unique ID of the stream output the stream is connected to. + * \param connection_count [Network Order] Index of the connection to get information about. + * \param acmp_rsp If non-NULL, the function will wait for an ACMP response (after sending the command) and acmp_rsp will point to it on return. + */ + +int acmp_get_tx_connection(struct avb_control_handle *ctrl_h, avb_u64 talker_entity_id, avb_u16 talker_unique_id, avb_u16 connection_count, struct avb_acmp_response *acmp_rsp) +{ + return acmp_send_command(ctrl_h, ACMP_GET_TX_CONNECTION_COMMAND, talker_entity_id, talker_unique_id, 0, 0, connection_count, 0, acmp_rsp); +} diff --git a/apps/linux/common/acmp.h b/apps/linux/common/acmp.h new file mode 100644 index 0000000..1b59aa5 --- /dev/null +++ b/apps/linux/common/acmp.h @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_ACMP_H_ +#define _COMMON_ACMP_H_ + +#include + + +int acmp_connect_stream(struct avb_control_handle *ctrl_h, avb_u64 talker_entity_id, avb_u16 talker_unique_id, avb_u64 listener_entity_id, avb_u16 listener_unique_id, avb_u16 flags, struct avb_acmp_response *acmp_rsp); +int acmp_disconnect_stream(struct avb_control_handle *ctrl_h, avb_u64 talker_entity_id, avb_u16 talker_unique_id, avb_u64 listener_entity_id, avb_u16 listener_unique_id, struct avb_acmp_response *acmp_rsp); +int acmp_get_rx_state(struct avb_control_handle *ctrl_h, avb_u64 listener_entity_id, avb_u16 listener_unique_id, struct avb_acmp_response *acmp_rsp); +int acmp_get_tx_state(struct avb_control_handle *ctrl_h, avb_u64 talker_entity_id, avb_u16 talker_unique_id, struct avb_acmp_response *acmp_rsp); +int acmp_get_tx_connection(struct avb_control_handle *ctrl_h, avb_u64 talker_entity_id, avb_u16 talker_unique_id, avb_u16 connection_count, struct avb_acmp_response *acmp_rsp); + +#endif /* _COMMON_ACMP_H_ */ diff --git a/apps/linux/common/adp.c b/apps/linux/common/adp.c new file mode 100644 index 0000000..32e7feb --- /dev/null +++ b/apps/linux/common/adp.c @@ -0,0 +1,188 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include "adp.h" + + +/** Initiate a dump of the ADP database of entities (i.e. entities currently seen on the network). + * Once the function has been called, entities may be retrieved by receiving ADP messages from the AVB stack (one message per entity). + * + * \return AVB return code (AVB_SUCCESS or negative error code). + * \param ctrl_h AVB control handle to use to send the request to the GenAVB stack (must be for a AVB_CTRL_AVDECC_CONTROLLER channel). + */ +int adp_start_dump_entities(struct avb_control_handle *ctrl_h) +{ + struct avb_adp_msg msg; + int rc; + + msg.msg_type = ADP_ENTITY_DISCOVER; + rc = avb_control_send(ctrl_h, AVB_MSG_ADP, &msg, sizeof(struct avb_adp_msg)); + + return rc; +} + + +int adp_new_entity(struct entity_info *entities, unsigned int n_entities, struct avb_adp_msg *msg) +{ + unsigned int i; + + for (i = 0; i < n_entities; i++) { + if ((memcmp(&entities[i].entity_id, &msg->info.entity_id, 8) == 0) && + (memcmp(&entities[i].local_mac_addr, &msg->info.local_mac_addr, 6) == 0)) /* we can receive the same entity on two different interfaces */ + return 0; + } + + return 1; +} + +/** Dump the full ADP database of entities. + * + * \return Total number of entities returned on success, or negative AVB error code. + * \param ctrl_h AVB control handle to use to send the request to the GenAVB stack (must be for a AVB_CTRL_AVDECC_CONTROLLER channel). + * \param entities on return, *entities will point to a newly-allocated array of entity_info structures describing the various entities. + */ +int adp_dump_entities(struct avb_control_handle *ctrl_h, struct entity_info **entities) +{ + union avb_controller_msg msg; + avb_msg_type_t msg_type; + int rc = -AVB_ERR_CTRL_RX; + int i, n_entities; + int ctrl_rx_fd; + struct pollfd ctrl_poll; + + if (!ctrl_h || !entities) + return -AVB_ERR_INVALID_PARAMS; + + rc = adp_start_dump_entities(ctrl_h); + if (rc != AVB_SUCCESS) { + printf("%s: ERROR: Got error message %d(%s) while trying to start dump of ADP entities.\n", __func__, rc, avb_strerror(rc)); + rc = -AVB_ERR_CTRL_RX; + goto exit; + } + + *entities = NULL; + n_entities = 0; + i = -1; + + ctrl_rx_fd = avb_control_rx_fd(ctrl_h); + ctrl_poll.fd = ctrl_rx_fd; + ctrl_poll.events = POLLIN; + ctrl_poll.revents = 0; + + while (i < n_entities) { + if (poll(&ctrl_poll, 1, -1) == -1) { + printf("%s: ERROR: poll(%d) failed on waiting for connect\n", __func__, ctrl_poll.fd); + rc = -AVB_ERR_CTRL_RX; + goto exit; + } + + if (ctrl_poll.revents & POLLIN) { + unsigned int msg_len = sizeof(union avb_controller_msg); + + rc = avb_control_receive(ctrl_h, &msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) { + printf("%s: ERROR: Got error message %d(%s) while trying to receive ADP response.\n", __func__, rc, avb_strerror(rc)); + rc = -AVB_ERR_CTRL_RX; + goto exit; + } + + switch (msg_type) { + case AVB_MSG_ADP: + /* The code below is a bit more complex than would seem necessary, because we need to handle a possible + * race condition: The API used to fetch entity discovery information closely follows the ADP protocol, + * which means a new entity notification (AVAILABLE or DEPARTING) could be sent by the stack after the + * application asked for an ADP database dump but before the stack received the dump request. + * + * Note 1: because of the way the ADP code is architected in the stack, an ADP dump will be made of a + * sequence of contiguous ADP messages, without any external notifications interrupting it. + * + * Note 2: we do not need to handle notifications arriving after the dump: we can listen to + * notifications later on if we need to, but the dump itself will accurately represent the ADP database + * at a specific time. + * + * As a consequence, we only need to handle ADP notifications that may be received before the dump + * actually starts (in addition to the messages for the dump itself). + */ + switch (msg.adp.msg_type) { + case ADP_ENTITY_AVAILABLE: + /* If we never allocated the entities array (or if we freed it in a previous + * ENTITY_DEPARTING message), n_entities will be 0, msg.adp.total >= 1, + * and *entities NULL. In that case, realloc will behave like malloc. + * + * If we already allocated the array, the current message has to be an external + * notification (all messages from the dump will have the same total of entities, + * and it cannot be more than the last notification received, if any), + * so it is safe to reallocate and start over from the beginning of the array. + */ + if (n_entities != msg.adp.total) { + n_entities = msg.adp.total; + *entities = realloc(*entities, n_entities * sizeof(struct entity_info)); + i = 0; + } + + if (!*entities) { + rc = -AVB_ERR_NO_MEMORY; + goto exit; + } + + /* We may have received an ENTITY_AVAILABLE notification before the dump started. + * In that case, the total number of detected entities retrieved in the notification + * will already be accurate (as well as the details of the new entity), but the dump + * will show that entity information a 2nd time, so we just skip duplicates. + */ + if (adp_new_entity(*entities, i, &msg.adp)) { + memcpy(&(*entities)[i], &msg.adp.info, sizeof(struct entity_info)); + i++; + } + + break; + + /* Dump messages can only be ENTITY_AVAILABLE or ENTITY_NOTFOUND, so this as to be an external + * notification. We do not bother trying to sync the existing array in that case and we just + * start over, since the dump messages are still to be read and will give us the info we need. + */ + case ADP_ENTITY_DEPARTING: + if (*entities) { + free(*entities); + *entities = NULL; + n_entities = 0; + i = -1; + } + break; + + case ADP_ENTITY_NOTFOUND: + rc = 0; + goto exit; + + default: + printf("%s: ERROR: Received a message of type %d while handling dump of ADP entities.\n", __func__, msg_type); + rc = -AVB_ERR_CTRL_RX; + goto exit; + } + break; + + default: + printf("%s: WARNING: Ignoring AVDECC message type %d received while handling dump of ADP entities.\n", __func__, msg_type); + break; + } + } + } + + if (i == n_entities) + rc = i; +exit: + if ((rc <= 0) && *entities) + free(*entities); + return rc; +} diff --git a/apps/linux/common/adp.h b/apps/linux/common/adp.h new file mode 100644 index 0000000..9684464 --- /dev/null +++ b/apps/linux/common/adp.h @@ -0,0 +1,16 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_ADP_H_ +#define _COMMON_ADP_H_ + +#include + +int adp_start_dump_entities(struct avb_control_handle *ctrl_h); +int adp_dump_entities(struct avb_control_handle *ctrl_h, struct entity_info **entities); + +#endif /* _COMMON_ADP_H_ */ diff --git a/apps/linux/common/aecp.c b/apps/linux/common/aecp.c new file mode 100644 index 0000000..d49a5ab --- /dev/null +++ b/apps/linux/common/aecp.c @@ -0,0 +1,593 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include + +#include +#include +#include + + +/** Generic function to send an AECP SET_CONTROL command or response, and optionally wait for a response. + * + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message to. If NULL, the message will be assumed to be an AECP response, and an AECP command otherwise. + * \param unsolicited A flag indicating if it's an unsolicited response (1 for unsolicited responses, 0 in all other cases) + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value New value to set for the specified CONTROL descriptor (if the message is a command), or current value to report (if the message is a response). On return, will + * contain the values from the response if sync != 0. + * \param len Length of the memory area pointed to by value. If sync != 0, will be updated to contain the length of the values contained in the response. + * \param status On return in sync mode, will contain the status from the AECP response if command was successful. Ignored otherwise. + * \param sync If sync == 0, the function will return without waiting for a response from the target entity. Otherwise, the function will block until receiving a response + * and value, len and status will be updated to match the response. + */ +int aecp_aem_send_set_control(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, int unsolicited, avb_u16 descriptor_index, void *value, avb_u16 *len, avb_u8 *status, int sync) +{ + struct avb_aecp_msg aecp_msg; + struct aecp_aem_pdu *aecp_cmd = (struct aecp_aem_pdu *) aecp_msg.buf; + struct aecp_aem_set_get_control_pdu *set_control_cmd = (struct aecp_aem_set_get_control_pdu *)(aecp_cmd + 1); + void *values_cmd = set_control_cmd + 1; + int value_len; + int rc; + + if (!value) { + printf("%s: ERROR: NULL pointer passed for value parameter\n", __func__); + rc = AVB_ERR_CTRL_TX; + goto exit; + } + + if (!len) { + printf("%s: ERROR: NULL pointer passed for len parameter\n", __func__); + rc = AVB_ERR_CTRL_TX; + goto exit; + } + value_len = *len; + + if (sync && !entity_id) { + printf("%s: ERROR: sync mode requested but cannot send a command without an entity id\n", __func__); + rc = AVB_ERR_CTRL_TX; + goto exit; + } + + memset(aecp_cmd, 0, sizeof(struct aecp_aem_pdu)); + memset(set_control_cmd, 0, sizeof(struct aecp_aem_set_get_control_pdu)); + aecp_msg.len = sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu) + value_len; + if (aecp_msg.len > AVB_AECP_MAX_MSG_SIZE) { + value_len -= aecp_msg.len - AVB_AECP_MAX_MSG_SIZE; + aecp_msg.len = AVB_AECP_MAX_MSG_SIZE; + printf("%s: WARNING: provided value doesn't fit in AEM message, clamping to %d instead of requested %d\n", __func__, value_len, *len); + } + aecp_msg.status = AECP_AEM_SUCCESS; + if (entity_id) { + AECP_AEM_SET_U_CMD_TYPE(aecp_cmd, 0, AECP_AEM_CMD_SET_CONTROL); + aecp_msg.msg_type = AECP_AEM_COMMAND; + memcpy(&aecp_cmd->entity_id, entity_id, 8); + } + else { + AECP_AEM_SET_U_CMD_TYPE(aecp_cmd, unsolicited, AECP_AEM_CMD_SET_CONTROL); + aecp_msg.msg_type = AECP_AEM_RESPONSE; + } + + set_control_cmd->descriptor_type = htons(AEM_DESC_TYPE_CONTROL); + set_control_cmd->descriptor_index = htons(descriptor_index); + memcpy(values_cmd, value, value_len); + + if (sync) { + unsigned int msg_type = AVB_MSG_AECP; + unsigned int msg_len = sizeof(struct avb_aecp_msg); + + rc = avb_control_send_sync(ctrl_h, &msg_type, &aecp_msg, sizeof(struct avb_aecp_msg), &aecp_msg, &msg_len, -1); + + if ((rc == AVB_SUCCESS) && (msg_type == AVB_MSG_AECP)) { + value_len = aecp_msg.len - sizeof(struct aecp_aem_pdu) - sizeof(struct aecp_aem_set_get_control_pdu); + if (value_len > *len) { + printf("%s: WARNING: response values (%d bytes) cannot fit in provided space (%d bytes)\n", __func__, value_len, *len); + value_len = *len; + } + + *len = value_len; + memcpy(value, values_cmd, value_len); + + if (status) + *status = aecp_msg.status; + } + } + else + rc = avb_control_send(ctrl_h, AVB_MSG_AECP, &aecp_msg, sizeof(struct avb_aecp_msg)); + + if (rc != AVB_SUCCESS) + printf("%s: avb_control_send() failed: AVB_MSG_AECP SET CONTROL entity_id(%" PRIx64 ") desc_idx(%d) value(%p)\n", __func__, entity_id?ntohll(*entity_id):0, descriptor_index, value); + +exit: + return rc; +} + +/** [DEPRECATED] Sends an AECP SET_CONTROL command or response for a single UINT8 value type. + * + * This function will return without waiting for a response from the target entity. + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message. If NULL, the message will be assumed to be an AECP response, and an AECP command otherwise. + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value NEW value to set for the specified CONTROL descriptor (if the message is a command), or current value to report (if the message is a response). + */ +int aecp_aem_send_set_control_single_u8(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, avb_u8 value) +{ + avb_u16 len = 1; + + return aecp_aem_send_set_control(ctrl_h, entity_id, 1, descriptor_index, &value, &len, NULL, 0); +} + +/** [DEPRECATED] Sends an AECP SET_CONTROL command or response for an UTF8 string type. + * + * This function will return without waiting for a response from the target entity. + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message to. If NULL, the message will be assumed to be an AECP response, and an AECP command otherwise. + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value NEW value to set for the specified CONTROL descriptor (if the message is a command), or current value to report (if the message is a response). + */ +int aecp_aem_send_set_control_utf8(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, char *value) +{ + avb_u16 len = strlen(value) + 1; + return aecp_aem_send_set_control(ctrl_h, entity_id, 1, descriptor_index, value, &len, NULL, 0); +} + +/** Sends an AECP SET_CONTROL command single UINT8 value type and optionally wait for a response. + * + * This function will return without waiting for a response from the target entity. + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message. + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value Pointer to the NEW value to set for the specified CONTROL descriptor (In sync mode, it always contains the current descriptor value). + * \param status On return in sync mode, will contain the status from the AECP response if command was successful. Ignored otherwise. + * \param sync If sync == 0, the function will return without waiting for a response from the target entity. Otherwise, the function will block until receiving a response + */ +int aecp_aem_send_set_control_single_u8_command(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, avb_u8 *value, avb_u8 *status, int sync) +{ + int rc; + avb_u16 len = 1; + + if (!entity_id) + rc = -AVB_ERR_INVALID_PARAMS; + else + rc = aecp_aem_send_set_control(ctrl_h, entity_id, 0, descriptor_index, value, &len, status, sync); + + return rc; +} + +/** Sends an AECP SET_CONTROL response for a single UINT8 value type + * + * This function will return without waiting for a response from the target entity. + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value Pointer to the current value to report. + */ +int aecp_aem_send_set_control_single_u8_response(struct avb_control_handle *ctrl_h, avb_u16 descriptor_index, avb_u8 *value) +{ + avb_u16 len = 1; + + return aecp_aem_send_set_control(ctrl_h, NULL, 0, descriptor_index, value, &len, NULL, 0); +} + +/** Sends an AECP SET_CONTROL unsolicited response for a single UINT8 value type + * + * This function will return without waiting for a response from the target entity. + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value Pointer to the current value to report. + */ +int aecp_aem_send_set_control_single_u8_unsolicited_response(struct avb_control_handle *ctrl_h, avb_u16 descriptor_index, avb_u8 *value) +{ + avb_u16 len = 1; + + return aecp_aem_send_set_control(ctrl_h, NULL, 1, descriptor_index, value, &len, NULL, 0); +} + +/** Sends an AECP SET_CONTROL command for an UTF8 string type and optionally wait for a response. + * + * This function will return without waiting for a response from the target entity. + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message. + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value NEW value to set for the specified CONTROL descriptor. + * \param status On return in sync mode, will contain the status from the AECP response if command was successful. Ignored otherwise. + * \param sync If sync == 0, the function will return without waiting for a response from the target entity. Otherwise, the function will block until receiving a response + */ +int aecp_aem_send_set_control_utf8_command(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, char *value, avb_u8 *status, int sync) +{ + int rc; + avb_u16 len = strlen(value) + 1; + + if (!entity_id) + rc = -AVB_ERR_INVALID_PARAMS; + else + rc = aecp_aem_send_set_control(ctrl_h, entity_id, 0, descriptor_index, value, &len, status, sync); + + return rc; +} + +/** Sends an AECP SET_CONTROL response for an UTF8 string type + * + * This function will return without waiting for a response from the target entity. + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value Current value to report. + */ +int aecp_aem_send_set_control_utf8_response(struct avb_control_handle *ctrl_h, avb_u16 descriptor_index, char *value) +{ + avb_u16 len = strlen(value) + 1; + + return aecp_aem_send_set_control(ctrl_h, NULL, 0, descriptor_index, value, &len, NULL, 0); +} + +/** Sends an AECP SET_CONTROL unsolicited response for an UTF8 string type + * + * This function will return without waiting for a response from the target entity. + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER or AVB_CTRL_AVDECC_CONTROLLED channel. + * \param descriptor_index Index of the CONTROL descriptor to modify. + * \param value Current value to report. + */ +int aecp_aem_send_set_control_utf8_unsolicited_response(struct avb_control_handle *ctrl_h, avb_u16 descriptor_index, char *value) +{ + avb_u16 len = strlen(value) + 1; + + return aecp_aem_send_set_control(ctrl_h, NULL, 1, descriptor_index, value, &len, NULL, 0); +} + +/** Sends an AECP GET_CONTROL command. + * + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message to. + * \param descriptor_index Index of the CONTROL descriptor to retrieve. + * \param value On return, will contain the values from the response if sync != 0. + * \param len Length of the memory area pointed to by value. If sync != 0, will be updated to contain the length of the values contained in the response. + * \param status On return in sync mode, will contain the status from the AECP response if command was successful. Ignored otherwise. + * \param sync If sync == 0, the function will return without waiting for a response from the target entity. Otherwise, the function will block until receiving a response + * and value, len and status will be updated to match the response. + * + */ +int aecp_aem_send_get_control(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, void *value, avb_u16 *len, avb_u8 *status, int sync) +{ + struct avb_aecp_msg aecp_msg; + struct aecp_aem_pdu *aecp_cmd = (struct aecp_aem_pdu *) aecp_msg.buf; + struct aecp_aem_set_get_control_pdu *get_control_cmd = (struct aecp_aem_set_get_control_pdu *)(aecp_cmd + 1); + void *values_cmd = get_control_cmd + 1; + avb_u16 value_len; + int rc; + + memset(aecp_cmd, 0, sizeof(struct aecp_aem_pdu)); + memset(get_control_cmd, 0, sizeof(struct aecp_aem_set_get_control_pdu)); + aecp_msg.len = sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu); + AECP_AEM_SET_U_CMD_TYPE(aecp_cmd, 0, AECP_AEM_CMD_GET_CONTROL); + aecp_msg.msg_type = AECP_AEM_COMMAND; + memcpy(&aecp_cmd->entity_id, entity_id, 8); + + get_control_cmd->descriptor_type = htons(AEM_DESC_TYPE_CONTROL); + get_control_cmd->descriptor_index = htons(descriptor_index); + + if (sync) { + unsigned int msg_type = AVB_MSG_AECP; + unsigned int msg_len = sizeof(struct avb_aecp_msg); + + rc = avb_control_send_sync(ctrl_h, &msg_type, &aecp_msg, sizeof(struct avb_aecp_msg), &aecp_msg, &msg_len, -1); + + if (value && len && (*len) && (rc == AVB_SUCCESS) && (msg_type == AVB_MSG_AECP) && (aecp_msg.status == AECP_AEM_SUCCESS)) { + value_len = aecp_msg.len - sizeof(struct aecp_aem_pdu) - sizeof(struct aecp_aem_set_get_control_pdu); + if (value_len > *len) { + printf("%s: WARNING: response values (%d bytes) cannot fit in provided space (%d bytes)\n", __func__, value_len, *len); + value_len = *len; + } + + *len = value_len; + memcpy(value, values_cmd, value_len); + } + + if (status && (rc == AVB_SUCCESS)) + *status = aecp_msg.status; + } + else + rc = avb_control_send(ctrl_h, AVB_MSG_AECP, &aecp_msg, sizeof(struct avb_aecp_msg)); + + if (rc != AVB_SUCCESS) + printf("%s: avb_control_send_sync() failed: AVB_MSG_AECP SET CONTROL entity_id(%" PRIx64 ") desc_idx(%d)\n", __func__, ntohll(*entity_id), descriptor_index); + + return rc; +} + +/** Acquire or release a given entity. + * + * \return 1 on success, 0 if entity already acquired by another entity, -1 otherwise. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param entity_id Pointer to the entity ID [Network Order] to acquire/release. + * \param acquire Set to 1 to acquire entity, 0 to release. + */ +int aecp_aem_acquire_entity(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, int acquire) +{ + struct avb_aecp_msg aecp_msg; + struct aecp_aem_pdu *aecp_cmd = (struct aecp_aem_pdu *) aecp_msg.buf; + struct aecp_aem_acquire_entity_pdu *acquire_pdu = (struct aecp_aem_acquire_entity_pdu *)(aecp_cmd + 1); + unsigned int msg_type, msg_len; + int rc; + + memset(aecp_cmd, 0, sizeof(struct aecp_aem_pdu)); + memset(acquire_pdu, 0, sizeof(struct aecp_aem_acquire_entity_pdu)); + aecp_msg.len = sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_acquire_entity_pdu); + AECP_AEM_SET_U_CMD_TYPE(aecp_cmd, 0, AECP_AEM_CMD_ACQUIRE_ENTITY); + aecp_msg.msg_type = AECP_AEM_COMMAND; + memcpy(&aecp_cmd->entity_id, entity_id, 8); + + acquire_pdu->descriptor_type = htons(AEM_DESC_TYPE_ENTITY); + acquire_pdu->descriptor_index = htons(0); + if (!acquire) + acquire_pdu->flags = ntohl(AECP_AEM_ACQUIRE_RELEASE); + + msg_type = AVB_MSG_AECP; + msg_len = sizeof(struct avb_aecp_msg); + rc = avb_control_send_sync(ctrl_h, &msg_type, &aecp_msg, sizeof(struct avb_aecp_msg), &aecp_msg, &msg_len, -1); + + if ((rc == AVB_SUCCESS) && (msg_type == AVB_MSG_AECP)) { + if (aecp_msg.status == AECP_AEM_SUCCESS) + rc = 1; + else if (aecp_msg.status == AECP_AEM_ENTITY_ACQUIRED) + rc = 0; + else + rc = -1; + } else + rc = -1; + + return rc; +} + + +/** Sends an AECP READ_DESCRIPTOR command. + * + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message to. + * \param configuration_index Index of the Configuration to read the descriptor from. + * \param descriptor_type Type of the descriptor to retrieve. + * \param descriptor_index Index of the CONTROL descriptor to retrieve. + * \param descriptor_buffer On return, will contain the requested descriptor if sync != 0. + * \param len Length of the memory area pointed to by descriptor_buffer. If sync != 0, will be updated to contain the length of the values contained in the response. + * \param status On return in sync mode, will contain the status from the AECP response if command was successful. Ignored otherwise. + * \param sync If sync == 0, the function will return without waiting for a response from the target entity. Otherwise, the function will block until receiving a response + * and descriptor, len and status will be updated to match the response. + * + */ +int aecp_aem_send_read_descriptor(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 configuration_index, avb_u16 descriptor_type, avb_u16 descriptor_index, void *buffer, avb_u16 *len, avb_u8 *status, int sync) +{ + struct avb_aecp_msg aecp_msg; + struct aecp_aem_pdu *aecp_cmd = (struct aecp_aem_pdu *) aecp_msg.buf; + struct aecp_aem_read_desc_cmd_pdu *read_desc_cmd = (struct aecp_aem_read_desc_cmd_pdu *)(aecp_cmd + 1); + int rc; + + memset(aecp_cmd, 0, sizeof(struct aecp_aem_pdu)); + memset(read_desc_cmd, 0, sizeof(struct aecp_aem_read_desc_cmd_pdu)); + aecp_msg.len = sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_read_desc_cmd_pdu); + AECP_AEM_SET_U_CMD_TYPE(aecp_cmd, 0, AECP_AEM_CMD_READ_DESCRIPTOR); + aecp_msg.msg_type = AECP_AEM_COMMAND; + memcpy(&aecp_cmd->entity_id, entity_id, 8); + + read_desc_cmd->configuration_index = htons(configuration_index); + read_desc_cmd->descriptor_type = htons(descriptor_type); + read_desc_cmd->descriptor_index = htons(descriptor_index); + + if (sync) { + unsigned int msg_type = AVB_MSG_AECP; + unsigned int msg_len = sizeof(struct avb_aecp_msg); + rc = avb_control_send_sync(ctrl_h, &msg_type, &aecp_msg, sizeof(struct avb_aecp_msg), &aecp_msg, &msg_len, -1); + + if (buffer && len && (rc == AVB_SUCCESS) && (msg_type == AVB_MSG_AECP) && (aecp_msg.status == AECP_AEM_SUCCESS)) { + struct aecp_aem_read_desc_rsp_pdu *read_desc_rsp = (struct aecp_aem_read_desc_rsp_pdu *)(aecp_cmd + 1); + void *descriptor_rsp = read_desc_rsp + 1; + avb_u16 descriptor_len; + + descriptor_len = aecp_msg.len - sizeof(struct aecp_aem_pdu) - sizeof(struct aecp_aem_read_desc_rsp_pdu); + if (descriptor_len > *len) { + printf("%s: WARNING: response values (%d bytes) cannot fit in provided space (%d bytes)\n", __func__, descriptor_len, *len); + descriptor_len = *len; + } + + *len = descriptor_len; + memcpy(buffer, descriptor_rsp, descriptor_len); + } + + if (status && (rc == AVB_SUCCESS)) + *status = aecp_msg.status; + + } + else + rc = avb_control_send(ctrl_h, AVB_MSG_AECP, &aecp_msg, sizeof(struct avb_aecp_msg)); + + if (rc != AVB_SUCCESS) + printf("%s: avb_control_send_sync() failed: AVB_MSG_AECP READ DESCRIPTOR entity_id(%" PRIx64 ") desc_idx(%d) error %s\n", __func__, ntohll(*entity_id), descriptor_index, avb_strerror(rc)); + + return rc; +} + + +/** Sends an AECP START_STREAM command. + * + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message to. + * \param descriptor_type Type of the stream descriptor to start (AEM_DESC_TYPE_STREAM_OUTPUT or AEM_DESC_TYPE_STREAM_INPUT). + * \param descriptor_index Index of the stream descriptor to start. + * \param status On return in sync mode, will contain the status from the AECP response if command was successful. Ignored otherwise. + * \param sync If sync == 0, the function will return without waiting for a response from the target entity. Otherwise, the function will block until receiving a response + * and descriptor, len and status will be updated to match the response. + * + */ +int aecp_aem_send_start_streaming(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_type, avb_u16 descriptor_index, avb_u8 *status, int sync) +{ + struct avb_aecp_msg aecp_msg; + struct aecp_aem_pdu *aecp_cmd = (struct aecp_aem_pdu *) aecp_msg.buf; + struct aecp_aem_start_streaming_cmd_pdu *start_streaming_cmd = (struct aecp_aem_start_streaming_cmd_pdu *)(aecp_cmd + 1); + int rc; + + memset(aecp_cmd, 0, sizeof(struct aecp_aem_pdu)); + memset(start_streaming_cmd, 0, sizeof(struct aecp_aem_start_streaming_cmd_pdu)); + aecp_msg.len = sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_start_streaming_cmd_pdu); + AECP_AEM_SET_U_CMD_TYPE(aecp_cmd, 0, AECP_AEM_CMD_START_STREAMING); + aecp_msg.msg_type = AECP_AEM_COMMAND; + memcpy(&aecp_cmd->entity_id, entity_id, 8); + + start_streaming_cmd->descriptor_type = htons(descriptor_type); + start_streaming_cmd->descriptor_index = htons(descriptor_index); + + if (sync) { + unsigned int msg_type = AVB_MSG_AECP; + unsigned int msg_len = sizeof(struct avb_aecp_msg); + rc = avb_control_send_sync(ctrl_h, &msg_type, &aecp_msg, sizeof(struct avb_aecp_msg), &aecp_msg, &msg_len, -1); + + if (status && (rc == AVB_SUCCESS)) + *status = aecp_msg.status; + } + else + rc = avb_control_send(ctrl_h, AVB_MSG_AECP, &aecp_msg, sizeof(struct avb_aecp_msg)); + + if (rc != AVB_SUCCESS) + printf("%s: avb_control_send_sync() failed: AVB_MSG_AECP START_STREAMING entity_id(%" PRIx64 ") desc_idx(%d) error %s\n", __func__, ntohll(*entity_id), descriptor_index, avb_strerror(rc)); + + return rc; +} + +/** Sends an AECP STOP_STREAM command. + * + * \return AVB_SUCCESS on success, or genavb error code. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_CONTROLLER channel. + * \param entity_id Pointer to the entity ID [Network Order] to send the message. + * \param descriptor_type Type of the stream descriptor to stop (AEM_DESC_TYPE_STREAM_OUTPUT or AEM_DESC_TYPE_STREAM_INPUT). + * \param descriptor_index Index of the stream descriptor to stop. + * \param status On return in sync mode, will contain the status from the AECP response if command was successful. Ignored otherwise. + * \param sync If sync == 0, the function will return without waiting for a response from the target entity. Otherwise, the function will block until receiving a response + * and descriptor, len and status will be updated to match the response. + * + */ +int aecp_aem_send_stop_streaming(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_type, avb_u16 descriptor_index, avb_u8 *status, int sync) +{ + struct avb_aecp_msg aecp_msg; + struct aecp_aem_pdu *aecp_cmd = (struct aecp_aem_pdu *) aecp_msg.buf; + struct aecp_aem_stop_streaming_cmd_pdu *stop_streaming_cmd = (struct aecp_aem_stop_streaming_cmd_pdu *)(aecp_cmd + 1); + int rc; + + memset(aecp_cmd, 0, sizeof(struct aecp_aem_pdu)); + memset(stop_streaming_cmd, 0, sizeof(struct aecp_aem_stop_streaming_cmd_pdu)); + aecp_msg.len = sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_stop_streaming_cmd_pdu); + AECP_AEM_SET_U_CMD_TYPE(aecp_cmd, 0, AECP_AEM_CMD_STOP_STREAMING); + aecp_msg.msg_type = AECP_AEM_COMMAND; + memcpy(&aecp_cmd->entity_id, entity_id, 8); + + stop_streaming_cmd->descriptor_type = htons(descriptor_type); + stop_streaming_cmd->descriptor_index = htons(descriptor_index); + + if (sync) { + unsigned int msg_type = AVB_MSG_AECP; + unsigned int msg_len = sizeof(struct avb_aecp_msg); + rc = avb_control_send_sync(ctrl_h, &msg_type, &aecp_msg, sizeof(struct avb_aecp_msg), &aecp_msg, &msg_len, -1); + + if (status && (rc == AVB_SUCCESS)) + *status = aecp_msg.status; + } + else + rc = avb_control_send(ctrl_h, AVB_MSG_AECP, &aecp_msg, sizeof(struct avb_aecp_msg)); + + if (rc != AVB_SUCCESS) + printf("%s: avb_control_send_sync() failed: AVB_MSG_AECP STOP_STREAMING entity_id(%" PRIx64 ") desc_idx(%d) error %s\n", __func__, ntohll(*entity_id), descriptor_index, avb_strerror(rc)); + + return rc; +} + +/** Creates a human-readable string describing an AVDECC format structure. + * + * \return Number of bytes copied into str. May be more than size if output had to be truncated (the function will never copy more than size bytes into str), or a negative value on error. + * \param format Pointer to the AVDECC format structure to decode. + * \param str Pointer to character string to use as output buffer. + * \param size Size in bytes of the buffer pointed to by str. + */ +int avdecc_fmt_pretty_printf(const struct avdecc_format *format, char *str, size_t size) +{ +#define APPEND_STR(...) do { \ + rc = snprintf(current, remaining, __VA_ARGS__); \ + if (rc >= remaining) return (size - remaining + rc); \ + if (rc < 0) return rc; \ + current += rc; \ + remaining -= rc; \ + } while (0) + + + char *current = str; + int remaining = size; + int rc; + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + if (format->u.s.subtype_u.iec61883.sf == 0) + APPEND_STR("IIDC"); + else + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_6: // For AVDECC formats, fdf != IEC_61883_6_FDF_NODATA should be a valid assumption + APPEND_STR("61883-6"); + switch (format->u.s.subtype_u.iec61883.format_u.iec61883_6.fdf_u.fdf.evt) { + case IEC_61883_6_FDF_EVT_AM824: + APPEND_STR(" AM824"); + break; + case IEC_61883_6_FDF_EVT_FLOATING: + APPEND_STR(" FLOAT"); + break; + case IEC_61883_6_FDF_EVT_INT32: + APPEND_STR(" INT32"); + break; + default: + APPEND_STR(" unknown"); + break;; + } + + APPEND_STR(" %dchans", avdecc_fmt_channels_per_sample(format)); + APPEND_STR(" %dHz", avdecc_fmt_sample_rate(format)); + break; + case IEC_61883_CIP_FMT_4: + APPEND_STR("61883-4/MPEG2-TS"); + break; + case IEC_61883_CIP_FMT_8: + APPEND_STR("61883-8"); + break; + default: + APPEND_STR("undetermined"); + } + + + break; + case AVTP_SUBTYPE_AAF: + APPEND_STR("AAF"); + APPEND_STR(" %dchans", avdecc_fmt_channels_per_sample(format)); + APPEND_STR(" %d/%dbits", format->u.s.subtype_u.aaf.format_u.pcm.bit_depth, avdecc_fmt_bits_per_sample(format)); + APPEND_STR(" %dHz", avdecc_fmt_sample_rate(format)); + APPEND_STR(" %dsamples/packet", avdecc_fmt_samples_per_packet(format, SR_CLASS_A)); //samples_per_packet doesn't rely on SR class + break; + + case AVTP_SUBTYPE_CVF: + APPEND_STR("CVF"); + break; + default: + APPEND_STR("undetermined"); + } + + return (size - remaining); +} diff --git a/apps/linux/common/aecp.h b/apps/linux/common/aecp.h new file mode 100644 index 0000000..cc1582f --- /dev/null +++ b/apps/linux/common/aecp.h @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2019-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_AECP_H_ +#define _COMMON_AECP_H_ + +#include + +int aecp_aem_send_set_control(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, int unsolicited, avb_u16 descriptor_index, void *value, avb_u16 *len, avb_u8 *status, int sync); +int aecp_aem_send_set_control_single_u8(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, avb_u8 value); +int aecp_aem_send_set_control_single_u8_command(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, avb_u8 *value, avb_u8 *status, int sync); +int aecp_aem_send_set_control_single_u8_response(struct avb_control_handle *ctrl_h, avb_u16 descriptor_index, avb_u8 *value); +int aecp_aem_send_set_control_single_u8_unsolicited_response(struct avb_control_handle *ctrl_h, avb_u16 descriptor_index, avb_u8 *value); +int aecp_aem_send_set_control_utf8(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, const char *value); +int aecp_aem_send_set_control_utf8_command(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, const char *value, avb_u8 *status, int sync); +int aecp_aem_send_set_control_utf8_response(struct avb_control_handle *ctrl_h, avb_u16 descriptor_index, const char *value); +int aecp_aem_send_set_control_utf8_unsolicited_response(struct avb_control_handle *ctrl_h, avb_u16 descriptor_index, const char *value); +int aecp_aem_send_get_control(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_index, void *value, avb_u16 *len, avb_u8 *status, int sync); +int aecp_aem_acquire_entity(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, int acquire); +int aecp_aem_send_read_descriptor(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 configuration_index, avb_u16 descriptor_type, avb_u16 descriptor_index, void *buffer, avb_u16 *len, avb_u8 *status, int sync); +int aecp_aem_send_start_streaming(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_type, avb_u16 descriptor_index, avb_u8 *status, int sync); +int aecp_aem_send_stop_streaming(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 descriptor_type, avb_u16 descriptor_index, avb_u8 *status, int sync); +int avdecc_fmt_pretty_printf(const struct avdecc_format *format, char *str, size_t size); + +#endif /* _COMMON_AECP_H_ */ diff --git a/apps/linux/common/alsa.c b/apps/linux/common/alsa.c new file mode 100644 index 0000000..9d4e326 --- /dev/null +++ b/apps/linux/common/alsa.c @@ -0,0 +1,637 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/time.h" +#include "alsa.h" + + +#define min(a,b) ((a)<(b)?(a):(b)) + +#define K 1024 +#define EVENT_BUF_SZ (K) +#define MIN_BUFFER_TO_BATCH_SIZE_RATIO 4 + +/** Configure actual ALSA device based on the alsa tx context information. + * @alsa: alsa tx context to configure + * + * Returns 0 on success or ALSA error code otherwise. + */ +int alsa_set_format(struct alsa_common *alsa, snd_pcm_stream_t direction) +{ + snd_pcm_t *handle = alsa->handle; + snd_pcm_hw_params_t *hwparams = alsa->hwparams; + snd_pcm_sw_params_t *swparams = alsa->swparams; + snd_pcm_uframes_t period_size, buffer_size_ns; + int ret = 0, dir; + snd_pcm_access_t access_type = SND_PCM_ACCESS_MMAP_INTERLEAVED; + int resample = 0; /* disable alsa-lib resampling */ + unsigned int configured_rate; + + /* choose all parameters */ + ret = snd_pcm_hw_params_any(handle, hwparams); + if (ret < 0) { + printf("alsa(%p) Broken configuration: no configurations available: %s\n", alsa, snd_strerror(ret)); + return ret; + } + /* set hardware resampling */ + ret = snd_pcm_hw_params_set_rate_resample(handle, hwparams, resample); + if (ret < 0) { + printf("alsa(%p) Resampling setup failed: %s\n", alsa, snd_strerror(ret)); + return ret; + } + /* set the interleaved read/write format */ + ret = snd_pcm_hw_params_set_access(handle, hwparams, access_type); + if (ret < 0) { + printf("alsa(%p) Access type not available: %s\n", alsa, snd_strerror(ret)); + return ret; + } + + ret = snd_pcm_hw_params_set_format(handle, hwparams, alsa->format); + if (ret < 0) { + printf("alsa(%p) Sample format not available: %s\n", alsa, snd_strerror(ret)); + return ret; + } + + if (alsa->channels_per_frame) { + ret = snd_pcm_hw_params_set_channels(handle, hwparams, alsa->channels_per_frame); + if (ret < 0) { + printf("alsa(%p) Channels count (%i) not available: %s\n", alsa, alsa->channels_per_frame, snd_strerror(ret)); + return ret; + } + } + + configured_rate = alsa->rate; + ret = snd_pcm_hw_params_set_rate_near(handle, hwparams, &configured_rate, 0); + if (ret < 0) { + printf("alsa(%p) Rate %iHz not available: %s\n", alsa, alsa->rate, snd_strerror(ret)); + return ret; + } + if (configured_rate != alsa->rate) { + printf("alsa(%p) Rate doesn't match (requested %iHz, get %iHz)\n", alsa, alsa->rate, configured_rate); + return -1; //FIXME define proper error return codes + } + + if (direction == SND_PCM_STREAM_CAPTURE) + period_size = CFG_ALSA_CAPTURE_LATENCY_NS / alsa->nsecs_per_frame; + else + period_size = CFG_ALSA_PERIOD_SIZE; + + /* set the period size */ + ret = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, &dir); + if (ret < 0) { + printf("alsa(%p) Unable to set period size %u: %s\n", alsa, (unsigned int)period_size, snd_strerror(ret)); + return ret; + } + + /* set the buffer size, round up to power of two */ + if (direction == SND_PCM_STREAM_CAPTURE) + buffer_size_ns = CFG_ALSA_CAPTURE_BUFFER_SIZE_NS; + else + buffer_size_ns = CFG_ALSA_PLAYBACK_BUFFER_SIZE_NS; + + alsa->buffer_size = 1; + while (((alsa->buffer_size * 100000) / alsa->rate) < (buffer_size_ns / 10000)) + alsa->buffer_size <<= 1; + + while (alsa->buffer_size < (MIN_BUFFER_TO_BATCH_SIZE_RATIO * alsa->batch_size)) + alsa->buffer_size <<= 1; + + ret = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &alsa->buffer_size); + if (ret < 0) { + printf("alsa(%p) Unable to set buffer size %u: %s\n", alsa, (unsigned int)alsa->buffer_size, snd_strerror(ret)); + return ret; + } + + ret = snd_pcm_hw_params_get_period_size(hwparams, &period_size, &dir); + if (ret < 0) { + printf("alsa(%p) Unable to get period size: %s\n", alsa, snd_strerror(ret)); + return ret; + } + + printf("alsa(%p) buffer size %u, period size %u\n", alsa, (unsigned int)alsa->buffer_size, (unsigned int)period_size); + + /* write the parameters to device */ + ret = snd_pcm_hw_params(handle, hwparams); + if (ret < 0) { + printf("alsa(%p) Unable to set hw params: %s\n", alsa, snd_strerror(ret)); + return ret; + } + + /* get the current swparams */ + ret = snd_pcm_sw_params_current(handle, swparams); + if (ret < 0) { + printf("alsa(%p) Unable to determine current swparams: %s\n", alsa, snd_strerror(ret)); + return ret; + } + + ret = snd_pcm_sw_params_set_stop_threshold(handle, swparams, alsa->buffer_size); + if (ret < 0) { + printf("alsa(%p) Unable to set stop threshold mode: %s\n", alsa, snd_strerror(ret)); + return ret; + } + + if ((ret = snd_pcm_sw_params_set_period_event(handle, swparams, 1)) < 0) { + printf("Unable to enable periodic events for capture: %s\n", snd_strerror(ret)); + return ret; + } + + ret = snd_pcm_sw_params(handle, swparams); + if (ret < 0) { + printf("alsa(%p) Unable to set sw params: %s\n", alsa, snd_strerror(ret)); + return ret; + } + return ret; +} + +/* This function swap endianness then adjust padding for AAF 24/32 bits format for alsa playback: + * As S24_LE alsa is putting the padding in the upper 8 bits (MSB padding) which will result when converting in + * Big endian for AVTPDU to have the padding in the lower bits which contradicts AVTP IEEE 1722-2016 7.3.4 + * that imposes the unused bits to be at the upper bits inside the AVTPDU (LSB padding) + * Apply this function on samples in LE format (e.g for talker : just after the capture from alsa and before converting + * to BE network format and for listener: before alsa playback and after converting from BE network format) + */ +static void alsa_swap_data_32_adjust_padding_s24_le_playback(struct alsa_tx *alsa, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + + src_sample = dst_sample = src_frame; + + if ((alsa->common.bytes_per_sample != 4) || (alsa->common.direction != SND_PCM_STREAM_PLAYBACK)) + return; + + for (i = 0; i < to_commit * alsa->common.channels_per_frame; i++) { + /* Do endianess conversion */ + *(unsigned int *)dst_sample = bswap_32(*(unsigned int *)src_sample); + /* Adjust padding: move unused bits from LSB (lower bits) to MSB (upper bits)*/ + *(unsigned int *)dst_sample = (*(unsigned int *)src_sample) >> 8; + + dst_sample += 4; + src_sample += 4; + } +} + +static void alsa_swap_data_32(struct alsa_tx *alsa, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + + src_sample = dst_sample = src_frame; + + if (alsa->common.bytes_per_sample != 4) + return; + + /* Do endianess conversion */ + for (i = 0; i < to_commit * alsa->common.channels_per_frame; i++) { + *(unsigned int *)dst_sample = bswap_32(*(unsigned int *)src_sample); + + dst_sample += 4; + src_sample += 4; + } +} + +static void alsa_swap_data_24(struct alsa_tx *alsa, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + unsigned char tmp; + + src_sample = dst_sample = src_frame; + + if (alsa->common.bytes_per_sample != 3) + return; + + /* Do endianess conversion */ + for (i = 0; i < to_commit * alsa->common.channels_per_frame; i++) { + tmp = *(unsigned char *)(src_sample + 2); + *(unsigned char *)(dst_sample + 2) = *(unsigned char *)(src_sample + 0); + *(unsigned char *)(dst_sample + 1) = *(unsigned char *)(src_sample + 1); + *(unsigned char *)(dst_sample + 0) = tmp; + + dst_sample += 3; + src_sample += 3; + } +} + +static void alsa_swap_data_16(struct alsa_tx *alsa, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + + src_sample = dst_sample = src_frame; + + if (alsa->common.bytes_per_sample != 2) + return; + + /* Do endianess conversion */ + for (i = 0; i < to_commit * alsa->common.channels_per_frame; i++) { + *(unsigned short *)dst_sample = bswap_16(*(unsigned short *)src_sample); + + dst_sample += 2; + src_sample += 2; + } +} + +int alsa_common_init(struct alsa_common *alsa, struct avdecc_format *avdecc_format, snd_pcm_stream_t direction, unsigned int batch_size, const char *alsa_device) +{ + int err; + + err = snd_output_stdio_attach(&alsa->output, stdout, 0); + if (err < 0) { + printf("alsa(%p) Output failed: %s\n", alsa, snd_strerror(err)); + goto hwparams_malloc_err; + } + + err = snd_pcm_hw_params_malloc(&alsa->hwparams); + if (err < 0) { + printf("alsa(%p) Cannot allocate ALSA HW param structure (%s)\n", alsa, snd_strerror(err)); + goto hwparams_malloc_err; + } + + err = snd_pcm_sw_params_malloc(&alsa->swparams); + if (err < 0) { + printf("alsa(%p) Cannot allocate ALSA SW param structure (%s)\n", alsa, snd_strerror(err)); + goto swparams_malloc_err; + } + + if ((err = snd_pcm_open(&alsa->handle, alsa_device, direction, 0)) < 0) { + printf("alsa(%p) Playback open error: %s\n", alsa, snd_strerror(err)); + goto pcm_open_err; + } + + alsa->avdecc_format = *avdecc_format; + alsa->batch_size = batch_size; + alsa->direction = direction; + /* To avoid unaligned accesses and simplify code a bit, unused_bits will be handled by shifting the value and + * padding with zeroes. + */ + alsa->rate = avdecc_fmt_sample_rate(&alsa->avdecc_format); + alsa->bytes_per_sample = avdecc_fmt_bits_per_sample(&alsa->avdecc_format) /8; + alsa->channels_per_frame = avdecc_fmt_channels_per_sample(&alsa->avdecc_format); + alsa->frame_size = avdecc_fmt_sample_size(&alsa->avdecc_format); + alsa->unused_bits = avdecc_fmt_unused_bits(&alsa->avdecc_format); + alsa->nsecs_per_frame = NSECS_PER_SEC/alsa->rate; + if (avdecc_fmt_audio_is_float(&alsa->avdecc_format)) + alsa->format = SND_PCM_FORMAT_FLOAT_LE; // Assume float type is always 32bit + else { + switch (alsa->bytes_per_sample) { + case 4: + switch (alsa->unused_bits) { + case 8: + alsa->format = SND_PCM_FORMAT_S24_LE; + break; + + case 0: + alsa->format = SND_PCM_FORMAT_S32_LE; + break; + + default: + goto config_err; + break; + } + + break; + + case 3: + switch (alsa->unused_bits) { + case 0: + alsa->format = SND_PCM_FORMAT_S24_3LE; + break; + + default: + goto config_err; + break; + } + + break; + + case 2: + switch (alsa->unused_bits) { + case 0: + alsa->format = SND_PCM_FORMAT_S16_LE; + break; + + default: + goto config_err; + break; + } + + break; + + default: + goto config_err; + break; + } + } + + /* set the sample format */ + err = alsa_set_format(alsa, direction); + if (err < 0) + goto config_err; + + return 0; + + +config_err: + snd_pcm_close(alsa->handle); +pcm_open_err: + snd_pcm_sw_params_free(alsa->swparams); +swparams_malloc_err: + snd_pcm_hw_params_free(alsa->hwparams); +hwparams_malloc_err: + return err; +} + +void alsa_common_exit(struct alsa_common *common) +{ + snd_pcm_close(common->handle); + snd_pcm_sw_params_free(common->swparams); + snd_pcm_hw_params_free(common->hwparams); +} + +static void alsa_tx_stats_min_alsa(struct stats *s) +{ + struct alsa_tx *alsa = s->priv; + + stats_update(&alsa->latency, s->min); +} + +static void alsa_tx_stats_alsa(struct stats *s) +{ + struct alsa_tx *alsa = s->priv; + + printf("alsa_tx(%p) frame: %8u alsa latency %4d/%4d/%4d (us)\n", alsa, alsa->common.count, + alsa->latency.min * USECS_PER_SEC / alsa->common.rate, + alsa->latency.mean * USECS_PER_SEC / alsa->common.rate, + alsa->latency.max * USECS_PER_SEC / alsa->common.rate); +} + +static void alsa_tx_stats_frame_rate(struct stats *s) +{ + struct alsa_tx *alsa = s->priv; + + printf("alsa_tx(%p) frame: %8u input frame rate %4d/%4d/%4d (fps)\n", alsa, alsa->common.count, + alsa->frame_rate.min, alsa->frame_rate.mean, alsa->frame_rate.max); +} + +/** Opens and initialize the ALSA device. + * @stream_id: pointer to the u64 stream ID value. + * @avdecc_format: pointer to the AVDECC format of the stream that will be received + * @batch_size: size of the batches coming from the GenAVB library, in bytes + * @alsa_device: Alsa device to be used for playback. + * + * Returns an alsa tx context structure. + */ +void *alsa_tx_init(void *stream_id, struct avdecc_format *avdecc_format, unsigned int batch_size, const char *alsa_device) +{ + int err; + struct alsa_tx *alsa; + + alsa = malloc(sizeof(struct alsa_tx)); + if (!alsa) { + printf("alsa_tx_int Malloc allocation error for stream %llu.\n", *(unsigned long long *)stream_id); + goto alsa_malloc_err; + } + + memset(alsa, 0, sizeof(struct alsa_tx)); + + err = alsa_common_init(&alsa->common, avdecc_format, SND_PCM_STREAM_PLAYBACK, batch_size, alsa_device); + + if (err < 0) { + printf("alsa_tx(%p) Output failed: %s\n", alsa, snd_strerror(err)); + goto hwparams_malloc_err; + } + + /*Set the right process sample function*/ + switch (alsa->common.bytes_per_sample) { + case 4: + if (alsa->common.format == SND_PCM_FORMAT_S24_LE && avdecc_format_is_aaf_pcm(&alsa->common.avdecc_format)) + alsa->alsa_process_samples = alsa_swap_data_32_adjust_padding_s24_le_playback; + else + alsa->alsa_process_samples = alsa_swap_data_32; + + break; + case 3: + alsa->alsa_process_samples = alsa_swap_data_24; + break; + case 2: + alsa->alsa_process_samples = alsa_swap_data_16; + break; + default: + printf("alsa_tx(%p) Unsupported bytes_per_sample %u\n", alsa, alsa->common.bytes_per_sample); + goto alsa_bytes_per_sample_err; + } + + snd_pcm_dump(alsa->common.handle, alsa->common.output); + + alsa->common.count = 0; + alsa->common.running = 0; + + stats_init(&alsa->latency_min, 7, alsa, alsa_tx_stats_min_alsa); + stats_init(&alsa->latency, 5, alsa, alsa_tx_stats_alsa); + stats_init(&alsa->frame_rate, 7, alsa, alsa_tx_stats_frame_rate); + + //printf("alsa_tx(%p) done\n", alsa); + + return alsa; + +alsa_bytes_per_sample_err: + alsa_common_exit(&alsa->common); +hwparams_malloc_err: + free(alsa); +alsa_malloc_err: + return NULL; +} + +void alsa_tx_exit(void *priv) +{ + struct alsa_tx *alsa = (struct alsa_tx *)priv; + alsa_common_exit(&alsa->common); + free(alsa); + + //printf("done\n"); +} + +/** Write frames of silence into an ALSA ring buffer + * @alsa: alsa_tx context to write to + * @n_frames: number of silence frames to insert + * + * Returns 0 on success or the alsa_lib error code otherwise. + * Note: since this function is used to start a stream, and starting a stream is done + * to recover from Xruns, we don't try and recover from any ALSA errors here. + */ +static int alsa_write_silence(struct alsa_tx *alsa, snd_pcm_uframes_t n_frames) +{ + snd_pcm_uframes_t offset, to_commit, size = n_frames; + const snd_pcm_channel_area_t *areas; + int err; + + while (size > 0) { + to_commit = size; + err = snd_pcm_avail_update(alsa->common.handle); + if (err < 0) { + printf("alsa_tx(%p) snd_pcm_avail_update error\n", alsa); + goto alsa_err; + } + err = snd_pcm_mmap_begin(alsa->common.handle, &areas, &offset, &to_commit); + if (err < 0) { + printf("alsa_tx(%p) MMAP begin error\n", alsa); + goto alsa_err; + } + + //printf("alsa_tx(%p) size: %d to_commit: %d\n", alsa, size, to_commit); + err = snd_pcm_areas_silence(areas, offset, alsa->common.channels_per_frame, to_commit, alsa->common.format); + if (err < 0) { + printf("alsa_tx(%p) snd_pcm_areas_silence error\n", alsa); + goto alsa_err; + } + + err = snd_pcm_mmap_commit(alsa->common.handle, offset, to_commit); + if (err < 0) { + printf("alsa_tx(%p) MMAP commit error\n", alsa); + goto alsa_err; + } + + size -= to_commit; + } + + return 0; + +alsa_err: + printf("alsa_tx(%p) frame: %8u Recovering from error %s state = %d\n", alsa, alsa->common.count, snd_strerror(err), snd_pcm_state(alsa->common.handle)); + return err; + +} + +int alsa_tx(void *priv, struct avb_stream_handle *stream_h, struct avb_stream_params *stream_params) +{ + struct alsa_tx *alsa = (struct alsa_tx *)priv; + int ret = 0; + const snd_pcm_channel_area_t *areas; + void *src_frame; + snd_pcm_uframes_t offset, frames_to_commit, frames_committed; + snd_pcm_uframes_t frames_remaining; + snd_pcm_sframes_t avail, delay; + unsigned int bytes_to_read, bytes_written; + int nbytes; + + /* if the stream is not started, start it now after adding some buffer padding ...*/ + if (!alsa->common.running) { + snd_pcm_drop(alsa->common.handle); + snd_pcm_prepare(alsa->common.handle); + snd_pcm_reset(alsa->common.handle); + alsa_write_silence(alsa, CFG_ALSA_PLAYBACK_LATENCY_NS / alsa->common.nsecs_per_frame); + ret = snd_pcm_start(alsa->common.handle); + if (ret < 0) { + printf("alsa_tx(%p) Couldn't start stream: error = %s state = %d\n", alsa, snd_strerror(ret), snd_pcm_state(alsa->common.handle)); + goto exit; + } + alsa->common.running = 1; + + if (!gettime_ns(&alsa->frame_rate_last)) { + alsa->frame_rate_count = 0; + stats_reset(&alsa->frame_rate); + } + else + printf("alsa_tx(%p) Couldn't get time for frame rate statistics\n", alsa); + } + + + ret = snd_pcm_avail_delay(alsa->common.handle, &avail, &delay); + if (ret < 0) { + printf("alsa_tx(%p) pcm_avail_delay error\n", alsa); + goto alsa_err; + } + + if ((avail * alsa->common.frame_size) < alsa->common.batch_size) { + printf("alsa_tx(%p) frame: %8u ALSA ring buffer almost full, restarting stream\n", alsa, alsa->common.count); + goto alsa_err; + } + +#define ESAI_FIFO_THRESHOLD 64 + + stats_update(&alsa->latency_min, delay + ESAI_FIFO_THRESHOLD / alsa->common.channels_per_frame); + + frames_remaining = avail; + bytes_written = 0; + do { + frames_to_commit = frames_remaining; + + ret = snd_pcm_mmap_begin(alsa->common.handle, &areas, &offset, &frames_to_commit); + if (ret < 0) { + printf("alsa_tx(%p) MMAP begin error\n", alsa); + goto alsa_err; + } + + bytes_to_read = frames_to_commit*alsa->common.frame_size; + src_frame = areas[0].addr + areas[0].first / 8 + offset * (areas[0].step / 8); + nbytes = avb_stream_receive(stream_h, src_frame, bytes_to_read, NULL, NULL); + if (nbytes <= 0) { + if (nbytes < 0) + printf("avb_stream_receive error stream_handle(%p) ret(%d) data(%p) len(%u) size(%u) frames_remaining(%u)\n", stream_h, nbytes, src_frame, bytes_to_read, alsa->common.frame_size, (unsigned int)frames_remaining); + + ret = snd_pcm_mmap_commit(alsa->common.handle, offset, 0); + if (ret != 0) { + printf("alsa_tx(%p) MMAP commit error while recovering from avb_stream_receive_error(%s)\n", alsa, avb_strerror(nbytes)); + goto alsa_err; + } + if (nbytes < 0) + ret = nbytes; + else + ret = bytes_written; + goto exit; + } + + frames_committed = nbytes / alsa->common.frame_size; + + if (alsa->alsa_process_samples) + alsa->alsa_process_samples(alsa, src_frame, frames_committed); + + ret = snd_pcm_mmap_commit(alsa->common.handle, offset, frames_committed); + if (ret != frames_committed) { + printf("alsa_tx(%p) MMAP commit error\n", alsa); + goto alsa_err; + } + + frames_remaining -= frames_committed; + bytes_written += nbytes; + alsa->common.count += frames_committed; + alsa->frame_rate_count += frames_committed; + } while (nbytes == bytes_to_read); + + ret = bytes_written; + goto exit; + +alsa_err: + printf("alsa_tx(%p) frame: %8u Recovering from error %s state = %d avail = %d delay = %d\n", alsa, alsa->common.count, snd_strerror(ret), snd_pcm_state(alsa->common.handle), (int)avail, (int)delay); + alsa->common.running = 0; + +exit: + if (alsa->frame_rate_count >= (alsa->common.rate >> 4) ) { + uint64_t now; + + if (!gettime_ns(&now)) { + unsigned int frame_rate = ((unsigned long long)alsa->frame_rate_count * NSECS_PER_SEC) / (now - alsa->frame_rate_last); + stats_update(&alsa->frame_rate, frame_rate); + alsa->frame_rate_last = now; + alsa->frame_rate_count = 0; + } + } + + return ret; +} diff --git a/apps/linux/common/alsa.h b/apps/linux/common/alsa.h new file mode 100644 index 0000000..9d38e1a --- /dev/null +++ b/apps/linux/common/alsa.h @@ -0,0 +1,66 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _ALSA_H_ +#define _ALSA_H_ + +#include +#include + +#include "../common/stats.h" + + +#define CFG_ALSA_PLAYBACK_LATENCY_NS 2000000 // Additional fixed playback latency in ns +#define CFG_ALSA_CAPTURE_LATENCY_NS 2000000 // Additional fixed capture latency in ns +#define CFG_ALSA_PLAYBACK_BUFFER_SIZE_NS (sr_class_max_transit_time(SR_CLASS_B) + 1000000) // Requested Alsa playback buffer size, in ns +#define CFG_ALSA_CAPTURE_BUFFER_SIZE_NS 20000000 // Requested Alsa capture buffer size, in ns +#define CFG_ALSA_PERIOD_SIZE 24 // Note: period_size cannot be less than max(16, buffer_size/128) + + +struct alsa_common { + snd_pcm_t *handle; + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + snd_output_t *output; + snd_pcm_uframes_t buffer_size; + snd_pcm_format_t format; + snd_pcm_stream_t direction; + unsigned int batch_size; + unsigned int running; /**< 1 if stream is running, 0 otherwise. */ + unsigned int count; /**< Number of frames already played out. */ + struct avdecc_format avdecc_format; /**< stream format provided at stream init time */ + unsigned int rate; /**< Sample rate in Hz */ + unsigned int bytes_per_sample; /**< Number of bytes for each sample (directly related to sample_format) */ + unsigned int unused_bits; /**< Number of unused bits in each sample */ + unsigned int channels_per_frame; /**< Number of active channels per frame */ + unsigned int frame_size; /**< Size of a full frame, in bytes */ + unsigned int nsecs_per_frame; /**< Duration of an audio frame, in ns */ +}; + +struct alsa_rx { + struct alsa_common common; + int fd; + + unsigned int payload_size; + unsigned int payload_offset; +}; + +struct alsa_tx { + struct alsa_common common; + struct stats latency; + struct stats latency_min; + struct stats frame_rate; + uint64_t frame_rate_last; + unsigned int frame_rate_count; + void (*alsa_process_samples)(struct alsa_tx *alsa, void *src_frame, snd_pcm_uframes_t to_commit); +}; + +void *alsa_tx_init(void *stream_id, struct avdecc_format *avdecc_format, unsigned int batch_size, const char *alsa_device); +void alsa_tx_exit(void *priv); +int alsa_tx(void *priv, struct avb_stream_handle *stream_h, struct avb_stream_params *stream_params); + +#endif /* _ALSA_H_ */ diff --git a/apps/linux/common/alsa2.c b/apps/linux/common/alsa2.c new file mode 100644 index 0000000..6d0fe23 --- /dev/null +++ b/apps/linux/common/alsa2.c @@ -0,0 +1,1226 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020, 2022-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include "log.h" +#include "alsa2.h" +#include "common.h" +#include "stats.h" +#include "time.h" +#include "clock.h" +#include "common.h" +#include "clock_domain.h" + +#define CFG_ALSA_PLAYBACK_LATENCY_NS 2000000 // Additional fixed playback latency in ns +#define CFG_ALSA_MIN_SILENCE_FRAMES 8 // Minimum number of silence frames to add in a single go when starting a stream + +#define ALSA_EXTRA_DEBUG 0 + +static snd_output_t *s_alsa_output; + +static unsigned int wait_clk_domain_validity = 0; + +static const aar_alsa_param_t *alsa_get_param(aar_alsa_handle_t *handle); + +static void alsa_add_61883_6_label_swap_data_32(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit); +static void alsa_adjust_padding_s24_le_input_swap_data_32(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit); +static void alsa_swap_data_32_adjust_padding_s24_le_output(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit); +static void alsa_swap_data_32(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit); +static void alsa_swap_data_24(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit) __attribute__((unused)); +static void alsa_swap_data_16(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit) __attribute__((unused)); + +static void alsa_tx_stats_min_alsa(struct stats *s) +{ + aar_alsa_handle_t *handle = s->priv; + + stats_update(&handle->stats.alsa_latency, s->min * USECS_PER_SEC / handle->rate); +} + +static int alsa_reset(aar_alsa_handle_t *handle) +{ + snd_pcm_t *snd_handle = handle->handle; + + // DBG("handle: %p", handle); + snd_pcm_drop(snd_handle); + snd_pcm_prepare(snd_handle); + snd_pcm_reset(snd_handle); + + return 0; +} + +static unsigned int alsa_ns_to_samples(unsigned int ns, struct avb_stream_params *stream_params) +{ + return (((unsigned long long)ns * avdecc_fmt_sample_rate(&stream_params->format) + NSECS_PER_SEC - 1) / NSECS_PER_SEC); +} + +static unsigned int alsa_bytes_to_ns(unsigned int bytes, struct avb_stream_params *stream_params) +{ + return (((unsigned long long)bytes * NSECS_PER_SEC) / ((unsigned long long)avdecc_fmt_sample_rate(&stream_params->format) * avdecc_fmt_sample_size(&stream_params->format))); +} + + /* 61883-6 AM824 data format requires a label in the unused part of the 32 bits (24 bits of data). + */ +static void alsa_add_61883_6_label_swap_data_32(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + unsigned int bytes_per_sample = handle->frame_size / handle->channels; + + src_sample = dst_sample = src_frame; + + if (bytes_per_sample != 4 || handle->direction != AAR_DATA_DIR_INPUT) + return; + + for (i = 0; i < to_commit * handle->channels; i++) { + /* Add iec61883-6 label*/ + *(unsigned char *)dst_sample = AM824_LABEL_RAW; + /* Do endianess conversion */ + *(unsigned int *)dst_sample = bswap_32(*(unsigned int *)src_sample); + + dst_sample += 4; + src_sample += 4; + } +} +/* This function adjust padding for AAF 24/32 bits format then do endianness swap from LE to BE for input direction (stream talker) + * As S24_LE alsa is putting the padding in the upper 8 bits (MSB padding) which will result when converting in + * Big endian for AVTPDU to have the padding in the lower bits which contradicts AVTP IEEE 1722-2016 7.3.4 + * that imposes the unused bits to be at the upper bits inside the AVTPDU (LSB padding) + * Apply this function on samples in LE format (e.g for talker : just after the capture from alsa and before converting + * to BE network format and for listener: before alsa playback and after converting from BE network format) + */ +static void alsa_adjust_padding_s24_le_input_swap_data_32(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + unsigned int bytes_per_sample = handle->frame_size / handle->channels; + + src_sample = dst_sample = src_frame; + + if (bytes_per_sample != 4 || handle->direction != AAR_DATA_DIR_INPUT) + return; + + for (i = 0; i < to_commit * handle->channels; i++) { + /* Adjust padding: move unused bits from MSB (upper bits) to LSB (lower bits)*/ + *(unsigned int *)dst_sample = (*(unsigned int *)src_sample) << 8; + /* Do endianess conversion */ + *(unsigned int *)dst_sample = bswap_32(*(unsigned int *)src_sample); + + dst_sample += 4; + src_sample += 4; + } +} + +/* This function do the endianness conversion swap (network order BE -> LE) then adjust padding for AAF 24/32 bits format for output direction (stream listener) + */ +static void alsa_swap_data_32_adjust_padding_s24_le_output(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + unsigned int bytes_per_sample = handle->frame_size / handle->channels; + + src_sample = dst_sample = src_frame; + + if (bytes_per_sample != 4 || handle->direction != AAR_DATA_DIR_OUTPUT) + return; + + for (i = 0; i < to_commit * handle->channels; i++) { + /* Do endianess conversion */ + *(unsigned int *)dst_sample = bswap_32(*(unsigned int *)src_sample); + + /* Adjust padding: move unused bits from LSB (lower bits) to MSB (upper bits)*/ + *(unsigned int *)dst_sample = (*(unsigned int *)src_sample) >> 8; + + dst_sample += 4; + src_sample += 4; + } +} + +static void alsa_swap_data_32(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + unsigned int bytes_per_sample = handle->frame_size / handle->channels; + + src_sample = dst_sample = src_frame; + + if (bytes_per_sample != 4) + return; + + /* Do endianess conversion */ + for (i = 0; i < to_commit * handle->channels; i++) { + *(unsigned int *)dst_sample = bswap_32(*(unsigned int *)src_sample); + + dst_sample += 4; + src_sample += 4; + } +} + +static void alsa_swap_data_24(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + unsigned char tmp; + unsigned int bytes_per_sample = handle->frame_size / handle->channels; + + src_sample = dst_sample = src_frame; + + if (bytes_per_sample != 3) + return; + + /* Do endianess conversion */ + for (i = 0; i < to_commit * handle->channels; i++) { + tmp = *(unsigned char *)(src_sample + 2); + *(unsigned char *)(dst_sample + 2) = *(unsigned char *)(src_sample + 0); + *(unsigned char *)(dst_sample + 1) = *(unsigned char *)(src_sample + 1); + *(unsigned char *)(dst_sample + 0) = tmp; + + dst_sample += 3; + src_sample += 3; + } +} + +static void alsa_swap_data_16(aar_alsa_handle_t *handle, void *src_frame, snd_pcm_uframes_t to_commit) +{ + void *dst_sample, *src_sample; + int i; + unsigned int bytes_per_sample = handle->frame_size / handle->channels; + + src_sample = dst_sample = src_frame; + + if (bytes_per_sample != 2) + return; + + /* Do endianess conversion */ + for (i = 0; i < to_commit * handle->channels; i++) { + *(unsigned short *)dst_sample = bswap_16(*(unsigned short *)src_sample); + + dst_sample += 2; + src_sample += 2; + } +} + +/** + * @brief Initialize ALSA device + * + * @param[in] handle ALSA handle pointer + * @param[in] stream_params AVB stream parameters + * @param[in] dev_name ALSA device to be used + * + * @return 0 if everything are ok, else negative error code + */ +static int alsa_init(aar_alsa_handle_t *handle, struct avb_stream_params *stream_params, const char *dev_name) +{ + snd_pcm_t **snd_handle = &handle->handle; + snd_pcm_hw_params_t *hw_params; + snd_pcm_sw_params_t *sw_params; + + int err; + + snd_pcm_uframes_t buffer_size = 0; + snd_pcm_uframes_t period_size = 0; + int sub_dir; + snd_pcm_stream_t dir = SND_PCM_STREAM_PLAYBACK; + int alsa_period_time_ns; + aar_alsa_param_t *alsa_param; + + int i; + + if (handle->direction == AAR_DATA_DIR_OUTPUT) { + i = handle->device; + alsa_param = (aar_alsa_param_t *)&g_alsa_playback_params[i]; + dir = SND_PCM_STREAM_PLAYBACK; + } else { + i = handle->device; + alsa_param = (aar_alsa_param_t *)&g_alsa_capture_params[i]; + dir = SND_PCM_STREAM_CAPTURE; + } + + handle->rate = avdecc_fmt_sample_rate(&stream_params->format); + alsa_period_time_ns = alsa_param->period_time_ns; + handle->channels = avdecc_fmt_channels_per_sample(&stream_params->format); + handle->frame_size = avdecc_fmt_sample_size(&stream_params->format); + handle->frame_duration = NSECS_PER_SEC / handle->rate; + alsa_period_time_ns = max(alsa_period_time_ns, sr_class_interval_p(stream_params->stream_class) / sr_class_interval_q(stream_params->stream_class)); + + /*Check format and set the right sample processing function*/ + if (avdecc_format_is_aaf_pcm(&stream_params->format)) { + switch (avdecc_fmt_bits_per_sample(&stream_params->format)) { + case 32: + switch (avdecc_fmt_unused_bits(&stream_params->format)) { + case 8: + handle->alsa_process_samples = (handle->direction == AAR_DATA_DIR_OUTPUT) ? + alsa_swap_data_32_adjust_padding_s24_le_output + : alsa_adjust_padding_s24_le_input_swap_data_32; + alsa_param->format = SND_PCM_FORMAT_S24_LE; + break; + + case 0: + handle->alsa_process_samples = alsa_swap_data_32; + alsa_param->format = SND_PCM_FORMAT_S32_LE; + break; + + default: + ERR("%s : Unsupported bit depth for 32bits sample", dev_name); + return -1; + } + + break; + + default: + ERR("%s : Unsupported Alsa format", dev_name); + return -1; + } + } else if (avdecc_format_is_61883_6(&stream_params->format) && (AVDECC_FMT_61883_6_FDF_EVT(&stream_params->format) == IEC_61883_6_FDF_EVT_AM824)) { + handle->alsa_process_samples = (handle->direction == AAR_DATA_DIR_OUTPUT) ? + alsa_swap_data_32 + : alsa_add_61883_6_label_swap_data_32; + } else { + ERR("%s : Unsupported AVDECC format", dev_name); + return -1; + } + + // Calculate period size in frames + period_size = alsa_ns_to_samples(alsa_period_time_ns, stream_params); + + if (handle->direction == AAR_DATA_DIR_OUTPUT) { + /* For formats where the avtp timestamp doesn't match the first sample + in the packet (e.g, 61883-6) there can be an extra maximum offset of + packet size. As the buffer_size needs to be a multiple of period_size, + adding a packet_size (which cannot be greater than 1 period_size) or a + period _size is the same. But actually the equation should be: + buffer_size = max_transit_time + CFG_ALSA_PLAYBACK_LATENCY_NS + + period_size + packet_size; */ + buffer_size = alsa_ns_to_samples(sr_class_max_transit_time(stream_params->stream_class) + CFG_ALSA_PLAYBACK_LATENCY_NS, stream_params) + 2 * period_size; + buffer_size = ((buffer_size + period_size - 1) / period_size) * period_size; + } else { + // Buffer size is double of period size + buffer_size = period_size * 4; + } + + // Restart the statistic counter + handle->stats.counter_stats.tx_err = 0; + handle->stats.counter_stats.rx_err = 0; + handle->stats.counter_stats.period_rx = 0; + handle->stats.counter_stats.period_tx = 0; + + DBG("snd_pcm_open handle %p, dev [%s], dir %d", snd_handle, dev_name, dir); + if (dir == SND_PCM_STREAM_CAPTURE) { + DBG("CAPTURE device"); + } else { + DBG("PLAYBACK device"); + } + + if ((err = snd_pcm_open(snd_handle, dev_name, dir, 0)) < 0) { + ERR("%s (%d): cannot open audio device (%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + DBG("snd_handle: %p", *snd_handle); + + if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { + ERR("%s (%d): cannot allocate hardware parameter structure(%s)", dev_name, + handle->direction, snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_hw_params_any(*snd_handle, hw_params)) < 0) { + ERR("%s (%d): cannot initialize hardware parameter structure(%s)", dev_name, + handle->direction, snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_hw_params_set_rate_resample(*snd_handle, hw_params, 0)) < 0) { + ERR ("Failed to set set_resample " ); + return -1; + } + + if ((err = snd_pcm_hw_params_set_access(*snd_handle, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED )) < 0) { + ERR("%s (%d): cannot set access type(%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + + DBG("snd_pcm_hw_params_set_format format: %d", alsa_param->format); + if ((err = snd_pcm_hw_params_set_format(*snd_handle, hw_params, alsa_param->format)) < 0) { + ERR("%s (%d): cannot set sample format(%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_hw_params_set_channels(*snd_handle, hw_params, handle->channels)) < 0) { + ERR("%s (%d): cannot set channel count(%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_hw_params_set_rate(*snd_handle, hw_params, handle->rate, 0)) < 0) { + ERR("%s (%d): cannot set sample rate(%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + INF("%s (%d): current rate: %d", dev_name, handle->direction, handle->rate); + + if ((err = snd_pcm_hw_params_set_period_size_near(*snd_handle, hw_params, &period_size, &sub_dir)) < 0) { + ERR("%s (%d): set period size to %d failed", dev_name, handle->direction, (int)period_size); + return -1; + } + + if ((err = snd_pcm_hw_params_set_buffer_size_near(*snd_handle, hw_params, &buffer_size)) < 0) { + ERR("%s (%d): set buffer size to %d failed", dev_name, handle->direction, (int)buffer_size); + return -1; + } + handle->buffer_size = buffer_size; + INF("%s (%d): buffer_size = %d", dev_name, handle->direction, (int)buffer_size); + + if ((err = snd_pcm_hw_params_get_period_size(hw_params, &period_size, &sub_dir)) < 0) { + ERR("%s (%d): get period size failed", dev_name, handle->direction); + return -1; + } + handle->period_size = period_size; + INF("%s (%d): period_size %d", dev_name, handle->direction, (int)period_size); + + if ((err = snd_pcm_hw_params(*snd_handle, hw_params)) < 0) { + ERR("%s (%d): cannot set hw parameters(%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + + snd_pcm_hw_params_free(hw_params); + + if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) { + ERR("%s (%d): cannot allocate software parameters structure(%s)", dev_name, + handle->direction, snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_sw_params_current(*snd_handle, sw_params)) < 0) { + ERR("%s (%d): cannot initialize software parameters structure(%s)", dev_name, + handle->direction, snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_sw_params_set_stop_threshold(*snd_handle, sw_params, buffer_size)) < 0) { + ERR("%s (%d): cannot set stop mode(%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_sw_params_set_period_event(*snd_handle, sw_params, 1)) < 0) { + ERR("%s (%d): periodic events for capture(%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_sw_params(*snd_handle, sw_params)) < 0) + { + ERR("%s (%d): cannot set software parameters(%s)", dev_name, handle->direction, snd_strerror(err)); + return -1; + } + + snd_pcm_sw_params_free (sw_params); + + if ((err = snd_pcm_prepare (*snd_handle)) < 0) + { + ERR ("%s (%d): Failed to prepare sound device ", dev_name, handle->direction); + return -1; + } + + snd_output_stdio_attach(&s_alsa_output, g_logfile_hdl, 0); + INF("%s (%d): pcm_dump", dev_name, handle->direction); + if ((err = snd_pcm_dump(*snd_handle, s_alsa_output)) < 0) { + ERR("Failed to dump pcm"); + return -1; + } + + snd_pcm_status_t *status; + snd_pcm_status_alloca(&status); + if ((err = snd_pcm_status(*snd_handle, status)) < 0) { + ERR("Stream status error: %s", snd_strerror(err)); + return -1; + } + INF("%s (%d): pcm_status_dump", dev_name, handle->direction); + if ((err = snd_pcm_status_dump(status, s_alsa_output)) < 0) { + ERR("Failed to dump pcm"); + return -1; + } + + return 0; +} + +int alsa_tx_init(aar_alsa_handle_t *handle, struct avb_stream_params *stream_params, const char *alsa_device) +{ + int ret; + + DBG("handle: %p", handle); + + ret = alsa_init(handle, stream_params, alsa_device); + if (ret < 0) + goto err; + + handle->flags = 0; + alsa_reset(handle); + + stats_init(&handle->stats.min_alsa_lat, 7, handle, alsa_tx_stats_min_alsa); + stats_init(&handle->stats.alsa_latency, 31, handle, NULL); + stats_init(&handle->stats.alsa_avail_samples, 31, handle, NULL); + +err: + return ret; +} + +int alsa_rx_init(aar_alsa_handle_t *handle, struct avb_stream_params *stream_params, const char *alsa_device) +{ + int ret; + + DBG("handle: %p", handle); + + ret = alsa_init(handle, stream_params, alsa_device); + if (ret < 0) + goto err; + + handle->flags = 0; + alsa_reset(handle); + + stats_init(&handle->stats.alsa_avail_samples, 31, handle, NULL); + +err: + return ret; +} + +int alsa_tx_exit(aar_alsa_handle_t *handle) +{ + DBG("handle: %p", handle); + handle->flags &= ~AAR_ALSA_RUNNING; + + if (snd_pcm_close(handle->handle) < 0) { + return -1; + } + + stats_reset(&handle->stats.min_alsa_lat); + stats_reset(&handle->stats.alsa_latency); + stats_reset(&handle->stats.alsa_avail_samples); + return 0; +} + +int alsa_rx_exit(aar_alsa_handle_t *handle) +{ + DBG("handle: %p", handle); + handle->flags &= ~AAR_ALSA_RUNNING; + if (snd_pcm_close(handle->handle) < 0) { + return -1; + } + stats_reset(&handle->stats.alsa_avail_samples); + + return 0; +} + +#define ALSA_RX_LATENCY 0 // immediate startup +int alsa_rx_start(aar_alsa_handle_t *handle) +{ + snd_pcm_t *snd_handle = handle->handle; + const aar_alsa_param_t *alsa_param = alsa_get_param(handle); + unsigned int port_id = 0; //FIXME + int err; + + DBG("alsa handle: %p", handle); + DBG("snd_handle: %p", snd_handle); + DBG("Current ALSA device state: %d", snd_pcm_state(snd_handle)); + + err = clock_gettime32(port_id, &handle->start_time); + if (err < 0) { + ERR("alsa(%p) clock_gettime32() failed", handle); + return -1; //FIXME + } + snd_pcm_start(snd_handle); + handle->start_time += alsa_param->pcm_start_delay + handle->period_size * (NSECS_PER_SEC / handle->rate) + ALSA_RX_LATENCY; + handle->flags = (AAR_ALSA_RUNNING | AAR_ALSA_FIRST_READ); + + stats_reset(&handle->stats.alsa_avail_samples); + + return 0; +} + +int alsa_rx(aar_alsa_handle_t *alsa_handle, aar_avb_stream_t *avbstream) +{ + const snd_pcm_channel_area_t *areas; + struct avb_stream_handle *stream_handle = avbstream->stream_handle; + struct avb_event event; + snd_pcm_sframes_t avail, delay; + snd_pcm_uframes_t frames_remaining; + snd_pcm_uframes_t offset, frames_to_commit; + snd_pcm_t *snd_handle = alsa_handle->handle; + int bytes_to_read; + void *src_frame; + int nbytes; + int ret; + int exchanged = 0; + unsigned int gptp_time; +#if ALSA_EXTRA_DEBUG + DBG("alsa(%p)", alsa_handle); +#endif + + // Calculate gPTP time to update stats + if (clock_gettime32(avbstream->stream_params.port, &gptp_time) >= 0) { + if (avbstream->is_first_wakeup) { + // First wakeup, just store the time + avbstream->is_first_wakeup = 0; + avbstream->last_gptp_time = gptp_time; + } else { + // calculate stats + stats_update(&avbstream->stats.gptp_2cont_wakeup, gptp_time - avbstream->last_gptp_time); + avbstream->last_gptp_time = gptp_time; + } + } + + ret = snd_pcm_avail_delay(snd_handle, &avail, &delay); + if (ret < 0) { + alsa_handle->stats.counter_stats.rx_err ++; + ERR("alsa(%p) - pcm_avail_delay error %s, pcm state: %d", alsa_handle, snd_strerror(ret), (int)snd_pcm_state(snd_handle)); + + alsa_handle->flags &= ~AAR_ALSA_RUNNING; + + alsa_reset(alsa_handle); + ret = alsa_rx_start(alsa_handle); + if (ret < 0) { + ERR("alsa(%p) - rx start failed %d", alsa_handle, ret); + } + + return ret; + } + + if (avail < 0) { + alsa_handle->stats.counter_stats.rx_err ++; +#if ALSA_EXTRA_DEBUG + ERR("*alsa(%p)-%d-%s", alsa_handle, (int)avail, snd_strerror(avail)); +#endif + alsa_reset(alsa_handle); + ret = alsa_rx_start(alsa_handle); + if (ret != 0) { + ERR("alsa(%p) - rx start failed %d", alsa_handle, ret); + } + return ret; + } else if (avail == 0) { +#if ALSA_EXTRA_DEBUG + ERR("#alsa(%p)", alsa_handle); +#endif + alsa_handle->stats.counter_stats.rx_err ++; + return 0; + } +#if ALSA_EXTRA_DEBUG + DBG("+alsa(%p)-%d", alsa_handle, (int) avail); +#endif + + /* Do not process more than one period_size or it may lead to miss a wakeup */ + if (avail > alsa_handle->period_size) { + frames_remaining = alsa_handle->period_size; + } else + frames_remaining = avail; + + // Update statistic + stats_update(&alsa_handle->stats.alsa_avail_samples, avail); + + do { + frames_to_commit = frames_remaining; + ret = snd_pcm_mmap_begin(snd_handle, &areas, &offset, &frames_to_commit); + if ((ret < 0) || (frames_to_commit == 0)) { +#if ALSA_EXTRA_DEBUG + ERR("alsa(%p) - alsa_tx MMAP begin error %s", alsa_handle, snd_strerror(ret)); +#endif + alsa_handle->stats.counter_stats.rx_err ++; + return 0; + } + bytes_to_read = frames_to_commit * alsa_handle->frame_size; + src_frame = areas[0].addr + areas[0].first / 8 + offset * (areas[0].step / 8); + + /* perform audio sample processing: endianness swap, padding adjust ... */ + if (alsa_handle->alsa_process_samples) + alsa_handle->alsa_process_samples(alsa_handle, src_frame, frames_to_commit); + + ret = bytes_to_read; + do { + if (alsa_handle->flags & AAR_ALSA_FIRST_READ) { + event.index = 0; + event.event_mask = AVTP_SYNC; + event.ts = alsa_handle->start_time + avb_stream_presentation_offset(avbstream->stream_handle); + nbytes = avb_stream_send(stream_handle, src_frame + (bytes_to_read - ret), ret, &event, 1); + alsa_handle->flags &= ~AAR_ALSA_FIRST_READ; + } else + nbytes = avb_stream_send(stream_handle, src_frame + (bytes_to_read - ret), ret, NULL, 0); + if (nbytes <= 0) { +#if ALSA_EXTRA_DEBUG + ERR("?alsa(%p)-%d-%d", alsa_handle, ret, nbytes); +#endif + /* Ignore the avb stack error and keep processing the alsa interface + * The error may be caused by avb stack exit (<0) or media queue full (=0) + */ + avbstream->stats.counter_stats.tx_err ++; + break; + } + avbstream->stats.counter_stats.batch_tx ++; + + ret -= nbytes; + } while(ret > 0); +#if ALSA_EXTRA_DEBUG + DBG("stats.counter_stats.rx_err ++; + return 0; + } + frames_remaining -= frames_to_commit; + } while (frames_remaining > 0); + alsa_handle->stats.counter_stats.period_rx ++; + + return exchanged; +} + +int alsa_get_fd_from_handle(aar_alsa_handle_t *handle, int *fd) +{ + struct pollfd ufds[2]; + int count; + int err; + + DBG("alsa handle: %p", handle); + count = snd_pcm_poll_descriptors_count(handle->handle); + if (count <= 0) { + ERR("ALSA pcm poll count = %d", count); + return -1; + } + DBG("fd count = %d", count); + if ((err = snd_pcm_poll_descriptors(handle->handle, ufds, 2)) < 0) { + ERR("ALSA pcm get poll fds failed, %d", err); + return -1; + } + + /* + * The alsa API recommends polling on all descriptors and using snd_pcm_poll_descriptors_revents() + * to demangle the revents mask returned by poll(). + * This method is not compatible with the thread API used here. + * Workaround: always poll on the first descriptor only. + */ + if (count > 1) + INF("PCM device returned multiple file descriptors(%u), poll only on the first one.\n", count); + + *fd = ufds[0].fd; + DBG("events = %x", ufds[0].events); + + return 0; +} + +static const aar_alsa_param_t *alsa_get_param(aar_alsa_handle_t *handle) +{ + int i; + // DBG("alsa handle: %p", handle); + // Get device name + if (handle->direction == AAR_DATA_DIR_OUTPUT) { + i = handle->device; + return &g_alsa_playback_params[i]; + } else { + i = handle->device; + return &g_alsa_capture_params[i]; + } + + return NULL; +} + +static inline unsigned int alsa_compute_silence(aar_alsa_handle_t *handle, unsigned int max_frames, unsigned int *desired_time, unsigned int now, unsigned int total, unsigned int pcm_start) +{ + unsigned int silence_frames; + unsigned int nsecs_per_frame = NSECS_PER_SEC / handle->rate; + unsigned int max = max_frames - (total - pcm_start); + + if (avtp_after(now, *desired_time)) { + if (total == pcm_start) { // We're late on the first iteration + ERR("alsa_tx(%p) desired time (%u) likely in the past (now = %u), resetting to %d ns in the future", + handle, *desired_time, now, CFG_ALSA_PLAYBACK_LATENCY_NS); + *desired_time = now + CFG_ALSA_PLAYBACK_LATENCY_NS; + } else { // We got late while adding silence, let's stop there + ERR("alsa_tx(%p) desired time (%u) likely in the past, resetting to now(%u)", + handle, *desired_time, now); + *desired_time = now; + } + } + + silence_frames = *desired_time - now; + silence_frames = silence_frames / nsecs_per_frame; + + if (silence_frames >= total) + silence_frames -= total; + else { + ERR("alsa_tx(%p) now(%u) desired_time(%u) Added too many frames (%u instead of %u)", + handle, now, *desired_time, total, silence_frames); + silence_frames = 0; + } + + if (silence_frames > max) { + ERR("alsa_tx(%p) Amount of silence to add (%u) exceeds max space available, clamping to %u", + handle, silence_frames, max); + silence_frames = max; + } + + + return silence_frames; +} + +static int listener_timestamp_accept(unsigned int ts, unsigned int now, aar_avb_stream_t *avbstream) +{ + /* Timestamp + playback offset must be after now (otherwise packet are too late) */ + /* Timestamp must be before now + transit time + timing uncertainty (otherwise they arrived too early) */ + if (avtp_after(ts + CFG_ALSA_PLAYBACK_LATENCY_NS, now) + && avtp_before(ts, now + sr_class_max_transit_time(avbstream->stream_params.stream_class) + + sr_class_max_timing_uncertainty(avbstream->stream_params.stream_class))) + return 1; + + DBG("avb(%p) Invalid timestamp, ts - now: %d", avbstream->stream_handle , ts -now); + + return 0; +} + +#define START_COPY_SIZE 1024 +#define MAX_EVENTS 12 +#define MAX_DROPPED_PERIOD 4 + +int alsa_tx_start(aar_alsa_handle_t *handle, aar_avb_stream_t *avbstream) +{ + int err = 0; + unsigned int desired_time, now, silence_frames, total, pcm_start_overhead; +#if ALSA_EXTRA_DEBUG + unsigned int then, ptp_intvl; +#endif + const aar_alsa_param_t *alsa_params = alsa_get_param(handle); + unsigned int nsecs_per_frame = NSECS_PER_SEC / handle->rate; + unsigned int port_id = avbstream->stream_params.port; + struct avb_event event[MAX_EVENTS]; + unsigned int start_copy_size; + unsigned int event_len; + unsigned int tstamp; + char buf[START_COPY_SIZE]; + snd_pcm_uframes_t offset, frames_read, frames_dropped; + snd_pcm_uframes_t alsa_space, frames_written; + snd_pcm_t *snd_handle = handle->handle; + const snd_pcm_channel_area_t *areas; + unsigned int bytes_to_read; + void *src_frame; + int idx; + int end; + uint64_t ts_offset; + + // DBG("alsa handle: %p, avb stream: %p", handle, avbstream); + if (handle->flags & AAR_ALSA_RUNNING) + return 0; + + alsa_reset(handle); + + //FIXME How many samples to fetch? Just enough to be sure of getting a time stamp? As much as possible? + //FIXME Need to check more than one event possibly + + handle->stats.counter_stats.tx_start++; + avbstream->last_exchanged_frames = 0; + avbstream->last_event_frame_offset = 0; + + frames_dropped = 0; + frames_read = 0; + frames_written = 0; + /* Round down the start copy size to multiple of frame size. */ + start_copy_size = (START_COPY_SIZE / handle->frame_size) * handle->frame_size; + + do { + event_len = MAX_EVENTS; + + err = avb_stream_receive(avbstream->stream_handle, buf, min(start_copy_size, avbstream->cur_batch_size), event, &event_len); + if (err < 0) { + ERR("alsa(%p) - tx start failed on avb_stream_receive err(%d) event_len: %d", handle, err, event_len); + err = -1; + goto exit; + } + else if (!err) { + ERR("alsa(%p) - tx start failed on avb_stream_receive: no data", handle); + handle->stats.counter_stats.tx_start_no_data++; + err = -1; + goto exit; + } + + frames_dropped += frames_read; + frames_read = err / handle->frame_size; + + handle->stats.counter_stats.tx_start_drop += frames_dropped; + if (frames_dropped >= (MAX_DROPPED_PERIOD * handle->period_size)) { + err = -1; + goto exit; + } + + if (!get_clk_domain_validity(avbstream->stream_params.clock_domain)) { + wait_clk_domain_validity++; + err = -1; + goto exit; + } + + wait_clk_domain_validity = 0; + + err = clock_gettime32(port_id, &now); + if (err < 0) { + ERR("alsa(%p) clock_gettime failed for now", handle); + goto exit; + } + + if (event_len == 0) { + ERR("alsa(%p) - tx start failed, no events received", handle); + err = -1; + goto exit; + } + + for (idx = 0; idx < event_len; idx++) { + if (event[idx].event_mask & (AVTP_TIMESTAMP_INVALID | AVTP_TIMESTAMP_UNCERTAIN)) + continue; + + ts_offset = alsa_bytes_to_ns(event[idx].index, &avbstream->stream_params); + event[idx].ts -= ts_offset; + + tstamp = event[idx].ts; + if (listener_timestamp_accept(tstamp, now, avbstream)) + goto start; + } + } + while (1); + +start: + pcm_start_overhead = alsa_params->pcm_start_delay / nsecs_per_frame; + desired_time = tstamp + CFG_ALSA_PLAYBACK_LATENCY_NS; + + total = pcm_start_overhead; + + alsa_space = handle->buffer_size; + err = snd_pcm_mmap_begin(snd_handle, &areas, &offset, &alsa_space); + if (err < 0) { + ERR("alsa(%p) MMAP begin error %s", handle, snd_strerror(err)); + goto exit; + } + + if (alsa_space != handle->buffer_size) { + ERR("alsa(%p) invalid alsa mmap_begin frames: %u", handle, (unsigned int)alsa_space); + err = -1; + goto exit; + } + + /* Since the process of adding silence frames takes time by itself, we account for that delay by + * adding silence in several steps with a progressively lower number of frames at each step, and + * stop once the number of frames to be added is low enough. + * With 100 frames to add (about 2ms@48kHz), CFG_ALSA_MIN_SILENCE_FRAMES==8, and CFG_ALSA_PCM_START_DELAY==35000, + * this results in 3 steps: 75, 18, 6. + */ + end = 0; + while (!end) { + err = clock_gettime32(port_id, &now); + if (err < 0) { + ERR("alsa(%p) clock_gettime32() failed for now", handle); + goto exit; + } + + silence_frames = alsa_compute_silence(handle, handle->buffer_size, &desired_time, now, total, pcm_start_overhead); + + if (silence_frames > CFG_ALSA_MIN_SILENCE_FRAMES) + silence_frames -= silence_frames >> 2; + else + end = 1; + + err = snd_pcm_areas_silence(areas, offset + frames_written, handle->channels, silence_frames, alsa_params->format); + if (err < 0) { + ERR("alsa_tx(%p) snd_pcm_areas_silence error", handle); + goto exit; + } + + total += silence_frames; + frames_written += silence_frames; + } + + total -= pcm_start_overhead; + + /* Start processing the first sample with a valid timestamp (shifted because of the index offset) to improve accuracy. */ + bytes_to_read = frames_read * handle->frame_size; + src_frame = areas[0].addr + areas[0].first / 8 + (offset + frames_written) * (areas[0].step / 8); + memcpy(src_frame, buf, bytes_to_read); + frames_written += frames_read; + + err = snd_pcm_mmap_commit(snd_handle, offset, frames_written); + if (err != frames_written) { + ERR("alsa(%p) MMAP commit error %s", handle, snd_strerror(err)); + err = -1; + goto exit; + } + +#if ALSA_EXTRA_DEBUG + err = clock_gettime32(port_id, &now); + if (err < 0) { + ERR("alsa(%p) clock_gettime32() failed for now", handle); + goto exit; + } +#endif + + err = snd_pcm_start(handle->handle); + if (err < 0) { + ERR("alsa(%p) Couldn't start stream: error = %s state = %d", handle, snd_strerror(err), snd_pcm_state(handle->handle)); + goto exit; + } + +#if ALSA_EXTRA_DEBUG + err = clock_gettime32(port_id, &then); + if (err < 0) { + ERR("alsa(%p) clock_gettime32() failed for then", handle); + goto exit; + } + + /* All computations are done on unsigned 32-bit values, since tstamp and now are not + * supposed to be more than 2^32-1 ns away. + */ + ptp_intvl = then - now; + now += (ptp_intvl / 2); // (now+then)/2 would not return the right value in some wrapping cases + + + INF("alsa(%p) now = %u avtp_ts = %u", + handle, now, tstamp); + INF("alsa(%p) gettime accuracy intvl = %u ns avtp_ts-now = %u us ", + handle, ptp_intvl, (tstamp - now)/1000); + INF("alsa(%p) initial silence = %u us error = %d us nsecs_per_frame = %d", + handle, (total*nsecs_per_frame)/1000, (total*nsecs_per_frame)/1000 - (desired_time - now)/1000, nsecs_per_frame); +#endif + stats_reset(&handle->stats.min_alsa_lat); + stats_reset(&handle->stats.alsa_latency); + stats_reset(&handle->stats.alsa_avail_samples); + + handle->flags |= AAR_ALSA_RUNNING; + + return frames_read; +exit: + return err; +} + + +#define EVENT_LEN 16 +int alsa_tx(aar_alsa_handle_t *alsa_handle, aar_avb_stream_t *avbstream) +{ + const snd_pcm_channel_area_t *areas; + struct avb_stream_handle *stream_handle = avbstream->stream_handle; + const aar_alsa_param_t *alsa_param = alsa_get_param(alsa_handle); + snd_pcm_sframes_t avail, delay, start_frames; + snd_pcm_uframes_t frames_remaining; + snd_pcm_uframes_t offset, frames_to_commit, frames_committed; + snd_pcm_t *snd_handle = alsa_handle->handle; + int bytes_to_read; + void *src_frame; + int nbytes, i; + int ret = 0; + int exchanged = 0; + struct avb_event event[EVENT_LEN] = {0}; + unsigned int event_len = EVENT_LEN; + unsigned int gptp_time = 0; + char is_first_event = 1; + uint64_t ts_offset; + + // Calculate gPTP time to update stats + if (clock_gettime32(avbstream->stream_params.port, &gptp_time) >= 0) { + if (avbstream->is_first_wakeup) { + // First wakeup, just store the time + avbstream->last_gptp_time = gptp_time; + } else { + stats_update(&avbstream->stats.gptp_2cont_wakeup, gptp_time - avbstream->last_gptp_time); + avbstream->last_gptp_time = gptp_time; + } + } + + // DBG("alsa handle: %p, avb stream: %p", alsa_handle, avbstream); + if (!(alsa_handle->flags & AAR_ALSA_RUNNING)) { +#if ALSA_EXTRA_DEBUG + DBG("alsa(%p)", alsa_handle); +#endif + ret = alsa_tx_start(alsa_handle, avbstream); + if (ret < 0) { + alsa_handle->stats.counter_stats.tx_start_err++; + return 0; + } + } + + start_frames = ret; + ret = snd_pcm_avail_delay(snd_handle, &avail, &delay); + if (ret < 0 || (avail < (avbstream->cur_batch_size / alsa_handle->frame_size)) || (delay < alsa_handle->period_size / 2)) { + alsa_handle->stats.counter_stats.tx_err++; + ERR("alsa(%p) - alsa_tx pcm_avail_delay error %s, pcm state: %d", alsa_handle, + snd_strerror(ret), snd_pcm_state(snd_handle)); + alsa_handle->flags &= ~AAR_ALSA_RUNNING; + + ret = alsa_tx_start(alsa_handle, avbstream); + if (ret < 0) { + alsa_handle->stats.counter_stats.tx_start_err++; + ERR("alsa(%p) - tx start failed %d", alsa_handle, ret); + } + return ret; + } + +#if ALSA_EXTRA_DEBUG + DBG("+alsa(%p)-%d", alsa_handle, (int)avail); +#endif + // Update statistic + stats_update(&alsa_handle->stats.alsa_avail_samples, avail); + + // Calculate remaining frame + frames_remaining = min(avail, (avbstream->cur_batch_size / alsa_handle->frame_size) - start_frames); + + stats_update(&alsa_handle->stats.min_alsa_lat, + delay + (alsa_param->fifo_threshold * 4) / alsa_handle->frame_size); + + while (frames_remaining > 0) { + frames_to_commit = frames_remaining; + ret = snd_pcm_mmap_begin(snd_handle, &areas, &offset, &frames_to_commit); + if ((ret < 0) || (frames_to_commit == 0)) { +#if ALSA_EXTRA_DEBUG + ERR("alsa(%p) - alsa_tx MMAP begin error %s", alsa_handle, snd_strerror(ret)); +#endif + alsa_handle->stats.counter_stats.tx_err++; + + return 0; + } +#if ALSA_EXTRA_DEBUG + DBG("@alsa(%p)-%d", alsa_handle, (int)frames_to_commit); +#endif + bytes_to_read = frames_to_commit * alsa_handle->frame_size; + src_frame = areas[0].addr + areas[0].first / 8 + offset * (areas[0].step / 8); // first and step are in bits + event_len = EVENT_LEN; + nbytes = avb_stream_receive(stream_handle, src_frame, bytes_to_read, event, &event_len); + if (nbytes < alsa_handle->frame_size) { +#if ALSA_EXTRA_DEBUG + ERR(":alsa(%p) %d %d\n", alsa_handle, bytes_to_read, nbytes); +#endif + avbstream->stats.counter_stats.rx_err ++; + + ret = snd_pcm_mmap_commit(snd_handle, offset, 0); + + return 0; + } + + if (event_len) { + int idx; + + // Check if AVTP packet lost occurs on starting of batch + for (i = 0; i < event_len; ++i) { + if (event[i].event_mask & AVTP_PACKET_LOST) { + ERR(":alsa(%p)-AVTP_PACKET_LOST %u bytes, event no %d\n", alsa_handle, event[i].event_data, i); + + avbstream->stats.counter_stats.rx_err++; + + ret = snd_pcm_mmap_commit(snd_handle, offset, 0); + + // Restart ALSA tx + alsa_handle->flags &= ~AAR_ALSA_RUNNING; + + ret = alsa_tx_start(alsa_handle, avbstream); + if (ret < 0) { + alsa_handle->stats.counter_stats.tx_start_err++; + } + + return ret; + } + } + + i = 0; + while ((i < event_len) && (event[i].event_mask & (AVTP_TIMESTAMP_INVALID | AVTP_TIMESTAMP_UNCERTAIN))) i++; + + for (idx = i; idx < event_len; idx++) { + + if (event[idx].event_mask & (AVTP_TIMESTAMP_INVALID | AVTP_TIMESTAMP_UNCERTAIN)) + continue; + + ts_offset = alsa_bytes_to_ns(event[idx].index, &avbstream->stream_params); + + if (!listener_timestamp_accept(event[idx].ts - ts_offset, gptp_time, avbstream)) + { + ret = snd_pcm_mmap_commit(snd_handle, offset, 0); + alsa_handle->stats.counter_stats.tx_err++; + + // Restart ALSA tx + alsa_handle->flags &= ~AAR_ALSA_RUNNING; + + ret = alsa_tx_start(alsa_handle, avbstream); + if (ret < 0) { + alsa_handle->stats.counter_stats.tx_start_err++; + } + + return ret; + } + } + + if (i < event_len) { + // Calculate event time statistic + if (avbstream->is_first_wakeup) { + avbstream->last_event_ts = event[i].ts; + avbstream->last_event_frame_offset = avbstream->last_exchanged_frames + event[i].index / alsa_handle->frame_size; + } else if (is_first_event) { + unsigned int dt_elapsed = event[i].ts - avbstream->last_event_ts; + unsigned int event_frame_offset = avbstream->last_exchanged_frames + event[i].index / alsa_handle->frame_size; + unsigned int frames_elapsed = event_frame_offset - avbstream->last_event_frame_offset; + + avbstream->last_event_ts = event[i].ts; + avbstream->last_event_frame_offset = event_frame_offset; + + if ( avbstream->last_exchanged_frames && ( abs(dt_elapsed - frames_elapsed * alsa_handle->frame_duration) > alsa_handle->frame_duration)) { + ret = snd_pcm_mmap_commit(snd_handle, offset, 0); + + alsa_handle->stats.counter_stats.rx_err++; + + // Restart ALSA tx + alsa_handle->flags &= ~AAR_ALSA_RUNNING; + + ret = alsa_tx_start(alsa_handle, avbstream); + if (ret < 0) { + alsa_handle->stats.counter_stats.tx_start_err++; + } + + return ret; + } + + is_first_event = 0; + stats_update(&avbstream->stats.event_2cont_wakeup, dt_elapsed); + stats_update(&avbstream->stats.event_gptp, event[i].ts - gptp_time); + } + } else // Invalid time stamp + alsa_handle->stats.counter_stats.tx_err++; + } + + avbstream->stats.counter_stats.batch_rx ++; + frames_committed = nbytes / alsa_handle->frame_size; + +#if ALSA_EXTRA_DEBUG + DBG(">alsa(%p)-%d\n", alsa_handle, (int)frames_committed); +#endif + exchanged += nbytes; + avbstream->last_exchanged_frames += nbytes / alsa_handle->frame_size; + + /* perform audio sample processing: endianness swap, padding adjust ... */ + if (alsa_handle->alsa_process_samples) + alsa_handle->alsa_process_samples(alsa_handle, src_frame, frames_to_commit); + + ret = snd_pcm_mmap_commit(snd_handle, offset, frames_committed); + if (ret != frames_committed) { + ERR("alsa_tx(%p): alsa_tx MMAP commit error %s", alsa_handle, snd_strerror(ret)); + return 0; + } + + if ((nbytes < bytes_to_read) && (event_len < EVENT_LEN)) { + break; + } + frames_remaining -= frames_committed; + } + + alsa_handle->stats.counter_stats.period_tx += exchanged / alsa_handle->frame_size; + + if (avbstream->is_first_wakeup) { + // First wakeup, just store the time + avbstream->is_first_wakeup = 0; + } + + return exchanged; +} diff --git a/apps/linux/common/alsa2.h b/apps/linux/common/alsa2.h new file mode 100644 index 0000000..6422b61 --- /dev/null +++ b/apps/linux/common/alsa2.h @@ -0,0 +1,173 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2019-2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __ALSA2_H__ +#define __ALSA2_H__ + +#include +#include "alsa_config.h" +#include "avb_stream_config.h" +#include "stats.h" + +typedef enum _DATA_STREAM_DIRECTION { + AAR_DATA_DIR_INPUT = 1, /**< Input direction */ + AAR_DATA_DIR_OUTPUT /**< Output direction */ +} aar_device_direction_t; + + +/** + * @addtogroup alsa + * @{ + */ +typedef enum { + AAR_ALSA_RUNNING = 0x01, + AAR_ALSA_FIRST_READ = 0x02, +} aar_alsa_flags_t; + +/** ALSA counter statistic */ +typedef struct { + unsigned int tx_err; /**< ALSA write error counter */ + unsigned int rx_err; /**< ALSA read error counter */ + + unsigned int period_rx; /**< ALSA period received counter */ + unsigned int period_tx; /**< ALSA period transmitted counter */ + + unsigned int tx_start; + unsigned int tx_start_drop; + unsigned int tx_start_err; + unsigned int tx_start_no_data; +} aar_alsa_counter_stats_t; + +typedef struct { + struct stats alsa_latency; + struct stats min_alsa_lat; + + struct stats alsa_avail_samples; /**< ALSA available samples */ + aar_alsa_counter_stats_t counter_stats; /**< ALSA simple counter statistic */ +} aar_alsa_stats_t; + +/** ALSA wrapper handle structure */ +typedef struct _ALSA_HANDLE_STRUCTURE { + snd_pcm_t *handle; /**< ALSA handle pointer */ + unsigned int device; /**< ALSA input/output device */ + aar_device_direction_t direction; /**< ALSA device direction */ + int flags; /**< ALSA flag, mixing from aar_alsa_flags_t */ + snd_pcm_uframes_t buffer_size; /**< ALSA buffer size of this handle */ + snd_pcm_uframes_t period_size; /**< ALSA period size of this handle */ + unsigned int frame_size; /**< Size in bytes of each ALSA frame */ + unsigned int frame_duration; /**< Duration in ns of each ALSA frame */ + unsigned int rate; + unsigned int channels; + unsigned int start_time; /**< gPTP time of snd_pcm_start (used for talkers only) */ + aar_alsa_stats_t stats; /**< ALSA statistics */ + void (*alsa_process_samples)(struct _ALSA_HANDLE_STRUCTURE *handle, void *src_frame, snd_pcm_uframes_t to_commit); /**< Custom function per stream to do sample processing + once for all to enhance performance: + endianness swap, label adding, padding adjust ...*/ +} aar_alsa_handle_t; + +extern const aar_alsa_param_t g_alsa_playback_params[MAX_ALSA_PLAYBACK]; +extern const aar_alsa_param_t g_alsa_capture_params[MAX_ALSA_CAPTURE]; + +/** Open and initialize an ALSA PCM playback handle + * + * @details This function will open a ALSA audio device, initialize and configure this device. + * Information of the handle must be set before call this function. + * + * @param handle Pointer of ALSA handle + * @param stream_params AVB stream parameters + * @param alsa_device ALSA device + * + * @return 0 if success or negative error code + */ +int alsa_tx_init(aar_alsa_handle_t *handle, struct avb_stream_params *stream_params, const char *alsa_device); + +/** Open and initialize an ALSA PCM capture handle + * + * @param handle ALSA handle pointer + * @param stream_params AVB stream parameters + * @param alsa_device ALSA device + * + * @return 0 if success or negative error code + */ +int alsa_rx_init(aar_alsa_handle_t *handle, struct avb_stream_params *stream_params, const char *alsa_device); + +/** Close and free an ALSA PCM playback handle + * + * @param handle The handle + * + * @return 0 if success or negative error code + */ +int alsa_tx_exit(aar_alsa_handle_t *handle); + +/** Close and free an ALSA PCM capture handle + * + * @param handle The handle + * + * @return 0 if success or negative error code + */ +int alsa_rx_exit(aar_alsa_handle_t *handle); + +/** + * @brief Get file description from ALSA handle + * + * @param[in] handle Pointer of ALSA handle + * @param[out] fd Pointer of file descriptor var + * + * @return 0 if success or negative error code + */ +int alsa_get_fd_from_handle(aar_alsa_handle_t *handle, int *fd); + +/** Write data into ALSA playback handle + * + * @param[in] handle The ALSA handle + * @param[in] avbstream The AVB stream + * + * @return Number of written data (in bytes) or negative error code + */ +int alsa_tx(aar_alsa_handle_t *handle, aar_avb_stream_t *avbstream); + +/** Read data from ALSA capture handle + * + * @param[in] handle The ALSA handle + * @param[in] avbstream The AVB stream + * + * @return Number of received data (in bytes) or negative error code + */ +int alsa_rx(aar_alsa_handle_t *handle, aar_avb_stream_t *avbstream); + +/** Handles synchronized start of playback (based on AVTP presentation time) + * + * @param[in] handle The ALSA handle + * @param[in] avbstream The AVB stream + * + * @return 0 if success or negative error code + * + * This function will add silence frames at the beginning of the ring buffer before starting playback so + * that the next frame to be posted to ALSA will be played out at the requested tstamp + a fixed value. + * Since tstamp is a u32, it is assumed that tstamp will not be more than 2^32-1 ns in the future, or about 4.2 seconds. + * + * The delay to be added is therefore given by: + * delay = tstamp + PLAYBACK_LATENCY_NS - current_time - current_alsa_delay + * where: + * . current_time is the current gPTP time in ns modulo 2^32 + * . current_alsa_delay is the current ALSA playout delay (based on the number of frames already queued) + * . PLAYBACK_LATENCY_NS is the fixed value to be added (in ns), to account for the per frame media stack processing + */ +int alsa_tx_start(aar_alsa_handle_t *handle, aar_avb_stream_t *avbstream); + +/** Handles synchronized start of capture and is able to assign a gptp time to audio samples (used + * with AVTP_SYNC event) + * + * @param handle The ALSA handle + * + * @return 0 if success or negative error code + */ +int alsa_rx_start(aar_alsa_handle_t *handle); + +/** @} */ + +#endif /* __ALSA2_H__ */ diff --git a/apps/linux/common/alsa_config.h b/apps/linux/common/alsa_config.h new file mode 100644 index 0000000..2b93943 --- /dev/null +++ b/apps/linux/common/alsa_config.h @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __ALSA_CONFIG_H__ +#define __ALSA_CONFIG_H__ + +/** + * @addtogroup aar + * @{ + */ + +enum { + ALSA_CAPTURE0 = 0, + ALSA_CAPTURE1, + ALSA_CAPTURE2, + ALSA_CAPTURE3, + ALSA_CAPTURE4, + ALSA_CAPTURE5, + MAX_ALSA_CAPTURE +}; + +enum { + ALSA_PLAYBACK0 = 0, + ALSA_PLAYBACK1, + ALSA_PLAYBACK2, + ALSA_PLAYBACK3, + ALSA_PLAYBACK4, + ALSA_PLAYBACK5, + MAX_ALSA_PLAYBACK +}; + +/** ALSA device parameter structure */ +typedef struct _ALSA_DEVICE_PARAMETER { + unsigned int period_time_ns; /**< ALSA device period */ + int format; /**< Audio data format */ + unsigned int fifo_threshold; /**< FIFO threshold below which the ESAI block triggers a transfer request, in bytes. + Only used to correct the reported ALSA latency statistics. */ + unsigned int pcm_start_delay; /**< Estimate of Alsa snd_pcm_start overhead, in nanoseconds. This is used to correct + the amount of silence frames that need to be added before calling snd_pcm_start, + and improve the accuracy of the playback time of audio samples. */ +} aar_alsa_param_t; + +extern const char alsa_playback_device_names[MAX_ALSA_PLAYBACK][15]; +extern const char alsa_capture_device_names[MAX_ALSA_CAPTURE][15]; + +#endif /* __ALSA_CONFIG_H__ */ diff --git a/apps/linux/common/alsa_stream.c b/apps/linux/common/alsa_stream.c new file mode 100644 index 0000000..82e7b60 --- /dev/null +++ b/apps/linux/common/alsa_stream.c @@ -0,0 +1,225 @@ +/* + * Copyright 2017-2018, 2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "log.h" +#include "alsa_stream.h" +#include "alsa2.h" +#include "avb_stream.h" +#include "stats.h" +#include "stream_stats.h" +#include "thread.h" + +void alsa_stats_dump(struct alsa_stream_stats *stats) +{ + aar_alsa_counter_stats_t *alsa_counter_stats = &stats->alsa_stats.counter_stats; + + stream_stats_dump(&stats->gen_stats); + + stats_compute(&stats->alsa_stats.alsa_avail_samples); + + INF_LOG(" alsa(%p) dev %d, dir %d, available samples %4d/%4d/%4d (frames)", + (void *)stats->alsa_handle_ptr, stats->alsa_device, + stats->alsa_direction, + stats->alsa_stats.alsa_avail_samples.min, + stats->alsa_stats.alsa_avail_samples.mean, + stats->alsa_stats.alsa_avail_samples.max); + + if (stats->gen_stats.is_listener) { + stats_compute(&stats->alsa_stats.alsa_latency); + INF_LOG(" alsa(%p) latency %4d/%4d/%4d (us)", + (void *)stats->alsa_handle_ptr, + stats->alsa_stats.alsa_latency.min, + stats->alsa_stats.alsa_latency.mean, + stats->alsa_stats.alsa_latency.max); + } + + // Also print ALSA counter stats + INF_LOG(" alsa(%p) tx_err: %d, rx_err: %d, period_tx: %d, period_rx: %d (frames)", + (void *)stats->alsa_handle_ptr, alsa_counter_stats->tx_err, + alsa_counter_stats->rx_err, alsa_counter_stats->period_tx, + alsa_counter_stats->period_rx); + + if (stats->gen_stats.is_listener) { + INF_LOG(" alsa(%p) tx_start: %d, tx_start_err: %d, tx_start_drop: %d, tx_start_no_data: %d", + (void *)stats->alsa_handle_ptr, alsa_counter_stats->tx_start, alsa_counter_stats->tx_start_err, + alsa_counter_stats->tx_start_drop, alsa_counter_stats->tx_start_no_data); + } +} + + +static void alsa_stats_store(struct alsa_stream *stream) +{ + struct alsa_stream_stats *stats = &stream->stats; + aar_alsa_stats_t *alsa_stats = &stream->alsa_handle.stats; + aar_avb_stats_t *avb_stats = &stream->avb_stream->stats; + + // Ignore if statistic handle is updated but not printed + if (stream_stats_is_updated(&stats->gen_stats)) { + ERR("ALSA stream(%p) Store stats failed", stream); + } else { + memcpy(&stats->alsa_stats, alsa_stats, sizeof(aar_alsa_stats_t)); + stream_stats_store(&stats->gen_stats, avb_stats); + + // Reset statistics + stats_reset(&alsa_stats->alsa_avail_samples); + stats_reset(&alsa_stats->alsa_latency); + } +} + +int talker_alsa_handler(void *data, unsigned int events) +{ + struct alsa_stream *talker = (struct alsa_stream *)data; + aar_alsa_handle_t *alsa_handle = &talker->alsa_handle; + aar_avb_stream_t *avb_stream = talker->avb_stream; + pthread_mutex_t *lock = &talker->thread_slot->slot_lock; + int result = -1; + + // Lock the slot + pthread_mutex_lock(lock); + + if (stream_stats_is_time(&talker->stats.gen_stats)) + alsa_stats_store(talker); + + result = alsa_rx(alsa_handle, avb_stream); + + pthread_mutex_unlock(lock); + + return result; +} + +int talker_alsa_connect(struct alsa_stream *talker, struct avb_stream_params *params) +{ + int trigger_fd; + + if (talker->created) + goto err_alsa; + + /* Use a one to one mapping between stream index and input device */ + talker->alsa_handle.device = talker->index; + talker->alsa_handle.direction = AAR_DATA_DIR_INPUT; + + if (alsa_rx_init(&talker->alsa_handle, params, talker->alsa_device) < 0) + goto err_alsa; + + params->clock_domain = AVB_CLOCK_DOMAIN_0; + + if (avbstream_talker_add(talker->index, params, &talker->avb_stream) < 0) + goto err_stream; + +#if 0 + trigger_fd = avb_stream_fd(talker->avb_stream->stream_handle); + if (thread_slot_add(THR_CAP_STREAM_TALKER | THR_CAP_ALSA | THR_CAP_STREAM_AUDIO, trigger_fd, EPOLLOUT, talker, talker_alsa_handler, &talker->thread_slot) < 0) +#else + if (alsa_get_fd_from_handle(&talker->alsa_handle, &trigger_fd) < 0) + goto err_fd; + + if (thread_slot_add(THR_CAP_STREAM_TALKER | THR_CAP_ALSA | THR_CAP_STREAM_AUDIO, trigger_fd, EPOLLIN, talker, talker_alsa_handler, NULL, 0, &talker->thread_slot) < 0) +#endif + goto err_thread; + + alsa_rx_start(&talker->alsa_handle); + + talker->created = 1; + + return 0; + +err_thread: +err_fd: + avbstream_talker_remove(talker->index); + +err_stream: + alsa_rx_exit(&talker->alsa_handle); + +err_alsa: + return -1; +} + +void talker_alsa_disconnect(struct alsa_stream *talker) +{ + if (talker->created) { + thread_slot_free(talker->thread_slot); + + alsa_rx_exit(&talker->alsa_handle); + + avbstream_talker_remove(talker->index); + + talker->created = 0; + } +} + +int listener_alsa_handler(void *data, unsigned int events) +{ + struct alsa_stream *listener = (struct alsa_stream *)data; + aar_alsa_handle_t *alsa_handle = &listener->alsa_handle; + aar_avb_stream_t *avb_stream = listener->avb_stream; + pthread_mutex_t *lock = &listener->thread_slot->slot_lock; + int result = -1; + + // Lock the slot + pthread_mutex_lock(lock); + + if (stream_stats_is_time(&listener->stats.gen_stats)) + alsa_stats_store(listener); + + result = alsa_tx(alsa_handle, avb_stream); + + pthread_mutex_unlock(lock); + + return result; +} + + +int listener_alsa_connect(struct alsa_stream *listener, struct avb_stream_params *params) +{ + int trigger_fd; + + if (listener->created) + goto err_alsa; + + /* Use a one to one mapping between stream index and output device */ + listener->alsa_handle.device = listener->index; + listener->alsa_handle.direction = AAR_DATA_DIR_OUTPUT; + + if (alsa_tx_init(&listener->alsa_handle, params, listener->alsa_device) < 0) + goto err_alsa; + + params->clock_domain = AVB_CLOCK_DOMAIN_0; + + if (avbstream_listener_add(listener->index, params, &listener->avb_stream) < 0) + goto err_stream; + + trigger_fd = avb_stream_fd(listener->avb_stream->stream_handle); + if (thread_slot_add(THR_CAP_STREAM_LISTENER | THR_CAP_ALSA | THR_CAP_STREAM_AUDIO, trigger_fd, EPOLLIN, listener, listener_alsa_handler, NULL, 0, &listener->thread_slot) < 0) + goto err_thread; + + listener->created = 1; + return 0; + +err_thread: + avbstream_listener_remove(listener->index); + +err_stream: + alsa_tx_exit(&listener->alsa_handle); + +err_alsa: + return -1; +} + +void listener_alsa_disconnect(struct alsa_stream *listener) +{ + if (listener->created) { + thread_slot_free(listener->thread_slot); + + alsa_tx_exit(&listener->alsa_handle); + + avbstream_listener_remove(listener->index); + + listener->created = 0; + } +} diff --git a/apps/linux/common/alsa_stream.h b/apps/linux/common/alsa_stream.h new file mode 100644 index 0000000..c5612cf --- /dev/null +++ b/apps/linux/common/alsa_stream.h @@ -0,0 +1,48 @@ +/* + * Copyright 2017, 2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __ALSA_STREAM_H__ +#define __ALSA_STREAM_H__ + +#include +#include "thread.h" +#include "alsa2.h" +#include "avb_stream_config.h" +#include "stats.h" +#include "stream_stats.h" + +#define APP_FLAG_FIRST_HANDLE (1 << 0) + +struct alsa_stream_stats { + struct stream_stats gen_stats; + unsigned long alsa_handle_ptr; /**< ALSA handle pointer value, just for printout */ + int alsa_device; /**< ALSA device value, just for printout */ + int alsa_direction; /**< ALSA device direction, just for printout */ + + aar_alsa_stats_t alsa_stats; /**< ALSA statistic structure */ +}; + +struct alsa_stream { + unsigned int created; + unsigned int index; + thr_thread_slot_t *thread_slot; + aar_alsa_handle_t alsa_handle; + aar_avb_stream_t *avb_stream; + char *alsa_device; + + struct alsa_stream_stats stats; +}; + +void alsa_stats_dump(struct alsa_stream_stats *stats); +int talker_alsa_handler(void *data, unsigned int events); +int talker_alsa_connect(struct alsa_stream *talker, struct avb_stream_params *params); +void talker_alsa_disconnect(struct alsa_stream *talker); +int listener_alsa_handler(void *data, unsigned int events); +int listener_alsa_connect(struct alsa_stream *listener, struct avb_stream_params *params); +void listener_alsa_disconnect(struct alsa_stream *listener); + + +#endif /* __ALSA_STREAM_H__ */ diff --git a/apps/linux/common/audio_mappings.c b/apps/linux/common/audio_mappings.c new file mode 100644 index 0000000..dcaf510 --- /dev/null +++ b/apps/linux/common/audio_mappings.c @@ -0,0 +1,330 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + #include + #include + #include + + #include + +#include "audio_mappings.h" + +#define MAX_STREAM_PORT_INPUT 4 +#define MAX_STREAM_PORT_OUTPUT 4 +#define MAX_AUDIO_MAPS 1 /* Use a single audio map for now to store all audio mappings for a single STREAM_PORT_INPUT/OUTPUT */ +#define MAX_AUDIO_MAP_SIZE 16 /* Maximum number of audio mappings in a single audio map */ +#define AUDIO_MAP_IDX 0 + +/* Make sure that maximum number of audio mappings does not exceed the IPC (and PDU) message size */ +#if ((MAX_AUDIO_MAP_SIZE * 8 + 32) > AVB_AECP_MAX_MSG_SIZE) +#error Maximum audio mapping count exceeds size of AECP IPC and PDU +#endif + +struct audio_mapping_entry { + struct aecp_aem_get_audio_map_mappings_format mapping; + avb_u8 valid:1; + avb_u8 selected:1; + avb_u8 reserved:6; +}; + +struct mapping_entry { + int registered_index; /* index in the registered array */ + int mapping_index; /* index in the received mappings array */ +}; + +static struct audio_mapping_entry stream_port_input_audio_mappings[MAX_STREAM_PORT_INPUT][MAX_AUDIO_MAPS][MAX_AUDIO_MAP_SIZE]; +static struct audio_mapping_entry stream_port_output_audio_mappings[MAX_STREAM_PORT_OUTPUT][MAX_AUDIO_MAPS][MAX_AUDIO_MAP_SIZE]; + +static int get_free_audio_mapping_index(struct audio_mapping_entry *stream_port_audio_mappings) +{ + int index = -1; + unsigned int i; + + for (i = 0; i < MAX_AUDIO_MAP_SIZE; i++) { + if (!stream_port_audio_mappings[i].valid && !stream_port_audio_mappings[i].selected) { + stream_port_audio_mappings[i].selected = true; + index = i; + goto exit; + } + } + +exit: + return index; +} + +int audio_mappings_remove(avb_u16 desc_index, avb_u16 desc_type, avb_u16 mappings_count, void *audio_mappings) +{ + struct audio_mapping_entry (*stream_port_audio_mappings)[MAX_AUDIO_MAPS][MAX_AUDIO_MAP_SIZE]; + struct mapping_entry remove_entries[MAX_AUDIO_MAP_SIZE] = {{-1 , -1}}; + unsigned int remove_entries_count = 0; + int rc = AECP_AEM_SUCCESS; + bool found = false; + unsigned int i, j; + + switch(desc_type) { + case AEM_DESC_TYPE_STREAM_PORT_INPUT: + stream_port_audio_mappings = stream_port_input_audio_mappings; + + if (desc_index >= MAX_STREAM_PORT_INPUT) { + printf("%s: Error: Unsupported AEM_DESC_TYPE_STREAM_PORT_INPUT index(%u). Max supported(%u)\n", __func__, desc_index, MAX_STREAM_PORT_INPUT - 1); + rc = AECP_AEM_NOT_SUPPORTED; + goto exit; + } + + break; + + case AEM_DESC_TYPE_STREAM_PORT_OUTPUT: + stream_port_audio_mappings = stream_port_output_audio_mappings; + + if (desc_index >= MAX_STREAM_PORT_OUTPUT) { + printf("%s: Error: Unsupported AEM_DESC_TYPE_STREAM_PORT_OUTPUT index(%u). Max supported(%u)\n", __func__, desc_index, MAX_STREAM_PORT_OUTPUT - 1); + rc = AECP_AEM_NOT_SUPPORTED; + goto exit; + } + + break; + + default: + printf("%s: Error: Unknown descriptor type(%u)\n", __func__, desc_type); + rc = AECP_AEM_BAD_ARGUMENTS; + goto exit; + } + + for (i = 0; i < mappings_count; i++) { + found = false; + + for (j = 0; j < MAX_AUDIO_MAP_SIZE; j++) { + /* Only current valid audio mappings can possibly be removed */ + if (!stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].valid) + continue; + + if (!memcmp(&((struct aecp_aem_get_audio_map_mappings_format *)audio_mappings)[i], &stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping, sizeof(struct aecp_aem_get_audio_map_mappings_format))) { + remove_entries[remove_entries_count++].registered_index = j; + found = true; + break; + } + } + + if (!found) { + printf("%s: Error: AEM_DESC_TYPE_STREAM_PORT_%s index(%u) does not contain the mapping at index(%u) from the REMOVE_AUDIO_MAPPINGS command\n", + __func__, (desc_type == AEM_DESC_TYPE_STREAM_PORT_INPUT) ? "INPUT" : "OUTPUT", desc_index, i); + rc = AECP_AEM_BAD_ARGUMENTS; + goto exit; + } + } + + for (i = 0; i < remove_entries_count; i++) { + stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][remove_entries[i].registered_index].valid = false; + } + +exit: + return rc; +} + +int audio_mappings_add(avb_u16 desc_index, avb_u16 desc_type, avb_u16 mappings_count, void *audio_mappings) +{ + struct audio_mapping_entry (*stream_port_audio_mappings)[MAX_AUDIO_MAPS][MAX_AUDIO_MAP_SIZE]; + avb_u16 mapping_stream_index, mapping_stream_channel, mapping_cluster_offset, mapping_cluster_channel; + struct mapping_entry new_entries[MAX_AUDIO_MAP_SIZE] = {{-1 , -1}}; + unsigned int new_entries_count = 0; + int rc = AECP_AEM_SUCCESS; + unsigned int i, j; + int index; + + switch(desc_type) { + case AEM_DESC_TYPE_STREAM_PORT_INPUT: + stream_port_audio_mappings = stream_port_input_audio_mappings; + + if (desc_index >= MAX_STREAM_PORT_INPUT) { + printf("%s: Error: Unsupported AEM_DESC_TYPE_STREAM_PORT_INPUT index(%u). Max supported(%u)\n", __func__, desc_index, MAX_STREAM_PORT_INPUT - 1); + rc = AECP_AEM_NOT_SUPPORTED; + goto exit; + } + + break; + + case AEM_DESC_TYPE_STREAM_PORT_OUTPUT: + stream_port_audio_mappings = stream_port_output_audio_mappings; + + if (desc_index >= MAX_STREAM_PORT_OUTPUT) { + printf("%s: Error: Unsupported AEM_DESC_TYPE_STREAM_PORT_OUTPUT index(%u). Max supported(%u)\n", __func__, desc_index, MAX_STREAM_PORT_OUTPUT - 1); + rc = AECP_AEM_NOT_SUPPORTED; + goto exit; + } + + break; + + default: + printf("%s: Error: Unknown descriptor type(%u)\n", __func__, desc_type); + rc = AECP_AEM_BAD_ARGUMENTS; + goto exit; + } + + for (i = 0; i < mappings_count; i++) { + mapping_stream_index = ((struct aecp_aem_get_audio_map_mappings_format *)audio_mappings)[i].mapping_stream_index; + mapping_stream_channel = ((struct aecp_aem_get_audio_map_mappings_format *)audio_mappings)[i].mapping_stream_channel; + mapping_cluster_offset = ((struct aecp_aem_get_audio_map_mappings_format *)audio_mappings)[i].mapping_cluster_offset; + mapping_cluster_channel = ((struct aecp_aem_get_audio_map_mappings_format *)audio_mappings)[i].mapping_cluster_channel; + + for (j = 0; j < MAX_AUDIO_MAP_SIZE; j++) { + /* Only compare current valid audio mappings to the audio mappings of the command */ + if (!stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].valid) + continue; + + if (!memcmp(&((struct aecp_aem_get_audio_map_mappings_format *)audio_mappings)[i], &stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping, sizeof(struct aecp_aem_get_audio_map_mappings_format))) { + /* The same audio mapping is already here and valid */ + goto next_mapping; + } + + if (desc_type == AEM_DESC_TYPE_STREAM_PORT_INPUT) { + /* If the command, on a Stream Port Input, contains a mapping + * that references an existing mapping with the same cluster’s channel + * and two different stream’s channels that are not redundant, + * the app may accept the command and override the previous mapping. + * As per MILAN Specification v1.2 5.4.2.27 + */ + if ((mapping_cluster_offset == stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping.mapping_cluster_offset) && + (mapping_cluster_channel == stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping.mapping_cluster_channel) && + ((mapping_stream_index != stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping.mapping_stream_index) || + (mapping_stream_channel != stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping.mapping_stream_channel))) { + new_entries[new_entries_count].registered_index = j; + new_entries[new_entries_count].mapping_index = i; + new_entries_count++; + goto next_mapping; + } + + } else { /* AEM_DESC_TYPE_STREAM_PORT_OUTPUT */ + /* If the command, on a Stream Port Output, contains a mapping + * that references an existing mapping with the same stream’s channel + * and two different cluster’s channels that are not redundant, + * the app may accept the command and override the previous mapping. + * As per MILAN Specification v1.2 5.4.2.27 + */ + if ((mapping_stream_index == stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping.mapping_stream_index) && + (mapping_stream_channel == stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping.mapping_stream_channel) && + ((mapping_cluster_offset != stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping.mapping_cluster_offset) || + (mapping_cluster_channel != stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][j].mapping.mapping_cluster_channel))) { + new_entries[new_entries_count].registered_index = j; + new_entries[new_entries_count].mapping_index = i; + new_entries_count++; + goto next_mapping; + } + } + } + + index = get_free_audio_mapping_index(stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX]); + if (index < 0) { + printf("%s: Error: AEM_DESC_TYPE_STREAM_PORT_%s index(%u)'s mappings are full. Max supported(%u)\n", + __func__, (desc_type == AEM_DESC_TYPE_STREAM_PORT_INPUT) ? "INPUT" : "OUTPUT", desc_index, MAX_AUDIO_MAP_SIZE); + rc = AECP_AEM_NOT_SUPPORTED; + goto mappings_full; /* Unable to get a free entry from our audio mappings array */ + } + + /* Save the entry of the new audio mapping to add */ + new_entries[new_entries_count].registered_index = index; + new_entries[new_entries_count].mapping_index = i; + new_entries_count++; + +next_mapping: + continue; + } + + /* Command is valid, update the audio mappings */ + for (i = 0; i < new_entries_count; i++) { + memcpy(&stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][new_entries[i].registered_index].mapping, + &((struct aecp_aem_get_audio_map_mappings_format *)audio_mappings)[new_entries[i].mapping_index], + sizeof(struct aecp_aem_get_audio_map_mappings_format)); + + stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][new_entries[i].registered_index].valid = true; + } + +mappings_full: + /* Reset selected state of audio_mapping_entries, they either became valid or the status is AECP_AEM_NOT_SUPPORTED */ + for (i = 0; i < MAX_AUDIO_MAP_SIZE; i++) { + stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][i].selected = false; + } + +exit: + return rc; +} + +int audio_mappings_get(avb_u16 desc_index, avb_u16 desc_type, avb_u16 map_index, avb_u16 *number_of_maps, avb_u16 *mappings_count, void *audio_mappings) +{ + struct audio_mapping_entry (*stream_port_audio_mappings)[MAX_AUDIO_MAPS][MAX_AUDIO_MAP_SIZE]; + avb_u16 mapping_index = 0; + int rc = AECP_AEM_SUCCESS; + unsigned int i; + + if (map_index >= MAX_AUDIO_MAPS) { + printf("%s: Error: Unsupported MAP INDEX index(%u). Max supported(%u)\n", __func__, map_index, MAX_AUDIO_MAPS - 1); + rc = AECP_AEM_BAD_ARGUMENTS; + goto exit; + } + + switch(desc_type) { + case AEM_DESC_TYPE_STREAM_PORT_INPUT: + stream_port_audio_mappings = stream_port_input_audio_mappings; + + if (desc_index >= MAX_STREAM_PORT_INPUT) { + printf("%s: Error: Unsupported AEM_DESC_TYPE_STREAM_PORT_INPUT index(%u). Max supported(%u)\n", __func__, desc_index, MAX_STREAM_PORT_INPUT - 1); + rc = AECP_AEM_NOT_SUPPORTED; + goto exit; + } + + break; + + case AEM_DESC_TYPE_STREAM_PORT_OUTPUT: + stream_port_audio_mappings = stream_port_output_audio_mappings; + + if (desc_index >= MAX_STREAM_PORT_OUTPUT) { + printf("%s: Error: Unsupported AEM_DESC_TYPE_STREAM_PORT_OUTPUT index(%u). Max supported(%u)\n", __func__, desc_index, MAX_STREAM_PORT_OUTPUT - 1); + rc = AECP_AEM_NOT_SUPPORTED; + goto exit; + } + + break; + + default: + printf("%s: Error: Unknown descriptor type(%u)\n", __func__, desc_type); + rc = AECP_AEM_BAD_ARGUMENTS; + goto exit; + } + + for (i = 0; i < MAX_AUDIO_MAP_SIZE; i++) { + if (stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][i].valid) { + memcpy(&((struct aecp_aem_get_audio_map_mappings_format *)audio_mappings)[mapping_index], &stream_port_audio_mappings[desc_index][AUDIO_MAP_IDX][i].mapping, sizeof(struct aecp_aem_get_audio_map_mappings_format)); + mapping_index++; + } + } + + *mappings_count = mapping_index; + *number_of_maps = MAX_AUDIO_MAPS; + +exit: + return rc; +} + +int audio_mappings_init(void) +{ + unsigned int i, j; + int rc = AECP_AEM_SUCCESS; + + for (i = 0; i < MAX_STREAM_PORT_INPUT; i++) { + for (j = 0; j < MAX_AUDIO_MAP_SIZE; j++) { + stream_port_input_audio_mappings[i][AUDIO_MAP_IDX][j].valid = false; + stream_port_input_audio_mappings[i][AUDIO_MAP_IDX][j].selected = false; + } + } + + for (i = 0; i < MAX_STREAM_PORT_OUTPUT; i++) { + for (j = 0; j < MAX_AUDIO_MAP_SIZE; j++) { + stream_port_output_audio_mappings[i][AUDIO_MAP_IDX][j].valid = false; + stream_port_output_audio_mappings[i][AUDIO_MAP_IDX][j].selected = false; + } + } + + return rc; +} diff --git a/apps/linux/common/audio_mappings.h b/apps/linux/common/audio_mappings.h new file mode 100644 index 0000000..9e8b5a4 --- /dev/null +++ b/apps/linux/common/audio_mappings.h @@ -0,0 +1,17 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __AUDIO_MAPPINGS_H__ +#define __AUDIO_MAPPINGS_H__ + +#include + +int audio_mappings_init(void); +int audio_mappings_remove(avb_u16 desc_index, avb_u16 desc_type, avb_u16 mappings_count, void *audio_mappings); +int audio_mappings_add(avb_u16 desc_index, avb_u16 desc_type, avb_u16 mappings_count, void *audio_mappings); +int audio_mappings_get(avb_u16 desc_index, avb_u16 desc_type, avb_u16 map_index, avb_u16 *number_of_maps, avb_u16 *mappings_count, void *audio_mappings); + +#endif /* __AUDIO_MAPPINGS_H__ */ diff --git a/apps/linux/common/avb_stream.c b/apps/linux/common/avb_stream.c new file mode 100644 index 0000000..26c9818 --- /dev/null +++ b/apps/linux/common/avb_stream.c @@ -0,0 +1,326 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "log.h" +#include "avb_stream.h" +#include "avb_stream_config.h" +#include "time.h" +#include +#include +#include "msrp.h" +#include "common.h" + +static struct avb_handle *s_avb_handle = NULL; + +static aar_avb_stream_t *avbstream_get_talker_stream(unsigned int unique_id) +{ + if (unique_id >= g_max_avb_talker_streams) + return NULL; + + return &g_avb_talker_streams[unique_id]; +} + +static aar_avb_stream_t *avbstream_get_listener_stream(unsigned int unique_id) +{ + if (unique_id >= g_max_avb_listener_streams) + return NULL; + + return &g_avb_listener_streams[unique_id]; +} + +int avbstream_init(void) +{ + int avb_result; + int rc; + + avb_result = avb_init(&s_avb_handle, 0); + if (avb_result != AVB_SUCCESS) { + ERR("avb_init() failed: %s", avb_strerror(avb_result)); + // Initialization failed + rc = -1; + goto err_init; + } + + DBG("avb_handle: %p", s_avb_handle); + + return 0; + +err_init: + return rc; +} + +int avbstream_exit(void) +{ + avb_exit(s_avb_handle); + + s_avb_handle = NULL; + + return 0; +} + + +static void print_stream_param(struct avb_stream_params * stream_params) +{ + INF("direction: %d", stream_params->direction); + INF("port: %d", stream_params->port); + INF("stream_class: %d", stream_params->stream_class); + INF("clock_domain: %d", stream_params->clock_domain); + INF("flags: %d", stream_params->flags); + INF("talker.latency: %d", stream_params->talker.latency); + INF("stream_id: " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + INF("dst_mac: " MAC_STR_FMT, MAC_STR(stream_params->dst_mac)); + INF("format.u.s.v: %d", stream_params->format.u.s.v); + INF("format.u.s.subtype: %d", stream_params->format.u.s.subtype); + if (stream_params->format.u.s.subtype == AVTP_SUBTYPE_61883_IIDC) { + INF("format.u.s.subtype_u.iec61883.r: %d", stream_params->format.u.s.subtype_u.iec61883.r); + INF("format.u.s.subtype_u.iec61883.sf: 0x%X", stream_params->format.u.s.subtype_u.iec61883.sf); + INF("format.u.s.subtype_u.iec61883.fmt: 0x%X", stream_params->format.u.s.subtype_u.iec61883.fmt); + if (stream_params->format.u.s.subtype_u.iec61883.sf == IEC_61883_SF_61883) { + if (stream_params->format.u.s.subtype_u.iec61883.fmt == IEC_61883_CIP_FMT_6) { + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.fdf_u.fdf.evt: 0x%X", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.fdf_u.fdf.evt); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.fdf_u.fdf.sfc: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.fdf_u.fdf.sfc); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.dbs: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.dbs); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.sc: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.sc); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.ut: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.ut); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.nb: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.nb); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.b: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.b); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.label_iec_60958_cnt: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.label_iec_60958_cnt); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.label_mbla_cnt: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.label_mbla_cnt); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.label_midi_cnt: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.label_midi_cnt); + INF("format.u.s.subtype_u.iec61883.format_u.iec61883_6.label_smptecnt: %d", stream_params->format.u.s.subtype_u.iec61883.format_u.iec61883_6.label_smptecnt); + } + } + } else if (stream_params->format.u.s.subtype == AVTP_SUBTYPE_CVF) { + INF("format.u.s.subtype_u.cvf.format: %d", stream_params->format.u.s.subtype_u.cvf.format); + INF("format.u.s.subtype_u.cvf.subtype: %d", stream_params->format.u.s.subtype_u.cvf.subtype); + if (stream_params->format.u.s.subtype_u.cvf.subtype == CVF_FORMAT_SUBTYPE_MJPEG) { + INF("format.u.s.subtype_u.cvf.format_u.mjpeg.p: %d", stream_params->format.u.s.subtype_u.cvf.format_u.mjpeg.p); + INF("format.u.s.subtype_u.cvf.format_u.mjpeg.type: %d", stream_params->format.u.s.subtype_u.cvf.format_u.mjpeg.type); + INF("format.u.s.subtype_u.cvf.format_u.mjpeg.width: %d", stream_params->format.u.s.subtype_u.cvf.format_u.mjpeg.width); + INF("format.u.s.subtype_u.cvf.format_u.mjpeg.height: %d", stream_params->format.u.s.subtype_u.cvf.format_u.mjpeg.height); + } else if (stream_params->format.u.s.subtype_u.cvf.subtype == CVF_FORMAT_SUBTYPE_JPEG2000) { + INF("format.u.s.subtype_u.cvf.format_u.jpeg2000.p: %d", stream_params->format.u.s.subtype_u.cvf.format_u.jpeg2000.p); + } + } else if (stream_params->format.u.s.subtype == AVTP_SUBTYPE_CRF) { + INF("format.u.s.subtype_u.crf.type: %d", stream_params->format.u.s.subtype_u.crf.type); + INF("format.u.s.subtype_u.crf.pull: %d", stream_params->format.u.s.subtype_u.crf.pull); + INF("format.u.s.subtype_u.crf.timestamps_per_pdu: %x", stream_params->format.u.s.subtype_u.crf.timestamps_per_pdu); + INF("format.u.s.subtype_u.crf.timestamp_interval_msb: %x", stream_params->format.u.s.subtype_u.crf.timestamp_interval_msb); + INF("format.u.s.subtype_u.crf.timestamp_interval_lsb: %x", stream_params->format.u.s.subtype_u.crf.timestamp_interval_lsb); +#ifdef __BIG_ENDIAN__ + INF("format.u.s.subtype_u.crf.base_frequency: %x", stream_params->format.u.s.subtype_u.crf.base_frequency); +#else + INF("format.u.s.subtype_u.crf.base_frequency_msb: %x", stream_params->format.u.s.subtype_u.crf.base_frequency_msb); + INF("format.u.s.subtype_u.crf.base_frequency_lsb: %x", stream_params->format.u.s.subtype_u.crf.base_frequency_lsb); +#endif // __BIG_ENDIAN__ + } + INF("format binary: %lx", *(long unsigned int *)&stream_params->format); + INF("sample_rate: %d", avdecc_fmt_sample_rate(&stream_params->format)); + INF("sample_size: %d", avdecc_fmt_sample_size(&stream_params->format)); +} + +unsigned int avbstream_batch_size(unsigned int batch_size_ns, struct avb_stream_params *params) +{ + return (((uint64_t)batch_size_ns * avdecc_fmt_sample_rate(¶ms->format) * avdecc_fmt_sample_size(¶ms->format) + NSECS_PER_SEC - 1) / NSECS_PER_SEC); +} + +int avbstream_listener_add(unsigned int unique_id, struct avb_stream_params *params, aar_avb_stream_t **stream) +{ + int result; + aar_avb_stream_t *avbstream; + struct avb_stream_params *stream_params; + + DBG("unique_id: %d", unique_id); + + avbstream = avbstream_get_listener_stream(unique_id); + if (!avbstream) { + ERR("Could not get listener AVB stream unique id %d", unique_id); + return -1; + } else if (avbstream->stream_handle) { + ERR("Stream (%p) already used.", avbstream); + return -1; + } + + stream_params = &avbstream->stream_params; + + if (params) + memcpy(stream_params, params, sizeof(*params)); + + stream_params->talker.latency = 0; + + DBG("avbstream: %p, stream_params: %p", avbstream, stream_params); + + print_stream_param(stream_params); + + avbstream->batch_size_ns = max(avbstream->batch_size_ns, sr_class_interval_p(avbstream->stream_params.stream_class) / sr_class_interval_q(avbstream->stream_params.stream_class)); + + // Restart the statistic counter + avbstream->stats.counter_stats.tx_err = 0; + avbstream->stats.counter_stats.rx_err = 0; + avbstream->stats.counter_stats.batch_rx = 0; + avbstream->stats.counter_stats.batch_tx = 0; + + // Init the quality statistics + avbstream->last_gptp_time = 0; + avbstream->last_event_ts = 0; + avbstream->is_first_wakeup = 1; + stats_init(&avbstream->stats.gptp_2cont_wakeup, 31, avbstream, NULL); + stats_init(&avbstream->stats.event_2cont_wakeup, 31, avbstream, NULL); + stats_init(&avbstream->stats.event_gptp, 31, avbstream, NULL); + + INF("stream_id: " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + INF("dst_mac: " MAC_STR_FMT, MAC_STR(stream_params->dst_mac)); + + // Initialize listener stream for RX + avbstream->cur_batch_size = avbstream_batch_size(avbstream->batch_size_ns, stream_params); + + result = avb_stream_create(s_avb_handle, &avbstream->stream_handle, stream_params, &avbstream->cur_batch_size, 0); + if (result != 0) { + ERR("create listener stream failed, err %d", result); + return -1; + } + + INF("batch: %u", avbstream->cur_batch_size); + + if (stream) + *stream = avbstream; + + + DBG("avb stream created, stream: %p", avbstream); + + return 0; +} + + +int avbstream_talker_add(unsigned int unique_id, struct avb_stream_params *params, aar_avb_stream_t **stream) +{ + int result; + aar_avb_stream_t *avbstream; + struct avb_stream_params *stream_params; + + DBG("unique_id: %d", unique_id); + + avbstream = avbstream_get_talker_stream(unique_id); + if (!avbstream) { + ERR("Could not get talker AVB stream unique id %d", unique_id); + return -1; + } else if (avbstream->stream_handle) { + ERR("Stream (%p) already used.", avbstream); + return -1; + } + + stream_params = &avbstream->stream_params; + + if (params) + memcpy(stream_params, params, sizeof(*params)); + + DBG("avbstream: %p, stream_params: %p", avbstream, stream_params); + + print_stream_param(stream_params); + + avbstream->batch_size_ns = max(avbstream->batch_size_ns, sr_class_interval_p(avbstream->stream_params.stream_class) / sr_class_interval_q(avbstream->stream_params.stream_class)); + + // Restart the statistic counter + avbstream->stats.counter_stats.tx_err = 0; + avbstream->stats.counter_stats.rx_err = 0; + avbstream->stats.counter_stats.batch_rx = 0; + avbstream->stats.counter_stats.batch_tx = 0; + + // Init the quality statistics + avbstream->last_gptp_time = 0; + avbstream->is_first_wakeup = 1; + stats_init(&avbstream->stats.gptp_2cont_wakeup, 31, avbstream, NULL); + + INF("stream_id: " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + INF("dst_mac: " MAC_STR_FMT, MAC_STR(stream_params->dst_mac)); + + // Initialize talker stream for TX + avbstream->cur_batch_size = avbstream_batch_size(avbstream->batch_size_ns, stream_params); + + result = avb_stream_create(s_avb_handle, &avbstream->stream_handle, stream_params, &avbstream->cur_batch_size, 0); + if (result != 0) { + ERR("create talker stream failed, err %d", result); + return -1; + } + + INF("batch: %u", avbstream->cur_batch_size); + + if (stream) + *stream = avbstream; + + DBG("avb stream created, stream: %p", avbstream); + + return 0; +} + +int avbstream_listener_remove(unsigned int unique_id) +{ + aar_avb_stream_t *avbstream; + int result; + + DBG("unique_id: %d", unique_id); + + avbstream = avbstream_get_listener_stream(unique_id); + if (!avbstream) { + ERR("Could not get listener AVB stream unique id %d", unique_id); + return -1; + } else if (!avbstream->stream_handle) { + ERR("Stream (%p) not created.", avbstream); + return -1; + } + + // Destroy listener stream + result = avb_stream_destroy(avbstream->stream_handle); + if (result != 0) { + ERR("router remove failed"); + return -1; + } + + avbstream->stream_handle = NULL; + + return 0; +} + +int avbstream_talker_remove(unsigned int unique_id) +{ + aar_avb_stream_t *avbstream; + int result; + + DBG("unique_id: %d", unique_id); + + avbstream = avbstream_get_talker_stream(unique_id); + if (!avbstream) { + ERR("Could not get talker AVB stream unique id %d", unique_id); + return -1; + } else if (!avbstream->stream_handle) { + ERR("Stream (%p) not created.", avbstream); + return -1; + } + + // destroy talker stream + result = avb_stream_destroy(avbstream->stream_handle); + if (result != 0) { + ERR("router remove failed"); + return -1; + } + + avbstream->stream_handle = NULL; + + return 0; +} + +unsigned int avbstream_get_max_transit_time(aar_avb_stream_t *stream) { + return sr_class_max_transit_time(stream->stream_params.stream_class) / NSECS_PER_MSEC; +} + +struct avb_handle *avbstream_get_avb_handle(void) +{ + return s_avb_handle; +} diff --git a/apps/linux/common/avb_stream.h b/apps/linux/common/avb_stream.h new file mode 100644 index 0000000..1024bdd --- /dev/null +++ b/apps/linux/common/avb_stream.h @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __AVB_STREAM_H__ +#define __AVB_STREAM_H__ + +#include "avb_stream_config.h" + +/** + * @addtogroup avb_stream + * @{ + */ + +/** + * @brief Initialize AVB stack and AVB resources. + * + * @return 0 if success or negative error code. + */ +int avbstream_init(); + +/** + * @brief De-initialize AVB stack and AVB resources. + * + * @return 0 if success or negative error code. + */ +int avbstream_exit(); + +/** + * @brief Get max transit time (in ms) of AVB stream + * + * @param stream The AVB stream handle pointer + * + * @return max transit time (in ms) + */ +unsigned int avbstream_get_max_transit_time(aar_avb_stream_t *stream); + +int avbstream_listener_add(unsigned int unique_id, struct avb_stream_params *params, aar_avb_stream_t **stream); +int avbstream_talker_add(unsigned int unique_id, struct avb_stream_params *params, aar_avb_stream_t **stream); +int avbstream_listener_remove(unsigned int unique_id); +int avbstream_talker_remove(unsigned int unique_id); +unsigned int avbstream_batch_size(unsigned int batch_size_ns, struct avb_stream_params *params); + +struct avb_handle *avbstream_get_avb_handle(void); +/** @} */ +#endif /* __AVB_STREAM_H__ */ diff --git a/apps/linux/common/avb_stream_config.h b/apps/linux/common/avb_stream_config.h new file mode 100644 index 0000000..f21934a --- /dev/null +++ b/apps/linux/common/avb_stream_config.h @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __AVB_STREAM_CONFIG_H__ +#define __AVB_STREAM_CONFIG_H__ + +#include +#include +#include "stats.h" + + +#define AAR_AVB_ETH_PORT 0 + +/** + * @addtogroup avb_stream + * @{ + */ + +/** AVB counter statistic */ +typedef struct { + unsigned int tx_err; /**< AVB send error counter */ + unsigned int rx_err; /**< AVB receive error counter */ + + unsigned int batch_rx; /**< AVB batches received counter */ + unsigned int batch_tx; /**< AVB batches transmitted counter */ +} aar_avb_counter_stats_t; + +typedef struct { + aar_avb_counter_stats_t counter_stats; /**< AVB Simple counter statistic */ + struct stats gptp_2cont_wakeup; /**< gPTP time delta since last wakeup */ + struct stats event_2cont_wakeup; /**< AVB event time delta since last wakeup (1st event) */ + struct stats event_gptp; /**< time delta between event time vs current gPTP time */ +} aar_avb_stats_t; + +typedef struct _AAR_AVB_STREAM { + struct avb_stream_params stream_params; + struct avb_stream_handle *stream_handle; + unsigned int batch_size_ns; + unsigned int cur_batch_size; + + unsigned int last_gptp_time; /**< gPTP time of last wakeup */ + unsigned int last_event_ts; /**< Event timestamp of last wakeup */ + unsigned int last_event_frame_offset; + unsigned int last_exchanged_frames; /**< Number of frames exchanged in the last batch */ + char is_first_wakeup; /**< Is first wakeup flag */ + + aar_avb_stats_t stats; /**< AVB statistics */ +} aar_avb_stream_t; + +extern const unsigned int g_max_avb_talker_streams; +extern const unsigned int g_max_avb_listener_streams; +extern aar_avb_stream_t g_avb_talker_streams[]; +extern aar_avb_stream_t g_avb_listener_streams[]; + +/** @} */ +#endif /* __AVB_STREAM_CONFIG_H__ */ diff --git a/apps/linux/common/avdecc.c b/apps/linux/common/avdecc.c new file mode 100644 index 0000000..5611b8e --- /dev/null +++ b/apps/linux/common/avdecc.c @@ -0,0 +1,334 @@ +/* + * Copyright 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define NVRAM_BINDING_PARAMS_PER_ENTRY 6 +#define NVRAM_ENTRY_MAX_LEN 256 +#define MAX_LISTENER_STREAMS 16 +#define TMP_FILENAME "/tmp_milan_binding_params" +#define FILENAME_MAX_LEN 256 + +static int nvram_update_entry(const char *binding_filename, avb_u64 entity_id, avb_u16 listener_stream_index, avb_u64 talker_entity_id, avb_u16 talker_stream_index, avb_u64 controller_entity_id, avb_u16 started) +{ + FILE *fOrig; + FILE *fTmp; + int fParent; + char new_entry[NVRAM_ENTRY_MAX_LEN]; + char *orig_entry = NULL; + unsigned int read_size; + size_t len = 0; + avb_u64 orig_entity_id, orig_talker_entity_id, orig_controller_entity_id; + avb_u16 orig_listener_stream_index, orig_talker_stream_index, orig_started; + bool create_new_entry = false; + int rc = 0; + char *parent_dirname = NULL; + char tmp_filename[FILENAME_MAX_LEN]; + char binding_filename_cpy[FILENAME_MAX_LEN]; + + if (h_strncpy_strict(binding_filename_cpy, binding_filename, FILENAME_MAX_LEN) < 0) { + printf("binding_filename (%s) copy failed, max allowed characters (%u)\n", binding_filename, FILENAME_MAX_LEN); + goto err; + } + + parent_dirname = dirname(binding_filename_cpy); + + /* Use the same parent directory as the binding file to keep the temporary file on the same filesystem, for the sake of rename() */ + if (h_strncpy_strict(tmp_filename, parent_dirname, FILENAME_MAX_LEN) < 0) { + printf("copy of binding file dirname (%s) failed\n", parent_dirname); + goto err; + } + + if (strlen(parent_dirname) + strlen(TMP_FILENAME) + 1 > FILENAME_MAX_LEN) { + printf("tmp_filename %s %s is too long\n", parent_dirname, TMP_FILENAME); + goto err; + } + + strncat(tmp_filename, TMP_FILENAME, strlen(TMP_FILENAME)); + + fParent = open(parent_dirname, O_RDONLY | O_DIRECTORY); + if (fParent < 0){ + printf("open(%s) failed: %s\n", parent_dirname, strerror(errno)); + goto err; + } + + fOrig = fopen(binding_filename, "r"); + if (fOrig == NULL) { + printf("fopen(%s) failed: %s\n", binding_filename, strerror(errno)); + goto err_orig_file_open; + } + + fTmp = fopen(tmp_filename, "w"); + if (fTmp == NULL) { + printf("fopen(%s) failed: %s\n", tmp_filename, strerror(errno)); + goto err_tmp_file_open; + } + + if (talker_entity_id) { + create_new_entry = true; + + sprintf(new_entry, "%016"PRIx64" %u %016"PRIx64" %u %016"PRIx64" %u\n", + entity_id , listener_stream_index, + talker_entity_id, talker_stream_index, + controller_entity_id, started); + + } + + while(((read_size = getline(&orig_entry, &len, fOrig)) != -1)) { + if (read_size != strlen(orig_entry)) { + printf("Unexpected embedded null byte(s)\n"); + goto err_read_entry; + } + + if (sscanf(orig_entry, "%016"PRIx64" %hu %016"PRIx64" %hu %016"PRIx64" %hu", &orig_entity_id, &orig_listener_stream_index, + &orig_talker_entity_id, &orig_talker_stream_index, &orig_controller_entity_id, &orig_started) == NVRAM_BINDING_PARAMS_PER_ENTRY) { + + if (orig_listener_stream_index == listener_stream_index && orig_entity_id == entity_id) { + /* If matching listener entity ID and stream index, either remove or update it + * in place (no need for a new entry in file). + */ + create_new_entry = false; + + if (talker_entity_id) { + /* If valid talker_entity_id, change the original entry with new one. */ + //printf("update new entry %s\n", new_entry); + fputs(new_entry, fTmp); + } else { + /* Zero talker_entity_id means remove original entry. */ + continue; + } + } else { + //printf("keep original entry %s\n", orig_entry); + fputs(orig_entry, fTmp); + } + } + } + + /* Create new entry in file with valid binding params. */ + if (create_new_entry) + fputs(new_entry, fTmp); + + rc = fflush(fTmp); + if (rc < 0) { + printf("fflush() failed, %s\n", strerror(errno)); + goto err_fflush; + } + + fsync(fileno(fTmp)); + + free(orig_entry); + fclose(fOrig); + fclose(fTmp); + + rc = rename(tmp_filename, binding_filename); + if (rc < 0) { + printf("rename() failed, %s\n", strerror(errno)); + goto err_rename; + } + + /* fsync parent directory to make sure the rename went through to the disk */ + fsync(fParent); + close(fParent); + + return 0; + +err_read_entry: +err_fflush: + fclose(fTmp); +err_tmp_file_open: + fclose(fOrig); +err_orig_file_open: +err_rename: + close(fParent); +err: + return -1; +} + +static int parse_binding_params_file(const char *binding_filename, struct genavb_msg_media_stack_bind *binding_params, unsigned int size) +{ + FILE *fBind; + char *nvram_entry = NULL; + unsigned int read_char; + avb_u64 entity_id, talker_entity_id, controller_entity_id; + avb_u16 listener_stream_index, talker_stream_index, started; + size_t len = 0; + int rc = 0; + + if (!binding_filename || !binding_params || !size) { + rc = -1; + goto err; + } + + fBind = fopen(binding_filename, "a+"); + if (fBind == NULL) { + printf("fopen(%s) failed: %s\n", binding_filename, strerror(errno)); + rc = -1; + goto err; + } + + printf("binding params file name: %s\n", binding_filename); + + /* read all entries in the nvram file one by one. if stream index matches read it */ + while (((read_char = getline(&nvram_entry, &len, fBind)) != -1)) { + if (read_char != strlen(nvram_entry)) { + printf("Unexpected embedded null byte(s)\n"); + rc = -1; + goto err_read_nvram; + } + + if (sscanf(nvram_entry, "%016"PRIx64" %hu %016"PRIx64" %hu %016"PRIx64" %hu", &entity_id, &listener_stream_index, + &talker_entity_id, &talker_stream_index, &controller_entity_id, &started) == NVRAM_BINDING_PARAMS_PER_ENTRY) { + + printf("Read EntityID %016"PRIx64" Listener unique ID %u Talker entity ID %016"PRIx64" Talker unique ID %u Controller entity ID%016"PRIx64" Started %u\n", + entity_id , listener_stream_index, talker_entity_id, talker_stream_index, + controller_entity_id, started); + + if (listener_stream_index < size) { + binding_params[listener_stream_index].entity_id = entity_id; + binding_params[listener_stream_index].listener_stream_index = listener_stream_index; + binding_params[listener_stream_index].talker_entity_id = talker_entity_id; + binding_params[listener_stream_index].talker_stream_index = talker_stream_index; + binding_params[listener_stream_index].controller_entity_id = controller_entity_id; + binding_params[listener_stream_index].started = started; + } else + printf("error on nvram entry %s, out of bound stream index %u\n", nvram_entry, listener_stream_index); + } else + printf("error: can not read data from nvram\n"); + } + +err_read_nvram: + free(nvram_entry); + fclose(fBind); + +err: + return rc; +} + +static int avdecc_listener_stream_bind_send_ipc(struct avb_control_handle *s_avdecc_handle, struct genavb_msg_media_stack_bind *binding_params) +{ + struct genavb_msg_media_stack_bind media_stack_bind; + unsigned int msg_len = sizeof(media_stack_bind); + genavb_msg_type_t msg_type = GENAVB_MSG_MEDIA_STACK_BIND; + int rc = AVB_SUCCESS; + + if (!binding_params || !s_avdecc_handle) { + rc = AVB_ERR_INVALID_PARAMS; + goto exit; + } + + media_stack_bind.entity_id = binding_params->entity_id; + media_stack_bind.listener_stream_index = binding_params->listener_stream_index; + media_stack_bind.talker_entity_id = binding_params->talker_entity_id; + media_stack_bind.talker_stream_index = binding_params->talker_stream_index; + media_stack_bind.controller_entity_id = binding_params->controller_entity_id; + media_stack_bind.started = binding_params->started; + + rc = avb_control_send(s_avdecc_handle, msg_type, &media_stack_bind, msg_len); + if (rc != AVB_SUCCESS) { + printf("avb_control_send (GENAVB_CTRL_AVDECC_MEDIA_STACK) failed: %s\n", avb_strerror(rc)); + goto exit; + } + + printf("Sent binding parameters: EntityID %016"PRIx64" Listener unique ID %u Talker entity ID %016"PRIx64" Talker unique ID %u Controller entity ID%016"PRIx64" Started %u\n", + binding_params->entity_id , binding_params->listener_stream_index, + binding_params->talker_entity_id, binding_params->talker_stream_index, + binding_params->controller_entity_id, binding_params->started); +exit: + return rc; +} + +/** Parses the file containing the saved binding params and init the avdecc stack with it + * + * \return 0 on success, negative otherwise. + * \param ctrl_h Handle to an avb_control_handle, opened for an AVB_CTRL_AVDECC_MEDIA_STACK channel. + * \param binding_filename path to the file used for saved binding parameters. + */ +int avdecc_nvm_bindings_init(struct avb_control_handle *s_avdecc_handle, const char *binding_filename) +{ + int i; + struct genavb_msg_media_stack_bind listener_streams_binding_params[MAX_LISTENER_STREAMS] = {0}; + + if (!s_avdecc_handle) { + printf("Invalid avb control handle\n"); + goto err; + } + + if (!binding_filename) { + printf("Invalid binding filename\n"); + goto err; + } + + /* Get saved binding parameters from non-volatile memory. */ + if (parse_binding_params_file(binding_filename, listener_streams_binding_params, MAX_LISTENER_STREAMS) < 0) { + printf("failed to parse binding file %s\n", binding_filename); + goto err; + } + + /* Send them to the AVDECC stack. */ + for (i = 0; i < MAX_LISTENER_STREAMS; i++) { + if (listener_streams_binding_params[i].talker_entity_id) { + if (avdecc_listener_stream_bind_send_ipc(s_avdecc_handle, &listener_streams_binding_params[i]) < 0) { + printf("failed to send binding params for stream %u\n", i); + goto err; + } + } + } + + return 0; + +err: + return -1; +} + +/** Updates the file containing the saved binding params with new binding params. If entry existed it will be updated + * otherwise a new entry is created. + * \return none + * \param binding_filename path to the file used for saved binding parameters. + * \param binding_params pointer to the genavb_msg_media_stack_bind struct received on bind event. + */ +void avdecc_nvm_bindings_update(const char *binding_filename, struct genavb_msg_media_stack_bind *binding_params) +{ + if (!binding_filename || !binding_params) + return; + + nvram_update_entry(binding_filename, binding_params->entity_id, binding_params->listener_stream_index, + binding_params->talker_entity_id, binding_params->talker_stream_index, + binding_params->controller_entity_id, binding_params->started); + + printf("update %s with entry: EntityID %016"PRIx64" Listener unique ID %u Talker entity ID %016"PRIx64" Talker unique ID %u Controller entity ID%016"PRIx64" Started %u\n", + binding_filename, binding_params->entity_id , binding_params->listener_stream_index, + binding_params->talker_entity_id, binding_params->talker_stream_index, + binding_params->controller_entity_id, binding_params->started); +} + +/** Remove binding entry from the file containing the saved binding params. + * + * \return none + * \param binding_filename path to the file used for saved binding parameters. + * \param unbinding_params pointer to the genavb_msg_media_stack_unbind struct received on unbind event. + */ +void avdecc_nvm_bindings_remove(const char *binding_filename, struct genavb_msg_media_stack_unbind *unbinding_params) +{ + if (!binding_filename || !unbinding_params) + return; + + nvram_update_entry(binding_filename, unbinding_params->entity_id, unbinding_params->listener_stream_index, 0, 0, 0, 0); + + printf("remove entry: EntityID %016"PRIx64" Listener unique ID %u from file %s\n", + unbinding_params->entity_id , unbinding_params->listener_stream_index, binding_filename); +} diff --git a/apps/linux/common/avdecc.h b/apps/linux/common/avdecc.h new file mode 100644 index 0000000..5de3666 --- /dev/null +++ b/apps/linux/common/avdecc.h @@ -0,0 +1,15 @@ +/* + * Copyright 2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_AVDECC_H_ +#define _COMMON_AVDECC_H_ + +#include + +int avdecc_nvm_bindings_init(struct avb_control_handle *s_avdecc_handle, const char *binding_filename); +void avdecc_nvm_bindings_update(const char *binding_filename, struct genavb_msg_media_stack_bind *binding_params); +void avdecc_nvm_bindings_remove(const char *binding_filename, struct genavb_msg_media_stack_unbind *unbinding_params); +#endif /* _COMMON_AVDECC_H_ */ diff --git a/apps/linux/common/clock.c b/apps/linux/common/clock.c new file mode 100644 index 0000000..992bf47 --- /dev/null +++ b/apps/linux/common/clock.c @@ -0,0 +1,107 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "clock.h" +#include "time.h" + +struct clock { + int id; + int fd; +}; + +#define CFG_PORTS 1 +#define PTP_DEVICE "/dev/ptp0" +#define CLOCKFD 3 +#define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD) +#define CLOCKID_TO_FD(clk) ((unsigned int) ~((clk) >> 3)) + +static struct clock c[CFG_PORTS]; + +int clock_init(int port_id) +{ + if (port_id >= CFG_PORTS) + goto err; + + c[port_id].fd= open(PTP_DEVICE, O_RDWR); + if (c[port_id].fd < 0) { + ERR("clock(%p) Couldn't open PTP char device: %s error: %d", &c, PTP_DEVICE, c[port_id].fd); + return -1; + } + + c[port_id].id = FD_TO_CLOCKID(c[port_id].fd); + DBG("clock(%p) PTP clock ID: 0x%x fd: 0x%x", &c, c[port_id].id, c[port_id].fd); + + return 0; +err: + return -1; +} + +int clock_exit(int port_id) +{ + if (port_id >= CFG_PORTS) + goto err; + + close(c[port_id].fd); + + return 0; +err: + return -1; +} + + +int clock_gettime32(int port_id, unsigned int *ns) +{ + int err = 0; + struct timespec now; + + if (port_id >= CFG_PORTS) + goto err; + + err = clock_gettime(c[port_id].id, &now); + if (err) { + ERR("clock(%p) clock_gettime failed: %s", &c, strerror(errno)); + return err; + } + + *ns = (unsigned long long)now.tv_sec*NSECS_PER_SEC + now.tv_nsec; + + return 0; +err: + return -1; +} + +int clock_gettime64(int port_id, unsigned long long *ns) +{ + int err = 0; + struct timespec now; + + if (port_id >= CFG_PORTS) + goto err; + + err = clock_gettime(c[port_id].id, &now); + if (err) { + ERR("clock(%p) clock_gettime failed: %s", &c, strerror(errno)); + return err; + } + + *ns = (unsigned long long)now.tv_sec*NSECS_PER_SEC + now.tv_nsec; + + return 0; +err: + return -1; +} diff --git a/apps/linux/common/clock.h b/apps/linux/common/clock.h new file mode 100644 index 0000000..224b0b5 --- /dev/null +++ b/apps/linux/common/clock.h @@ -0,0 +1,39 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _CLOCK_H_ +#define _CLOCK_H_ + +/** Initialize the PTP clock framework for a given Ethernet port. + * \param port_id id of the Ethernet port. + * \return 0 on success or -1 on error. + */ +int clock_init(int port_id); + +/** Close the PTP clock framework for a given Ethernet port. + * \param port_id id of the Ethernet port. + * \return 0 on success or -1 on error. + */ +int clock_exit(int port_id); + +/** + * Get GPTP time in nanoseconds modulo 2^32. + * \param port_id id of the Ethernet port from which PTP clock is to be read. + * \param ns pointer to u32 variable that will hold the result. + * \return 0 on success, or negative value on error. + */ +int clock_gettime32(int port_id, unsigned int *ns); + +/** + * Get GPTP time in nanoseconds. + * \param port_id id of the Ethernet port from which PTP clock is to be read. + * \param ns pointer to u64 variable that will hold the result. + * \return 0 on success, or negative value on error. + */ +int clock_gettime64(int port_id, unsigned long long *ns); + +#endif /* _OS_CLOCK_H_ */ diff --git a/apps/linux/common/clock_domain.c b/apps/linux/common/clock_domain.c new file mode 100644 index 0000000..2824391 --- /dev/null +++ b/apps/linux/common/clock_domain.c @@ -0,0 +1,278 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include + +#include "clock_domain.h" +#include "log.h" + +static struct avb_control_handle *s_clk_handle = NULL; +static int audio_clk_sync[GENAVB_CLOCK_DOMAIN_MAX] = {0}; +static unsigned int clk_domain_is_valid[GENAVB_CLOCK_DOMAIN_MAX] = {0}; +static pthread_mutex_t clk_domain_mutex = PTHREAD_MUTEX_INITIALIZER; + +static const char *clk_domain_name[] = { + [GENAVB_CLOCK_DOMAIN_0] = "GENAVB_CLOCK_DOMAIN_0", + [GENAVB_CLOCK_DOMAIN_1] = "GENAVB_CLOCK_DOMAIN_1", + [GENAVB_CLOCK_DOMAIN_2] = "GENAVB_CLOCK_DOMAIN_2", + [GENAVB_CLOCK_DOMAIN_3] = "GENAVB_CLOCK_DOMAIN_3", + [GENAVB_CLOCK_DOMAIN_MAX] = "GENAVB_CLOCK_DOMAIN_MAX" +}; + +static const char *clk_source_type_name[] = { + [GENAVB_CLOCK_SOURCE_TYPE_INTERNAL] = "GENAVB_CLOCK_SOURCE_TYPE_INTERNAL", + [GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM] = "GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM" +}; + +static const char *clk_source_name[] = { + [GENAVB_CLOCK_SOURCE_AUDIO_CLK] = "GENAVB_CLOCK_SOURCE_AUDIO_CLK", + [GENAVB_CLOCK_SOURCE_PTP_CLK] = "GENAVB_CLOCK_SOURCE_PTP_CLK" +}; + +static const char *clk_domain_status_name[] = { + [GENAVB_CLOCK_DOMAIN_STATUS_UNLOCKED] = "GENAVB_CLOCK_DOMAIN_STATUS_UNLOCKED", + [GENAVB_CLOCK_DOMAIN_STATUS_LOCKED] = "GENAVB_CLOCK_DOMAIN_STATUS_LOCKED", + [GENAVB_CLOCK_DOMAIN_STATUS_FREE_WHEELING] = "GENAVB_CLOCK_DOMAIN_STATUS_FREE_WHEELING", + [GENAVB_CLOCK_DOMAIN_STATUS_HW_ERROR] = "GENAVB_CLOCK_DOMAIN_STATUS_HW_ERROR" +}; + +const char* get_genavb_clock_domain_t_name(genavb_clock_domain_t type) +{ + if ((type < GENAVB_CLOCK_DOMAIN_0) || (type > GENAVB_CLOCK_DOMAIN_MAX)) + return ""; + else + return clk_domain_name[type]; +} + +const char* get_genavb_clock_source_type_t_name(genavb_clock_source_type_t type) +{ +if ((type < GENAVB_CLOCK_SOURCE_TYPE_INTERNAL) || (type > GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM)) + return ""; + else + return clk_source_type_name[type]; +} + +const char* get_genavb_clock_source_local_id_t_name(genavb_clock_source_local_id_t type) +{ + if ((type < GENAVB_CLOCK_SOURCE_AUDIO_CLK) || (type > GENAVB_CLOCK_SOURCE_PTP_CLK)) + return ""; + else + return clk_source_name[type]; +} + +const char* get_genavb_clock_domain_status_t_name(genavb_clock_domain_status_t type) +{ + if ((type < GENAVB_CLOCK_DOMAIN_STATUS_UNLOCKED) || (type > GENAVB_CLOCK_DOMAIN_STATUS_HW_ERROR)) + return ""; + else + return clk_domain_status_name[type]; +} + +int get_clk_domain_validity(avb_clock_domain_t clk_domain) +{ + unsigned int validity = 0; + + pthread_mutex_lock(&clk_domain_mutex); + + if (clk_domain < GENAVB_CLOCK_DOMAIN_MAX) + validity = clk_domain_is_valid[clk_domain]; + else + validity = -1; + + pthread_mutex_unlock(&clk_domain_mutex); + + return validity; +} + +void set_clk_domain_validity(avb_clock_domain_t clk_domain, int validity) +{ + pthread_mutex_lock(&clk_domain_mutex); + + if (clk_domain < GENAVB_CLOCK_DOMAIN_MAX) + clk_domain_is_valid[clk_domain] = validity; + + pthread_mutex_unlock(&clk_domain_mutex); +} + +int handle_clock_domain_event(void) +{ + union avb_msg_clock_domain msg; + unsigned int msg_type, msg_len; + int rc; + + msg_len = sizeof(union avb_msg_clock_domain); + rc = avb_control_receive(s_clk_handle, &msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) + goto error_control_receive; + + switch (msg_type) { + case AVB_MSG_CLOCK_DOMAIN_STATUS: + + if ((msg.status.status == GENAVB_CLOCK_DOMAIN_STATUS_LOCKED) || (msg.status.status == GENAVB_CLOCK_DOMAIN_STATUS_FREE_WHEELING)) { + INF("AVB_MSG_CLOCK_DOMAIN_STATUS - domain: %s, source_type: %s, status: %s, Setting clk domain validity to TRUE", get_genavb_clock_domain_t_name(msg.status.domain), get_genavb_clock_source_type_t_name(msg.status.source_type), get_genavb_clock_domain_status_t_name(msg.status.status)); + set_clk_domain_validity(msg.status.domain, 1); + } + + if (msg.status.status == GENAVB_CLOCK_DOMAIN_STATUS_UNLOCKED) { + INF("AVB_MSG_CLOCK_DOMAIN_STATUS - domain: %s, source_type: %s, status: %s, Setting clk domain validity to FALSE", get_genavb_clock_domain_t_name(msg.status.domain), get_genavb_clock_source_type_t_name(msg.status.source_type), get_genavb_clock_domain_status_t_name(msg.status.status)); + set_clk_domain_validity(msg.status.domain, 0); + } + + break; + + default: + break; + } + +error_control_receive: + return rc; +} + +void clock_domain_get_status(avb_clock_domain_t domain) +{ + struct avb_msg_clock_domain_get_status get_status; + unsigned int msg_len = sizeof(get_status); + avb_msg_type_t msg_type = AVB_MSG_CLOCK_DOMAIN_GET_STATUS; + int rc; + + get_status.domain = domain; + + rc = avb_control_send(s_clk_handle, msg_type, &get_status, msg_len); + if (rc != AVB_SUCCESS) + ERR("clock_domain_get_status (AVB_CTRL_CLOCK_DOMAIN) failed: %s\n", avb_strerror(rc)); +} + +int get_audio_clk_sync(avb_clock_domain_t clk_domain) +{ + if (clk_domain < GENAVB_CLOCK_DOMAIN_MAX) + return audio_clk_sync[clk_domain]; + + return -1; +} + +void set_audio_clk_sync(avb_clock_domain_t clk_domain, int clk_sync) +{ + if (clk_domain < GENAVB_CLOCK_DOMAIN_MAX) + audio_clk_sync[clk_domain] = clk_sync; + else + ERR("cannot set audio clock sync, clk_domain unknown"); +} + +static int clock_domain_set_source(struct avb_msg_clock_domain_set_source *set_source) +{ + struct avb_msg_clock_domain_response set_source_rsp; + unsigned int msg_len = sizeof(*set_source); + unsigned int msg_type = AVB_MSG_CLOCK_DOMAIN_SET_SOURCE; + int rc; + + rc = avb_control_send_sync(s_clk_handle, &msg_type, set_source, msg_len, &set_source_rsp, &msg_len, 1000); + if ((rc == AVB_SUCCESS) && (msg_type == AVB_MSG_CLOCK_DOMAIN_RESPONSE)) + rc = set_source_rsp.status; + + return rc; +} + +int clock_domain_set_source_internal(avb_clock_domain_t domain, + avb_clock_source_local_id_t local_id) +{ + struct avb_msg_clock_domain_set_source set_source; + + set_source.domain = domain; + set_source.source_type = AVB_CLOCK_SOURCE_TYPE_INTERNAL; + set_source.local_id = local_id; + + return clock_domain_set_source(&set_source); +} + +int clock_domain_set_source_stream(avb_clock_domain_t domain, + struct avb_stream_params *stream_params) +{ + struct avb_msg_clock_domain_set_source set_source; + + set_source.domain = domain; + set_source.source_type = AVB_CLOCK_SOURCE_TYPE_INPUT_STREAM; + memcpy(set_source.stream_id, stream_params->stream_id, 8); + + return clock_domain_set_source(&set_source); +} + +int clock_domain_set_role(media_clock_role_t role, avb_clock_domain_t domain, + struct avb_stream_params *stream_params) +{ + int rc = 0; + + set_audio_clk_sync(domain, 0); + + /* Master */ + if (role == MEDIA_CLOCK_MASTER) { + /* If possible try to configure with internal HW source */ + if (clock_domain_set_source_internal(domain, AVB_CLOCK_SOURCE_AUDIO_CLK) != AVB_SUCCESS) { + INF("cannot set clock source to internal audio clock"); + + /* Fallback */ + rc = clock_domain_set_source_internal(domain, AVB_CLOCK_SOURCE_PTP_CLK); + if (rc != AVB_SUCCESS) { + ERR("cannot set clock source to PTP based clock, rc = %d", rc); + goto exit; + } + INF("successfull fallback to PTP based clock"); + } + else { + set_audio_clk_sync(domain, 1); + INF("clock source setup to internal audio clock"); + } + } + /* Slave */ + else { + if (!stream_params) { + ERR("slave role requires a stream argument"); + goto exit; + } + + rc = clock_domain_set_source_stream(domain, stream_params); + if (rc != AVB_SUCCESS) { + ERR("clock_domain_set_source_stream error, rc = %d", rc); + goto exit; + } + + set_audio_clk_sync(domain, 1); + INF("clock source setup to stream ID" STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + } + +exit: + return rc; +} + +int clock_domain_init(struct avb_handle *s_avb_handle, int *clk_fd) +{ + int rc; + + rc = avb_control_open(s_avb_handle, &s_clk_handle, AVB_CTRL_CLOCK_DOMAIN); + if (rc != AVB_SUCCESS) { + ERR("avb_control_open(AVB_CTRL_CLOCK_DOMAIN) failed: %s\n", avb_strerror(rc)); + *clk_fd = -1; + return -1; + } + + *clk_fd = avb_control_rx_fd(s_clk_handle); + DBG("clock domain control handle: %p", s_clk_handle); + + return 0; +} + +int clock_domain_exit(void) +{ + avb_control_close(s_clk_handle); + + s_clk_handle = NULL; + + return 0; +} diff --git a/apps/linux/common/clock_domain.h b/apps/linux/common/clock_domain.h new file mode 100644 index 0000000..193be23 --- /dev/null +++ b/apps/linux/common/clock_domain.h @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _CLOCK_DOMAIN_H_ +#define _CLOCK_DOMAIN_H_ + +typedef enum { + MEDIA_CLOCK_MASTER, + MEDIA_CLOCK_SLAVE +}media_clock_role_t; + +const char* get_genavb_clock_domain_t_name(genavb_clock_domain_t type); + +const char* get_genavb_clock_source_type_t_name(genavb_clock_source_type_t type); + +const char* get_genavb_clock_source_local_id_t_name(genavb_clock_source_local_id_t type); + +const char* get_genavb_clock_domain_status_t_name(genavb_clock_domain_status_t type); + +int get_clk_domain_validity(avb_clock_domain_t clk_domain); + +void set_clk_domain_validity(avb_clock_domain_t clk_domain, int validity); + +int handle_clock_domain_event(void); + +void clock_domain_get_status(avb_clock_domain_t domain); + +int get_audio_clk_sync(avb_clock_domain_t clk_domain); + +void set_audio_clk_sync(avb_clock_domain_t clk_domain, int clk_sync); + +int clock_domain_set_source_stream(avb_clock_domain_t domain, struct avb_stream_params *stream_params); + +int clock_domain_set_source_internal(avb_clock_domain_t domain, avb_clock_source_local_id_t local_id); + +int clock_domain_set_role(media_clock_role_t role, avb_clock_domain_t domain, struct avb_stream_params *stream_params); + +int clock_domain_init(struct avb_handle *s_avb_handle, int *clk_fd); + +int clock_domain_exit(void); + +#endif /* _CLOCK_DOMAIN_H_ */ diff --git a/apps/linux/common/common.c b/apps/linux/common/common.c new file mode 100644 index 0000000..e096853 --- /dev/null +++ b/apps/linux/common/common.c @@ -0,0 +1,740 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "common.h" + +#define AVB_LISTENER_TS_LOG + +#define NUM_CONTROL_FDS 3 + +void print_stream_id(avb_u8 *id) +{ + printf("stream ID: %02x%02x%02x%02x%02x%02x%02x%02x\n", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7]); +} + +#if defined (AVB_LISTENER_TS_LOG) +unsigned char listener_first_buffer = 0; +#endif + +int listener_file_handler(struct avb_stream_handle *stream_h, int fd, unsigned int batch_size, struct stats *s) +{ + unsigned int event_len = EVENT_BUF_SZ; + unsigned char data_buf[DATA_BUF_SZ]; + struct avb_event event[EVENT_BUF_SZ]; + int nbytes; + int rc = 0; + uint64_t poll_time = 0; + uint64_t now = 0; + + /* + * read data from stack... + */ + nbytes = avb_stream_receive(stream_h, data_buf, batch_size, event, &event_len); + if (nbytes <= 0) { + if (nbytes < 0) + printf("avb_stream_receive() failed: %s\n", avb_strerror(nbytes)); + else + printf("avb_stream_receive() incomplete\n"); + + rc = nbytes; + goto exit; + } + +#if defined (AVB_LISTENER_TS_LOG) + if (listener_first_buffer == 0) { + uint64_t now; + + gettime_us(&now); + + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); + printf("!!!AVB listener: first buffer received @%" PRIu64 " (us)\n", now); + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); + listener_first_buffer = 1; + } +#endif + + if (event_len != 0) { + if (event[0].event_mask & AVTP_MEDIA_CLOCK_RESTART) + printf ("AVTP media clock restarted\n"); + + if (event[0].event_mask & AVTP_PACKET_LOST) + printf ("AVTP packet lost\n"); + } + /* + * ...and write to local file + */ + if (s) + gettime_us(&poll_time); + + rc = write(fd, data_buf, nbytes); + + if (s) { + if (poll_time) { + gettime_us(&now); + stats_update(s, (now - poll_time)); + } + } + + if (rc < nbytes) { + if (rc < 0) + printf("write() failed: %s\n", strerror(errno)); + else + printf("write() incomplete\n"); + + goto exit; + } + +exit: + return rc; +} + +void listener_stream_flush(struct avb_stream_handle *stream_h) +{ + unsigned int event_len = EVENT_BUF_SZ; + unsigned char data_buf[DATA_BUF_SZ]; + struct avb_event event[EVENT_BUF_SZ]; + int nbytes; + + while ((nbytes = avb_stream_receive(stream_h, data_buf, DATA_BUF_SZ, event, &event_len)) > 0) + printf("flushed %d bytes\n", nbytes); + +#if defined (AVB_LISTENER_TS_LOG) + listener_first_buffer = 0; +#endif +} + +void talker_stream_flush(struct avb_stream_handle *stream_h, struct avb_stream_params *params) +{ + struct avb_event event; + int rc; + + event.event_mask = AVTP_FLUSH; + if ( !params || (params->format.u.s.subtype_u.cvf.subtype != CVF_FORMAT_SUBTYPE_H264) ) + rc = avb_stream_send(stream_h, NULL, 0, &event, 1); + else + rc = avb_stream_h264_send(stream_h, NULL, 0, &event, 1); + + if (rc < 0) + printf("%s() avb_stream_send() failed: %s\n", __func__, avb_strerror(rc)); +} + +int file_read(int fd, unsigned char *buf, unsigned int len, unsigned int timeout) +{ + fd_set read_set; + struct timeval read_timeout; + int rc; + +retry: + rc = read(fd, buf, len); + if (rc <= 0) { + if (rc == 0) { + /* end of file */ + //printf("%s: end of file\n", __func__); + return 0; + } + + if (errno == EINTR) + goto retry; + + if (errno == EAGAIN) { + if (!timeout) + return 0; + + read_timeout.tv_sec = 0; + read_timeout.tv_usec = timeout; + + wait: + FD_ZERO(&read_set); + FD_SET(fd, &read_set); + + rc = select(fd + 1, &read_set, NULL, NULL, &read_timeout); + if (rc > 0) + goto retry; + else if (!rc) { + /* end of file */ + //printf("%s: timeout/end of file\n", __func__); + + return 0; + } + + if (errno == EINTR) + goto wait; + + printf("%s(), select() failed: %s\n", __func__, strerror(errno)); + + return -1; + } + + printf("%s(), read() failed: %s\n", __func__, strerror(errno)); + + return -1; + } + + return rc; +} + +int talker_file_handler(struct avb_stream_handle *stream_h, int fd, unsigned int batch_size, unsigned int flags) +{ + unsigned char data_buf[DATA_BUF_SZ]; + int nbytes; + int rc = 0; + int i; + + /* + * read data from local file... + */ + nbytes = read(fd, data_buf, batch_size); + + /* no more data to read, we are done*/ + if (nbytes <= 0) { + if (nbytes < 0) { + printf("read() failed: %s\n", strerror(errno)); + rc = nbytes; + } else { + lseek(fd, 0, SEEK_SET); + rc = 0; + } + + goto exit; + } + + /* + * AM824 data format requires a label in the unused part + * of the 32 bits (24 bits of data). + */ + if (flags & MEDIA_FLAGS_SET_AM824_LABEL_RAW) { + if (nbytes & 0x3) { + printf("nbytes: %d is not sample round\n", nbytes); + rc = -1; + goto exit; + } + + for (i = 0; i < nbytes; i+= 4) + *(avb_u8 *)(data_buf + i) = AM824_LABEL_RAW; + } + + /* + * ...and write to avb stack + */ + rc = avb_stream_send(stream_h, data_buf, nbytes, NULL, 0); + if (rc != nbytes) { + if (rc < 0) + printf("avb_stream_send() failed: %s\n", avb_strerror(rc)); + else + printf("avb_stream_send() incomplete\n"); + + goto exit; + } + +exit: + return rc; +} + +void media_stream_poll_set(const struct media_stream *stream, int enable) +{ + struct media_thread *thread = stream->thread; + + /* FIXME this needs to be different for gui/QT applications */ + + if (enable) + thread->poll_fd[stream->index + NUM_CONTROL_FDS].fd = stream->fd; + else + thread->poll_fd[stream->index + NUM_CONTROL_FDS].fd = -1; + + if (stream->params.direction == AVTP_DIRECTION_LISTENER) + thread->poll_fd[stream->index + NUM_CONTROL_FDS].events = POLLIN; + else + thread->poll_fd[stream->index + NUM_CONTROL_FDS].events = POLLOUT; + +// printf("%s: stream(%d) %s fd(%d)\n", __func__, stream->index, enable ? "enabled" : "disabled", thread->poll_fd[stream->index + 2].fd); +} + +static void stream_connect_handler(const struct media_control *ctrl, const struct avb_stream_params *stream_params, unsigned int avdecc_stream_index) +{ + struct media_thread *thread = ctrl->data; + struct media_stream *stream; + int rc; + + if (avdecc_stream_index >= thread->max_supported_streams) { + printf("%s stream_index(%d) out of bounds(%d)\n", __func__, avdecc_stream_index, thread->max_supported_streams); + goto err_stream_index; + } + + if (avdecc_stream_index < 2) /*This is for stream indexes 0 : 61883_6 audio and 1: 61883_4 video*/ + stream = &thread->stream[avdecc_stream_index]; + else /*This is for stream index 3 : H264 video*/ + stream = &thread->stream[0]; + + if (stream->state & MEDIA_STREAM_STATE_CONNECTED) + return; + + memcpy(&stream->params, stream_params, sizeof(struct avb_stream_params)); + + if (ctrl->config_handler) + ctrl->config_handler(stream); + + rc = avb_stream_create(thread->avb_h, &stream->handle, &stream->params, &stream->batch_size, stream->flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto err_stream_create; + } + + /* + * retrieve the file descriptor associated to the stream + */ + + stream->fd = avb_stream_fd(stream->handle); + if (stream->fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(stream->fd)); + rc = -1; + goto err_stream_fd; + } + + media_stream_poll_set(stream, 1); + + if (ctrl->connect_handler) { + if (ctrl->connect_handler(stream) < 0) { + rc = -1; + goto err_connect; + } + } + + stream->state |= MEDIA_STREAM_STATE_CONNECTED; + + printf("stream(%d) connect success\n", stream->index); + + printf("Configured AVB batch size (bytes): %d\n", stream->batch_size); + + return; + +err_connect: +err_stream_fd: + avb_stream_destroy(stream->handle); + +err_stream_create: +err_stream_index: + + printf("stream(%d) connect failed\n", avdecc_stream_index); + + return; +} + +static void stream_disconnect_handler(struct media_control *ctrl, unsigned int avdecc_stream_index) +{ + struct media_thread *thread = ctrl->data; + struct media_stream *stream; + + if (avdecc_stream_index >= thread->max_supported_streams) { + printf("%s stream_index(%d) out of bounds(%d)\n", __func__, avdecc_stream_index, thread->max_supported_streams); + goto err_stream_index; + } + + if (avdecc_stream_index < 2) /*This is for stream indexes 0 : 61883_6 audio and 1: 61883_4 video*/ + stream = &thread->stream[avdecc_stream_index]; + else /*This is for stream index 3 : H264 video*/ + stream = &thread->stream[0]; + + if (!(stream->state & MEDIA_STREAM_STATE_CONNECTED)) + return; + + stream->state &= ~MEDIA_STREAM_STATE_CONNECTED; + + media_stream_poll_set(stream, 0); + + if (ctrl->disconnect_handler) + ctrl->disconnect_handler(stream); + + avb_stream_destroy(stream->handle); + +err_stream_index: + return; +} + +int media_control_handler(struct media_control *ctrl) +{ + union avb_media_stack_msg msg; + unsigned int msg_type, msg_len; + int rc; + + msg_len = sizeof(union avb_media_stack_msg); + rc = avb_control_receive(ctrl->handle, &msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) { + printf("avb_control_receive() failed, %s\n", avb_strerror(rc)); + rc = -1; + goto err_rx; + } + + switch (msg_type) { + case AVB_MSG_MEDIA_STACK_CONNECT: + printf("event: AVB_MSG_MEDIA_STACK_CONNECT\n"); + + stream_connect_handler(ctrl, &msg.media_stack_connect.stream_params, msg.media_stack_connect.stream_index); + + break; + + case AVB_MSG_MEDIA_STACK_DISCONNECT: + printf("event: AVB_MSG_MEDIA_STACK_DISCONNECT\n"); + + stream_disconnect_handler(ctrl, msg.media_stack_disconnect.stream_index); + + break; + + default: + printf("event: UNKNOWN\n"); + break; + } + + return 0; + +err_rx: + return rc; +} + +/** Generic AECP AEM SET_CONTROL command handler. + * + * Simply calls the application specific aem_set_control_handler. + * \return AECP_AEM return code, according to IEEE 1722.1-2013 table 7.126 (AECP_AEM_SUCCESS in case of success). + * \param controlled avdecc_controlled context that received the command. + * \param pdu Pointer to received AECP AEM PDU. + */ +int aem_set_control_handler(struct avdecc_controlled *controlled, struct aecp_aem_pdu *pdu) +{ + struct aecp_aem_set_get_control_pdu *set_control_cmd = (struct aecp_aem_set_get_control_pdu *)(pdu + 1); + avb_u16 ctrl_index = ntohs(set_control_cmd->descriptor_index); + void *ctrl_value = set_control_cmd + 1; + int rc = AECP_AEM_NOT_IMPLEMENTED; + + if (controlled->aem_set_control_handler) + rc = controlled->aem_set_control_handler(controlled, ctrl_index, ctrl_value); + + return rc; +} + +/** Generic AECP AEM REMOVE_AUDIO_MAPPINGS command handler. + * + * Simply calls the application specific aem_remove_audio_mappings_handler + * Remove audio_mappings from a stream port input/output, stored on the app side + * \return AECP_AEM return code, according to IEEE 1722.1-2013 table 7.126 (AECP_AEM_SUCCESS in case of success). + * \param controlled avdecc_controlled context that received the command. + * \param pdu Pointer to received AECP AEM PDU. + */ +int aem_remove_audio_mappings_handler(struct avdecc_controlled *controlled, struct aecp_aem_pdu *pdu) +{ + struct aecp_aem_modify_audio_mappings_cmd_pdu *audio_map_cmd = (struct aecp_aem_modify_audio_mappings_cmd_pdu *)(pdu + 1); + avb_u16 mappings_count = ntohs(audio_map_cmd->number_of_mappings); + avb_u16 descriptor_index = ntohs(audio_map_cmd->descriptor_index); + avb_u16 descriptor_type = ntohs(audio_map_cmd->descriptor_type); + void *audio_mappings = audio_map_cmd + 1; + int rc = AECP_AEM_NOT_IMPLEMENTED; + + if (controlled->aem_remove_audio_mappings_handler) + rc = controlled->aem_remove_audio_mappings_handler(descriptor_index, descriptor_type, mappings_count, audio_mappings); + + return rc; +} + +/** Generic AECP AEM ADD_AUDIO_MAPPINGS command handler. + * + * Simply calls the application specific aem_add_audio_mappings_handler. + * Add audio_mappings to a stream port input/output, stored on the app side + * \return AECP_AEM return code, according to IEEE 1722.1-2013 table 7.126 (AECP_AEM_SUCCESS in case of success). + * \param controlled avdecc_controlled context that received the command. + * \param pdu Pointer to received AECP AEM PDU. + */ +int aem_add_audio_mappings_handler(struct avdecc_controlled *controlled, struct aecp_aem_pdu *pdu) +{ + struct aecp_aem_modify_audio_mappings_cmd_pdu *audio_map_cmd = (struct aecp_aem_modify_audio_mappings_cmd_pdu *)(pdu + 1); + avb_u16 mappings_count = ntohs(audio_map_cmd->number_of_mappings); + avb_u16 descriptor_index = ntohs(audio_map_cmd->descriptor_index); + avb_u16 descriptor_type = ntohs(audio_map_cmd->descriptor_type); + void *audio_mappings = audio_map_cmd + 1; + int rc = AECP_AEM_NOT_IMPLEMENTED; + + if (controlled->aem_add_audio_mappings_handler) + rc = controlled->aem_add_audio_mappings_handler(descriptor_index, descriptor_type, mappings_count, audio_mappings); + + return rc; +} + +/** Generic AECP AEM GET_AUDIO_MAP command handler. + * + * Simply calls the application specific aem_get_audio_mappings_handler. + * Retrive the stored audio mappings from the app for a given stream port input/output + * Prepare the response using the GET_AUDIO_MAP command received and add the retrived audio mappings to the response + * \return AECP_AEM return code, according to IEEE 1722.1-2013 table 7.126 (AECP_AEM_SUCCESS in case of success). + * \param controlled avdecc_controlled context that received the command. + * \param[in,out] pdu Pointer to received AECP AEM CMD PDU and points to AECP RSP PDU to be sent on exit + * \param[out] pdu_len Length of the output AECP AEM RSP PDU. + */ +int aem_get_audio_mappings_handler(struct avdecc_controlled *controlled, struct aecp_aem_pdu *pdu, unsigned int *pdu_len) +{ + struct aecp_aem_get_audio_map_rsp_pdu *audio_map_cmd = (struct aecp_aem_get_audio_map_rsp_pdu *)(pdu + 1); + struct aecp_aem_get_audio_map_rsp_pdu *audio_map_rsp = (struct aecp_aem_get_audio_map_rsp_pdu *)(pdu + 1); + avb_u16 descriptor_index = ntohs(audio_map_cmd->descriptor_index); + avb_u16 descriptor_type = ntohs(audio_map_cmd->descriptor_type); + avb_u16 map_index = ntohs(audio_map_cmd->map_index); + void *audio_mappings = audio_map_rsp + 1; + avb_u16 mappings_count, number_of_maps; + int rc = AECP_AEM_NOT_IMPLEMENTED; + + audio_map_rsp->number_of_maps = htons(0); + audio_map_rsp->number_of_mappings = htons(0); + audio_map_rsp->reserved = htons(0); + + /* Set the AECP Response PDU length to match the aecp_aem_get_audio_map_rsp_pdu to be sent back */ + *pdu_len = sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_get_audio_map_rsp_pdu); + + if (controlled->aem_get_audio_mappings_handler) { + rc = controlled->aem_get_audio_mappings_handler(descriptor_index, descriptor_type, map_index, &number_of_maps, &mappings_count, audio_mappings); + + if (rc == AECP_AEM_SUCCESS) { + audio_map_rsp->number_of_maps = htons(number_of_maps); + audio_map_rsp->number_of_mappings = htons(mappings_count); + + *pdu_len += (mappings_count * (sizeof(struct aecp_aem_get_audio_map_mappings_format))); + } + } + + return rc; +} + +int avdecc_controlled_handler(struct avdecc_controlled *controlled, unsigned int events) +{ + union avb_controlled_msg msg; + struct aecp_aem_pdu *pdu = (struct aecp_aem_pdu *)msg.aecp.buf; + unsigned int msg_len, pdu_len; + avb_msg_type_t msg_type; + avb_u16 cmd_type; + avb_u8 status; + int rc = 0; + + (void)events; + + msg_len = sizeof(struct avb_aecp_msg); + rc = avb_control_receive(controlled->handle, &msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) { + printf("%s: WARNING: Got error message %d(%s) while trying to receive AECP command.\n", __func__, rc, avb_strerror(rc)); + rc = -1; + goto receive_error; + } + + switch (msg_type) { + case AVB_MSG_AECP: + cmd_type = AECP_AEM_GET_CMD_TYPE(pdu); + if (msg.aecp.msg_type == AECP_AEM_COMMAND) { + printf("%s: Received AVB AECP command %d\n", __func__, cmd_type); + + switch (cmd_type) { + case AECP_AEM_CMD_SET_CONTROL: + status = aem_set_control_handler(controlled, pdu); + break; + + case AECP_AEM_CMD_START_STREAMING: + printf("%s: Received AVB AECP command : AECP_AEM_CMD_START_STREAMING ... Do nothing for the moment\n", __func__); + status = AECP_AEM_SUCCESS; + break; + + case AECP_AEM_CMD_STOP_STREAMING: + printf("%s: Received AVB AECP command : AECP_AEM_CMD_STOP_STREAMING ... Do nothing for the moment\n", __func__); + status = AECP_AEM_SUCCESS; + break; + + case AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS: + status = aem_remove_audio_mappings_handler(controlled, pdu); + break; + + case AECP_AEM_CMD_ADD_AUDIO_MAPPINGS: + status = aem_add_audio_mappings_handler(controlled, pdu); + break; + + case AECP_AEM_CMD_GET_AUDIO_MAP: + status = aem_get_audio_mappings_handler(controlled, pdu, &pdu_len); + msg.aecp.len = pdu_len; + break; + + default: + status = AECP_AEM_NOT_IMPLEMENTED; + printf("%s: Command type (0x%x) not handled in this app, skip\n", __func__, cmd_type); + break; + } + + /* Multiple apps can be listening/handling controlled commands, if this app do not handle it, do not answer the stack and let other apps handle that. */ + if (status == AECP_AEM_NOT_IMPLEMENTED) + break; + + msg.aecp.msg_type = AECP_AEM_RESPONSE; + msg.aecp.status = status; + + rc = avb_control_send(controlled->handle, msg_type, &msg, msg_len); + if (rc != AVB_SUCCESS) { + printf("%s: WARNING: Got error message %d(%s) while trying to send response to AECP command.\n", __func__, rc, avb_strerror(rc)); + rc = -1; + } + } else { + printf("%s: Error: Received AVB AECP response %d but only expecting commands.\n", __func__, cmd_type); + rc = -1; + } + + break; + default: + printf("%s: WARNING: Received unsupported AVDECC message type %d.\n", __func__, msg_type); + rc = -1; + break; + } + +receive_error: + return rc; +} + + +int media_thread_loop(struct media_thread *thread) +{ + struct media_stream *stream; + int ready, n; + int i, rc = 0; + int timer_fd = -1; + struct itimerspec timer; + + if (thread->timeout_handler) { + timer_fd = timerfd_create(CLOCK_REALTIME, 0); + if (timer_fd < 0) { + printf("%s timerfd_create() failed %s\n", __func__, strerror(errno)); + goto err; + } + + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_nsec = thread->timeout_ms * 1000000; + timer.it_value.tv_sec = 0; + timer.it_value.tv_nsec = thread->timeout_ms * 1000000; + + if (timerfd_settime(timer_fd, 0, &timer, NULL) < 0) { + printf("%s timerfd_settime() failed %s\n", __func__, strerror(errno)); + goto err; + } + } + + thread->poll_fd[0].fd = timer_fd; + thread->poll_fd[0].events = POLLIN; + + if (thread->ctrl.handle) { + thread->poll_fd[1].fd = avb_control_rx_fd(thread->ctrl.handle); + thread->poll_fd[1].events = POLLIN; + thread->ctrl.data = thread; + } else + thread->poll_fd[1].fd = -1; + + if (thread->controlled.handle) { + thread->poll_fd[2].fd = avb_control_rx_fd(thread->controlled.handle); + thread->poll_fd[2].events = POLLIN; + thread->controlled.data = thread; + } else + thread->poll_fd[2].fd = -1; + + for (i = 0; i < thread->num_streams; i++) { + stream = &thread->stream[i]; + + stream->index = i; + stream->thread = thread; + + media_stream_poll_set(stream, 0); + } + + if (thread->init_handler) { + rc = thread->init_handler(thread); + if (rc < 0) + goto err; + } + + while (1) { + if (thread->signal_handler && thread->signal_handler(thread)) { + printf("signal_handler() exiting\n"); + break; + } + + ready = poll(thread->poll_fd, thread->num_streams + NUM_CONTROL_FDS, -1); + if (ready < 0) { + if (errno == EINTR) { + continue; + } + + printf("thread(%p): poll() failed: %s\n", thread, strerror(errno)); + + break; + } + + if (!ready) + continue; + + n = 0; + + if (thread->poll_fd[0].revents & POLLIN) { + char tmp[8]; + int ret; + + ret = read(thread->poll_fd[0].fd, tmp, 8); + if (ret < 0) + printf("thread(%p): timer_fd read() failed: %s\n", thread, strerror(errno)); + + if (thread->timeout_handler && thread->timeout_handler(thread)) { + printf("timeout_handler() exiting\n"); + break; + } + + n++; + } + + if (thread->poll_fd[1].revents & POLLIN) { + if (media_control_handler(&thread->ctrl) < 0) + break; + + n++; + } + + if (thread->poll_fd[2].revents & POLLIN) { + if (avdecc_controlled_handler(&thread->controlled, POLLIN) < 0) + break; + + n++; + } + + for (i = 0; (i < thread->num_streams) && (n < ready); i++) { + if ((thread->poll_fd[i + NUM_CONTROL_FDS].fd > 0) && (thread->poll_fd[i + NUM_CONTROL_FDS].revents & (POLLIN | POLLOUT))) { + stream = &thread->stream[i]; + + thread->data_handler(stream); + + n++; + } + } + } + + if (thread->exit_handler) + rc = thread->exit_handler(thread); + +err: + if (timer_fd > 0) + close(timer_fd); + + return rc; +} diff --git a/apps/linux/common/common.h b/apps/linux/common/common.h new file mode 100644 index 0000000..560d6c8 --- /dev/null +++ b/apps/linux/common/common.h @@ -0,0 +1,140 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include + +#include +#include +#include "stats.h" +#include "time.h" + +#define K 1024 +#define DATA_BUF_SZ (16*K) +#define EVENT_BUF_SZ (K) + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_STREAMS 4 + +#define MEDIA_STREAM_STATE_CONNECTED (1 << 0) + +#define MEDIA_FLAGS_SET_AM824_LABEL_RAW (1 << 0) +#define MEDIA_FLAGS_ALSA (1 << 1) +#define MEDIA_FLAGS_FILE (1 << 2) + +#define AM824_LABEL_RAW 0x40 + +#define CFG_TALKER_LATENCY_NS 1000000 // Additional fixed playback latency in ns +#define min(a,b) ((a)<(b)?(a):(b)) +#define max(a,b) ((a)>(b)?(a):(b)) + +/** + * Generic media stream context + */ +struct media_stream { + int index; + unsigned int state; + struct avb_stream_params params; + struct avb_stream_handle *handle; + int fd; + unsigned int batch_size; + unsigned int flags; + + struct media_thread *thread; + + void *data; /**< Media application stream private data */ +}; + +/** + * Generic media control handler + */ +struct media_control { + void *data; + struct avb_control_handle *handle; /** AVB control handle. Can be NULL */ + int (*config_handler)(struct media_stream *); /**< Media application stream configuration callback. Called when a connect event is received but before the avb stream is created. + Used to adjust stream create parameters. Can be NULL */ + int (*connect_handler)(struct media_stream *); /**< Media application stream connect callback. Called once the avb stream has been created. Can be NULL. */ + int (*disconnect_handler)(struct media_stream *); /**< Media application stream disconnect callback. Called when a disconnect event is received, before destroying the avb stream. Can be NULL. */ +}; + + +/** + * Generic AVDECC listener/talker handler + */ +struct avdecc_controlled { + void *data; + struct avb_control_handle *handle; + int (*aem_set_control_handler)(struct avdecc_controlled *, avb_u16, void *); + int (*aem_get_audio_mappings_handler)(avb_u16, avb_u16, avb_u16, avb_u16 *, avb_u16 *, void *); + int (*aem_add_audio_mappings_handler)(avb_u16, avb_u16, avb_u16, void *); + int (*aem_remove_audio_mappings_handler)(avb_u16, avb_u16, avb_u16, void *); +}; + +/** + * Generic media thread context + * Must be initialized before calling media_thread_loop() + */ +struct media_thread { + int (*init_handler)(struct media_thread *); /**< Media application initialization callback. Called once at the start of media_thread_loop(). + Used for application thread context initialization. Can be NULL. */ + int (*exit_handler)(struct media_thread *); /**< Media application exit callback. Called once at the end of media_thread_loop(). + Used for application thread context cleanup. Can be NULL. */ + int (*timeout_handler)(struct media_thread *); /**< Media application timeout callback. Called at each timeout from media_thread_loop(). + Used for application thread periodic tasks. Can be NULL. */ + int (*signal_handler)(struct media_thread *); /**< Media application signal callback. Called at each iteration of media_thread_loop(). + Used to handle application thread signals. Can be NULL. */ + + int (*data_handler)(struct media_stream *); /**< Media application data callback. Called each time data can be written/read from the stream file descriptor. */ + + struct avb_handle *avb_h; /**< AVB handle */ + + struct media_stream stream[MAX_STREAMS]; + int num_streams; /**< Number of active streams handled by this thread */ + unsigned int max_supported_streams; /**< Number of maximum stream index that can be handled by this thread */ + + struct pollfd poll_fd[MAX_STREAMS + 2]; + + struct media_control ctrl; + + struct avdecc_controlled controlled; + + unsigned int timeout_ms; /**< media_thread_loop() timeout period. Only used if timeout_handler() is non NULL */ + + void *data; /**< Media application thread private data */ +}; + +/** + * Enables/disables media stream file descriptor polling in media_thread_loop() + */ +void media_stream_poll_set(const struct media_stream *stream, int enable); + +/** + * Generic media thread main loop handler + */ +int media_thread_loop(struct media_thread *thread); + +void print_stream_id(avb_u8 *id); +int listener_file_handler(struct avb_stream_handle *stream_h, int fd, unsigned int batch_size, struct stats *s); +void listener_stream_flush(struct avb_stream_handle *stream_h); +int talker_file_handler(struct avb_stream_handle *stream_h, int fd, unsigned int batch_size, unsigned int flags); +void talker_stream_flush(struct avb_stream_handle *stream_h, struct avb_stream_params *params); +int file_read(int fd, unsigned char *buf, unsigned int len, unsigned int timeout); +int avdecc_controlled_handler(struct avdecc_controlled *controlled, unsigned int events); +int parse_binding_params_file(const char *binding_file, struct genavb_msg_media_stack_bind *binding_params, unsigned int size); +int nvram_update_entry(const char *binding_file_name, avb_u64 entity_id, avb_u16 listener_stream_index, avb_u64 talker_entity_id, avb_u16 talker_stream_index, avb_u64 controller_entity_id, avb_u16 started); +int avdecc_listener_stream_bind_send_ipc(struct avb_control_handle *s_avdecc_handle, struct genavb_msg_media_stack_bind *binding_params); + +#endif /* _COMMON_H_ */ + +#ifdef __cplusplus +} +#endif diff --git a/apps/linux/common/crf_stream.c b/apps/linux/common/crf_stream.c new file mode 100644 index 0000000..9b8d1ae --- /dev/null +++ b/apps/linux/common/crf_stream.c @@ -0,0 +1,215 @@ +/* + * Copyright 2017-2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "log.h" +#include "avb_stream.h" +#include "crf_stream.h" +#include "clock_domain.h" +#include "msrp.h" + +aar_crf_stream_t *crf_stream_get(unsigned int domain_index) +{ + if (domain_index < MAX_CLOCK_DOMAIN) { + return &g_crf_streams[domain_index]; + } + return NULL; +} + +int crf_stream_create(unsigned int domain_index) +{ + int rc; + aar_crf_stream_t *crf; + + struct avb_handle *avb_handle = avbstream_get_avb_handle(); + + crf = crf_stream_get(domain_index); + + if (!crf) { + ERR("CRF stream not found for domain_index (%u)\n", domain_index); + goto err_crf; + } + + if (crf->stream_handle) { + ERR("CRF stream handle already created"); + goto err_crf; + } + + INF("stream_id: " STREAM_STR_FMT, STREAM_STR(crf->stream_params.stream_id)); + INF("dst_mac: " MAC_STR_FMT, MAC_STR(crf->stream_params.dst_mac)); + + crf->cur_batch_size = avbstream_batch_size(crf->batch_size_ns, &crf->stream_params); + + /* The app is not aware of which SR classes are enabled, so different values are tried */ + rc = avb_stream_create(avb_handle, &crf->stream_handle, &crf->stream_params, &crf->cur_batch_size, 0); + if (rc != AVB_SUCCESS) { + crf->stream_params.stream_class = SR_CLASS_C; + + rc = avb_stream_create(avb_handle, &crf->stream_handle, &crf->stream_params, &crf->cur_batch_size, 0); + if (rc != AVB_SUCCESS) { + crf->stream_params.stream_class = SR_CLASS_E; + + rc = avb_stream_create(avb_handle, &crf->stream_handle, &crf->stream_params, &crf->cur_batch_size, 0); + if (rc != AVB_SUCCESS) { + crf->stream_params.stream_class = SR_CLASS_A; + + rc = avb_stream_create(avb_handle, &crf->stream_handle, &crf->stream_params, &crf->cur_batch_size, 0); + if (rc != AVB_SUCCESS) { + crf->stream_params.stream_class = SR_CLASS_D; + + rc = avb_stream_create(avb_handle, &crf->stream_handle, &crf->stream_params, &crf->cur_batch_size, 0); + if (rc != AVB_SUCCESS) { + ERR("create CRF stream failed, err %d", rc); + goto err_avb; + } + } + } + } + } + + if (crf->stream_params.direction == AVTP_DIRECTION_TALKER) { + rc = msrp_talker_register(&crf->stream_params); + if (rc != AVB_SUCCESS) { + ERR("msrp_talker_register error, rc = %d", rc); + goto err_msrp; + } + } + else { + rc = msrp_listener_register(&crf->stream_params); + if (rc != AVB_SUCCESS) { + ERR("msrp_talker_register error, rc = %d", rc); + goto err_msrp; + } + } + + return 0; + +err_msrp: + avb_stream_destroy(crf->stream_handle); + +err_avb: +err_crf: + return -1; +} + +int crf_stream_destroy(unsigned int domain_index) +{ + int rc; + aar_crf_stream_t *crf; + + crf = crf_stream_get(domain_index); + + if (!crf) { + ERR("CRF stream not found for domain_index (%u)\n", domain_index); + rc = -1; + goto exit; + } + + if (!crf->stream_handle) { + rc = 0; + goto exit; + } + + rc = avb_stream_destroy(crf->stream_handle); + if (rc != AVB_SUCCESS) + ERR("avb_stream_destroy error, rc = %d", rc); + + crf->stream_handle = NULL; + + if (crf->stream_params.direction == AVTP_DIRECTION_TALKER) + msrp_talker_deregister(&crf->stream_params); + else + msrp_listener_deregister(&crf->stream_params); + +exit: + return rc; +} + +int crf_connect(media_clock_role_t role, unsigned int domain_index, struct avb_stream_params *stream_params) +{ + struct avb_handle *avb_handle = avbstream_get_avb_handle(); + aar_crf_stream_t *crf; + int rc; + + if (stream_params) + stream_params->clock_domain = crf_get_clock_domain(domain_index); + + crf = crf_stream_get(domain_index); + if (!crf) { + ERR("CRF stream not found for domain_index (%u)\n", domain_index); + goto err; + } + + if (crf->stream_handle) { + ERR("CRF stream already connected for domain_index (%u)", domain_index); + goto err; + } + + rc = clock_domain_set_role(role, crf_get_clock_domain(domain_index), stream_params); + if (rc != AVB_SUCCESS) { + ERR("clock_domain_set_role failed, rc = %d", rc); + goto err; + } + + /* Update CRF stream params */ + if (stream_params) + memcpy(&crf->stream_params, stream_params, sizeof(*stream_params)); + + crf->cur_batch_size = avbstream_batch_size(crf->batch_size_ns, &crf->stream_params); + + rc = avb_stream_create(avb_handle, &crf->stream_handle, &crf->stream_params, &crf->cur_batch_size, 0); + if (rc != AVB_SUCCESS) { + ERR("avb_stream_create failed, rc = %d", rc); + goto err; + } + + return 0; + +err: + return -1; +} + +void crf_disconnect(unsigned int domain_index) +{ + aar_crf_stream_t *crf; + + crf = crf_stream_get(domain_index); + if (!crf) { + ERR("CRF stream not found for domain_index (%u)\n", domain_index); + return; + } + + if (!crf->stream_handle) { + ERR("CRF stream already disconnected for domain_index (%u)", domain_index); + return; + } + + if (avb_stream_destroy(crf->stream_handle) != AVB_SUCCESS) + ERR("avb_stream_destroy error"); + + crf->stream_handle = NULL; +} + +int crf_configure(unsigned int domain_index, avtp_direction_t direction, unsigned int flags) +{ + aar_crf_stream_t *crf; + + crf = crf_stream_get(domain_index); + + if (!crf) { + ERR("CRF stream not found for domain_index (%u)\n", domain_index); + goto err_crf; + } + + crf->stream_params.direction = direction; + crf->stream_params.flags = flags; + + return 0; + +err_crf: + return -1; +} diff --git a/apps/linux/common/crf_stream.h b/apps/linux/common/crf_stream.h new file mode 100644 index 0000000..bfa1a5c --- /dev/null +++ b/apps/linux/common/crf_stream.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017-2018, 2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _CRF_STREAM_H_ +#define _CRF_STREAM_H_ + +#include "clock_domain.h" + +#define MAX_CLOCK_DOMAIN 1 + +typedef struct { + struct avb_stream_params stream_params; + struct avb_stream_handle *stream_handle; + + unsigned int batch_size_ns; + unsigned int cur_batch_size; + + bool is_static_config; /* single static stream configuration for the CRF stream supported */ +} aar_crf_stream_t; + +extern aar_crf_stream_t g_crf_streams[]; + +static inline unsigned int crf_get_clock_domain(unsigned int domain_index) +{ + return (domain_index + AVB_CLOCK_DOMAIN_0); +} + +aar_crf_stream_t *crf_stream_get(unsigned int domain_index); +int crf_stream_create(unsigned int domain_index); +int crf_stream_destroy(unsigned int domain_index); +int crf_connect(media_clock_role_t role, unsigned int domain_index, struct avb_stream_params *stream_params); +void crf_disconnect(unsigned int domain_index); +int crf_configure(unsigned int domain_index, avtp_direction_t direction, unsigned int flags); + +#endif /* _CLOCK_DOMAIN_H_ */ diff --git a/apps/linux/common/file_buffer.c b/apps/linux/common/file_buffer.c new file mode 100644 index 0000000..d0aad76 --- /dev/null +++ b/apps/linux/common/file_buffer.c @@ -0,0 +1,131 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include + +#include "file_buffer.h" +#include "common.h" + +void file_buffer_init(struct file_buffer *buf, unsigned int readers) +{ + unsigned int i; + + buf->w_offset = 0; + + if (readers > FILE_BUFFER_READERS) + readers = FILE_BUFFER_READERS; + + for (i = 0; i < readers; i++) + buf->r_offset[i] = 0; + + buf->readers = readers; + buf->eof = 0; + buf->size = FILE_BUFFER_SIZE; +} + + +unsigned int file_buffer_available(struct file_buffer *buf, int reader) +{ + if (buf->w_offset >= buf->r_offset[reader]) + return buf->w_offset - buf->r_offset[reader]; + else + return (buf->w_offset + buf->size) - buf->r_offset[reader]; +} + +void file_buffer_read(struct file_buffer *buf, int reader, unsigned int len) +{ + buf->r_offset[reader] += len; + if (buf->r_offset[reader] >= buf->size) + buf->r_offset[reader] -= buf->size; +} + +void *file_buffer_buf(struct file_buffer *buf, int reader) +{ + return buf->buf + buf->r_offset[reader]; +} + +unsigned int file_buffer_available_wrap(struct file_buffer *buf, int reader) +{ + if (buf->w_offset >= buf->r_offset[reader]) + return buf->w_offset - buf->r_offset[reader]; + else + return buf->size - buf->r_offset[reader]; +} + + +int file_buffer_empty(struct file_buffer *buf, int reader) +{ + return !file_buffer_available(buf, reader); +} + +unsigned int file_buffer_free(struct file_buffer *buf, int reader) +{ + return buf->size - file_buffer_available(buf, reader) - 1; +} + +int file_buffer_write(struct file_buffer *buf, unsigned int fd, unsigned int timeout) +{ + unsigned int len, len_now; + unsigned int written = 0; + int rc; + + if (buf->eof) + return 0; + +// printf("%s: %u %u %u %u\n", __func__, buf->w_offset, buf->r_offset[0], buf->r_offset[1], buf->eof); + + len = file_buffer_free(buf, 0); + if (len < buf->size / 16) + return 0; + + if (buf->readers > 1) { + if (len > file_buffer_free(buf, 1)) + len = file_buffer_free(buf, 1); + } + + len_now = len; + if ((buf->w_offset + len_now) > buf->size) { + len_now = buf->size - buf->w_offset; + + rc = file_read(fd, buf->buf + buf->w_offset, len_now, timeout); + if (rc <= 0) + return rc; + + buf->w_offset += rc; + if (buf->w_offset >= buf->size) + buf->w_offset = 0; + + written += rc; + + if (rc < (int)len_now) + return written; + + len_now = len - rc; + } + + if (!len_now) + return written; + + rc = file_read(fd, buf->buf + buf->w_offset, len_now, timeout); + if (rc <= 0) { + if (!rc && written) + return written; + + return rc; + } + + written += rc; + + buf->w_offset += rc; + if (buf->w_offset >= buf->size) + buf->w_offset = 0; + + return written; +} diff --git a/apps/linux/common/file_buffer.h b/apps/linux/common/file_buffer.h new file mode 100644 index 0000000..69147f8 --- /dev/null +++ b/apps/linux/common/file_buffer.h @@ -0,0 +1,53 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _FILE_BUFFER_H_ +#define _FILE_BUFFER_H_ + +#define PES_SIZE 188 + +#define FILE_BUFFER_SIZE (1024 * 10 * PES_SIZE) +#define FILE_BUFFER_READERS 2 + +#ifdef __cplusplus +extern "C" { +#endif + +struct file_buffer { + unsigned char buf[FILE_BUFFER_SIZE]; + unsigned int size; + + unsigned int w_offset; + + unsigned int r_offset[FILE_BUFFER_READERS]; + + unsigned int eof; + unsigned int readers; +}; + +/* initialize a buffer that can handle "readers" number of readers */ +void file_buffer_init(struct file_buffer *buf, unsigned int readers); + +unsigned int file_buffer_available(struct file_buffer *buf, int reader); +unsigned int file_buffer_available_wrap(struct file_buffer *buf, int reader); + +/* return true if buffer is empty */ +int file_buffer_empty(struct file_buffer *buf, int reader); + +unsigned int file_buffer_free(struct file_buffer *buf, int reader); + +void *file_buffer_buf(struct file_buffer *buf, int reader); + +void file_buffer_read(struct file_buffer *buf, int reader, unsigned int len); + +int file_buffer_write(struct file_buffer *buf, unsigned int fd, unsigned int timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* _FILE_BUFFER_H_ */ diff --git a/apps/linux/common/gst_pipeline_definitions.c b/apps/linux/common/gst_pipeline_definitions.c new file mode 100644 index 0000000..7b955f2 --- /dev/null +++ b/apps/linux/common/gst_pipeline_definitions.c @@ -0,0 +1,690 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020, 2022-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "gstreamer.h" +#include "gst_pipeline_definitions.h" + + +/* Talker pipelines */ + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4 = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue max-size-buffers=0 max-size-time=0 ! audio/mpeg ! mux." + " demux. ! queue max-size-buffers=0 max-size-time=0 ! video/x-h264 ! h264parse config-interval=1 ! queue max-size-buffers=0 max-size-time=0 ! mux." + " mpegtsmux name=mux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 1, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6 = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue max-size-buffers=0 max-size-time=0 ! audio/mpeg ! aacparse ! tee name=t" /* audio */ + " t. ! queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioresample ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink1" + " t. ! queue max-size-buffers=0 max-size-time=0 ! mux." + " demux. ! queue max-size-buffers=0 max-size-time=0 ! video/x-h264 ! h264parse config-interval=1 ! queue max-size-buffers=0 max-size-time=0 ! mux." /* video */ + " mpegtsmux name=mux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 2, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_h264 = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue max-size-buffers=0 max-size-time=0 ! audio/mpeg ! aacparse ! tee name=ta" /* Audio */ + " ta. ! queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioresample ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink1" + " ta. ! queue max-size-buffers=0 max-size-time=0 ! mux." + " demux. ! queue max-size-buffers=0 max-size-time=0 ! video/x-h264 ! tee name=tv" + " tv. ! queue max-size-buffers=0 max-size-time=0 ! h264parse config-interval=1 ! mux." /* Video MPEG2TS */ + " tv. ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! h264parse config-interval=1 ! capsfilter caps=\"video/x-h264, stream-format=byte-stream, alignment=nal\" ! appsink name=sink2" /* Video CVF H264 */ + " mpegtsmux name=mux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 3, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_audio_mp3 = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! audio/mpeg !" + " mpegaudioparse ! queue max-size-buffers=0 max-size-time=0 ! mpegtsmux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 1, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_audio_m4a = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux !" + " audio/mpeg ! aacparse ! queue max-size-buffers=0 max-size-time=0 ! mpegtsmux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 1, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_6_audio_mp3 = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! audio/mpeg !" + " mpegaudioparse ! queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioresample ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 1, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_6_audio_m4a = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux ! audio/mpeg !" + " aacparse ! queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioresample ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 1, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_6_audio_wav = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! wavparse !" + " queue max-size-buffers=0 max-size-time=0 ! audioresample ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 1, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_audio_mp3 = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! audio/mpeg !" + " mpegaudioparse ! tee name=t t. !" /*61883_6 streaming tee*/ + " queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioresample !" + " audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink1" /* 61883_4 streaming tee */ + " t. ! queue max-size-buffers=0 max-size-time=0 !" + " mpegtsmux name=mux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 2, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_audio_m4a = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux name=demux demux. ! audio/mpeg !" + " aacparse ! tee name=t t. !" /*61883_6 streaming tee*/ + " queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioresample !" + " audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink1" /* 61883_4 streaming tee */ + " t. ! queue max-size-buffers=0 max-size-time=0 !" + " mpegtsmux name=mux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 2, + .prepare = gst_pipeline_prepare_filesrc, +}; + +int pipeline_talker_file_preview_prepare(struct gstreamer_pipeline *gst) +{ + gst->video_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink"); + if (!gst->video_sink) { + printf("Error: Video sink not found in pipeline\n"); + goto err_video_sink; + } + + gst_pipeline_prepare_videosink(gst); + + g_object_set(G_OBJECT(gst->video_sink), + "ts-offset", (gint64) gst->config.talker.preview_ts_offset, + NULL); + + if (gst_pipeline_prepare_filesrc(gst) < 0) + goto err_file_src; + + return 0; + + err_file_src: + g_object_unref(gst->video_sink); + gst->video_sink = NULL; + +err_video_sink: + return -1; +} + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_preview = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue max-size-buffers=0 max-size-time=0 ! audio/mpeg ! mux." + " demux. ! tee name=t" +#ifdef WL_BUILD + " t. ! queue max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=true sync=true async=false" /* local display tee */ +#else + " t. ! queue max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true sync=true async=false" /* local display tee */ +#endif + " t. ! queue max-size-buffers=0 max-size-time=0 ! video/x-h264 ! h264parse config-interval=1 ! queue max-size-buffers=0 max-size-time=0 ! mux." /* avb streaming tee */ + " mpegtsmux name=mux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 1, + .prepare = pipeline_talker_file_preview_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_preview = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! audio/mpeg ! aacparse ! tee name=taudio" /* audio */ + " taudio. ! queue ! decodebin ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! appsink name=sink1" + " taudio. ! queue ! mux." + " demux. ! tee name=tvideo" +#ifdef WL_BUILD + " tvideo. ! queue ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=true sync=true" /* local display tee */ +#else + " tvideo. ! queue ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true sync=true" /* local display tee */ +#endif + " tvideo. ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! video/x-h264 ! h264parse config-interval=1 ! mux." /* video */ + " mpegtsmux name=mux alignment=1 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 2, + .prepare = pipeline_talker_file_preview_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_h264_preview = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue max-size-buffers=0 max-size-time=0 ! audio/mpeg ! aacparse ! tee name=ta" /* audio */ + " ta. ! queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioresample ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink1" + " ta. ! queue max-size-buffers=0 max-size-time=0 ! mux." + " demux. ! video/x-h264 ! queue max-size-buffers=0 max-size-time=0 ! tee name=tvideo" + " tvideo. ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! h264parse config-interval=1 ! tee name=tvideoparsed " +#ifdef WL_BUILD + " tvideoparsed. ! queue max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=true sync=true" /*local display*/ +#else + " tvideoparsed. ! queue max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true sync=true" /*local display*/ +#endif + " tvideoparsed. ! queue max-size-buffers=0 max-size-time=0 ! mux." /* video mpegts*/ + " tvideo. ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! h264parse config-interval=1 ! capsfilter caps=\"video/x-h264, stream-format=byte-stream, alignment=nal\" ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink2" /*video h264*/ + " mpegtsmux name=mux ! queue max-size-buffers=0 max-size-time=0 ! appsink name=sink0", + .num_sources = 0, + .num_sinks = 3, + .prepare = pipeline_talker_file_preview_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_cvf_h264 = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux " + "! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 " + "! video/x-h264 ! h264parse config-interval=1 ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! capsfilter caps=\"video/x-h264, stream-format=byte-stream, alignment=nal\" " + "! appsink name=sink0", + .num_sources = 0, + .num_sinks = 1, + .prepare = gst_pipeline_prepare_filesrc, +}; + +struct gstreamer_pipeline_definition pipeline_talker_file_cvf_h264_preview = { + .pipeline_string = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 " + " ! video/x-h264 ! tee name=t " + " t. ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 " + " ! h264parse ! capsfilter caps=\"video/x-h264, stream-format=byte-stream, alignment=au\" " +#ifdef WL_BUILD + " ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=true sync=true" /* local display tee */ +#else + " ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true sync=true" /* local display tee */ +#endif + " t. ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 " + " ! h264parse config-interval=1 ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! capsfilter caps=\"video/x-h264, stream-format=byte-stream, alignment=nal\" " + " ! appsink name=sink0", /* avb streaming tee */ + .num_sources = 0, + .num_sinks = 1, + .prepare = pipeline_talker_file_preview_prepare, +}; + +/* Listener pipelines */ +int pipeline_listener_audio_video_prepare(struct gstreamer_pipeline *gst) +{ + + gst->audio_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "audiosink"); + if (!gst->audio_sink) { + printf("Error: Audio sink not found in pipeline\n"); + goto err_audio_sink; + } + + gst->video_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink"); + if (!gst->video_sink) { + printf("Error: Video sink not found in pipeline\n"); + goto err_video_sink; + } + + gst_pipeline_prepare_videosink(gst); + + gst_set_listener_latency(gst); + + gst_pipeline_prepare_appsrcs(gst); + + gst_pipeline_set_alsasink_config(gst); + + return 0; + +err_video_sink: + g_object_unref(gst->audio_sink); + gst->audio_sink = NULL; + +err_audio_sink: + return -1; +} + +int pipeline_listener_audio_only_prepare(struct gstreamer_pipeline *gst) +{ + + gst->audio_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "audiosink"); + if (!gst->audio_sink) { + printf("Error: Audio sink not found in pipeline\n"); + goto err_audio_sink; + } + + gst_set_listener_latency(gst); + + gst_pipeline_prepare_appsrcs(gst); + + gst_pipeline_set_alsasink_config(gst); + + return 0; + +err_audio_sink: + return -1; +} + +int pipeline_listener_video_only_prepare(struct gstreamer_pipeline *gst) +{ + gst->video_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink"); + if (!gst->video_sink) { + printf("Error: Video sink not found in pipeline\n"); + goto err_video_sink; + } + + gst_pipeline_prepare_videosink(gst); + + gst_set_listener_latency(gst); + + gst_pipeline_prepare_appsrcs(gst); + + return 0; + +err_video_sink: + return -1; +} + +int pipeline_listener_cvf_mjpeg_prepare(struct gstreamer_pipeline *gst) +{ + gst->video_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink"); + if (!gst->video_sink) { + printf("Error: Video sink not found in pipeline\n"); + goto err_video_sink; + } + + gst_pipeline_prepare_videosink(gst); + + gst_pipeline_prepare_appsrcs(gst); + + return 0; + +err_video_sink: + return -1; +} + +int pipeline_listener_cvf_h264_prepare(struct gstreamer_pipeline *gst) +{ + gst->video_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink"); + if (!gst->video_sink) { + printf("Error: Video sink not found in pipeline\n"); + goto err_video_sink; + } + + gst_pipeline_prepare_videosink(gst); + + gst_set_listener_latency(gst); + + gst_pipeline_prepare_appsrcs(gst); + + return 0; + +err_video_sink: + return -1; +} + + + +struct gstreamer_pipeline_definition pipeline_listener_61883_4_audio_video = { + .pipeline_string = + "appsrc name=source0 is-live=true ! video/mpegts ! tsdemux name=demux latency=100" +#ifdef WL_BUILD + " demux. ! video/x-h264 ! h264parse ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=true max-lateness=1000000 sync=true async=false" +#else + " demux. ! video/x-h264 ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true max-lateness=1000000 sync=true async=false" +#endif + " demux. ! audio/mpeg ! queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioconvert ! alsasink name=audiosink max-lateness=1000000 alignment-threshold=1000000 latency-time=5000 buffer-time=50000 sync=true", + .num_sources = 1, + .num_sinks = 0, + .latency = TSDEMUX_LATENCY + ALSA_LATENCY, + .prepare = pipeline_listener_audio_video_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_listener_61883_4_audio_only = { + .pipeline_string = + "appsrc name=source0 is-live=true ! video/mpegts ! tsdemux latency=100" + " ! audio/mpeg ! queue max-size-buffers=0 max-size-time=0 ! decodebin ! audioconvert" + " ! alsasink name=audiosink max-lateness=1000000 alignment-threshold=1000000 latency-time=5000 buffer-time=50000 sync=true", + .num_sources = 1, + .num_sinks = 0, + .latency = TSDEMUX_LATENCY + ALSA_LATENCY, + .prepare = pipeline_listener_audio_only_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_listener_61883_4_video_only = { + .pipeline_string = + "appsrc name=source0 is-live=true ! video/mpegts ! tsdemux latency=100" +#ifdef WL_BUILD + " ! video/x-h264 ! queue max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=true max-lateness=1000000 sync=true", +#else + " ! video/x-h264 ! queue max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true max-lateness=1000000 sync=true", +#endif + .num_sources = 1, + .num_sinks = 0, + .latency = TSDEMUX_LATENCY, + .prepare = pipeline_listener_video_only_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_listener_61883_6 = { + .pipeline_string = + "appsrc name=source0 is-live=true" + " ! audio/x-raw,format=S24_32BE,rate=48000,channels=2,layout=interleaved ! queue max-size-buffers=0 max-size-time=0 ! audioconvert" + " ! alsasink name=audiosink max-lateness=1000000 alignment-threshold=1000000 latency-time=5000 buffer-time=50000 sync=true", + .num_sources = 1, + .num_sinks = 0, + .latency = ALSA_LATENCY, + .prepare = pipeline_listener_audio_only_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_listener_cvf_mjpeg = { + .pipeline_string = + "appsrc name=source0 is-live=true" +#ifdef WL_BUILD + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=true sync=true max-lateness=0", +#else + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true sync=true max-lateness=0", +#endif + .num_sources = 1, + .num_sinks = 0, + .latency = MJPEG_PIPELINE_LATENCY, + .prepare = pipeline_listener_cvf_mjpeg_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_listener_cvf_h264 = { + .pipeline_string = + "appsrc name=source0 is-live=true" + " ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! h264parse" + " ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false" +#ifdef WL_BUILD + " ! glimagesink name=videosink force-aspect-ratio=true sync=true max-lateness=0", +#else + " ! imxv4l2sink name=videosink force-aspect-ratio=true sync=true max-lateness=0", +#endif + .num_sources = 1, + .num_sinks = 0, + .latency = H264_PIPELINE_LATENCY, + .prepare = pipeline_listener_cvf_h264_prepare, +}; + +int pipeline_video_overlay_prepare(struct gstreamer_pipeline *gst) +{ + GstElement *overlaysink; + int i, rc; + + gst_blank_screen(gst->config.device); + + gst_pipeline_prepare_appsrcs(gst); + + + overlaysink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink3"); + if (!overlaysink) { + printf("Warning: overlaysink not found in pipeline\n"); + goto err; + } + + if (gst->config.width == 0) { + gst_element_set_state(GST_ELEMENT(gst->pipeline), GST_STATE_PAUSED); + + if (!strcmp(gst->config.device,V4L2_LVDS_DEVICE_FILE)) { + g_object_get(G_OBJECT(overlaysink), + "overlay-width-1", &gst->config.width, + "overlay-height-1", &gst->config.height, + NULL); + } else if (!strcmp(gst->config.device,V4L2_HDMI_DEVICE_FILE)) { + g_object_get(G_OBJECT(overlaysink), + "overlay-width-2", &gst->config.width, + "overlay-height-2", &gst->config.height, + NULL); + + } + } + + + if (!strcmp(gst->config.device,V4L2_LVDS_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "overlay-width-1", gst->config.width / 2, + "overlay-height-1", gst->config.height / 2, + "overlay-left-1", gst->config.width / 2, + "overlay-top-1", gst->config.height / 2, + NULL); + } else if (!strcmp(gst->config.device,V4L2_HDMI_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "overlay-width-2", gst->config.width / 2, + "overlay-height-2", gst->config.height / 2, + "overlay-left-2", gst->config.width / 2, + "overlay-top-2", gst->config.height / 2, + NULL); + + } + + overlaysink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink2"); + if (!overlaysink) { + printf("Warning: overlaysink not found in pipeline\n"); + goto err; + } + + if (!strcmp(gst->config.device,V4L2_LVDS_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "overlay-width-1", gst->config.width / 2, + "overlay-height-1", gst->config.height / 2, + "overlay-top-1", gst->config.height / 2, + NULL); + } else if (!strcmp(gst->config.device,V4L2_HDMI_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "overlay-width-2", gst->config.width / 2, + "overlay-height-2", gst->config.height / 2, + "overlay-top-2", gst->config.height / 2, + NULL); + } + + + overlaysink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink1"); + if (!overlaysink) { + printf("Warning: overlaysink not found in pipeline\n"); + goto err; + } + + if (!strcmp(gst->config.device,V4L2_LVDS_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "overlay-width-1", gst->config.width / 2, + "overlay-height-1", gst->config.height / 2, + "overlay-left-1", gst->config.width / 2, + NULL); + } else if (!strcmp(gst->config.device,V4L2_HDMI_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "overlay-width-2", gst->config.width / 2, + "overlay-height-2", gst->config.height / 2, + "overlay-left-2", gst->config.width / 2, + NULL); + } + + overlaysink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink0"); + if (!overlaysink) { + printf("Warning: overlaysink not found in pipeline\n"); + goto err; + } + + if (!strcmp(gst->config.device,V4L2_LVDS_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "overlay-width-1", gst->config.width / 2, + "overlay-height-1", gst->config.height / 2, + NULL); + } else if (!strcmp(gst->config.device,V4L2_HDMI_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "overlay-width-2", gst->config.width / 2, + "overlay-height-2", gst->config.height / 2, + NULL); + } + + /* Enable the right display for all sinks*/ + for (i = 0; i < 4; i++ ) { + + char sink_name[16]; + + rc = snprintf(sink_name, 16, "videosink%d", i); + if ((rc < 0) || (rc >= 16)) { + printf("Error %d while generating video sink name\n", rc); + goto err; + } + + overlaysink = gst_bin_get_by_name(GST_BIN(gst->pipeline), sink_name); + + if (!strcmp(gst->config.device,V4L2_LVDS_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "display-master", 0, + "display-lvds", 1, + "display-hdmi", 0, + NULL); + } else if (!strcmp(gst->config.device,V4L2_HDMI_DEVICE_FILE)) { + g_object_set(G_OBJECT(overlaysink), + "display-master", 0, + "display-lvds", 0, + "display-hdmi", 1, + NULL); + } + } + + return 0; +err: + return -1; +} + + +struct gstreamer_pipeline_definition pipeline_cvf_mjpeg_four_cameras = { + .pipeline_string = + "appsrc name=source0 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! overlaysink name=videosink0 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source1 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! overlaysink name=videosink1 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source2 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! overlaysink name=videosink2 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source3 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! overlaysink name=videosink3 force-aspect-ratio=true sync=true max-lateness=0", + .num_sources = 4, + .num_sinks = 0, + .latency = MJPEG_PIPELINE_LATENCY, + .prepare = pipeline_video_overlay_prepare, +}; + + + +int pipeline_cvf_mjpeg_decode_prepare(struct gstreamer_pipeline *gst) +{ + GstElement *filesink; + + gst_pipeline_prepare_appsrcs(gst); + + filesink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "filesink"); + if (!filesink) { + printf("Warning: filesink not found in pipeline\n"); + goto err; + } + + g_object_set(G_OBJECT(filesink), + "location", gst->config.device, + NULL); + return 0; +err: + return 0; +} + +struct gstreamer_pipeline_definition pipeline_cvf_mjpeg_decode_only = { + .pipeline_string = + "appsrc name=source0 is-live=true max-bytes=150000 block=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! tee name=tjpeg" + " tjpeg. ! videorate skip-to-first=true average-period=1000000000 ! image/jpeg, framerate=1/1 ! queue ! multifilesink name=filesink max-files=60" + " tjpeg. ! vpudec frame-drop=false output-format=1 frame-plus=8" + " ! video/x-raw,format=NV12" + " ! appsink name=sink0 sync=true max-buffers=1 drop=true", + .num_sources = 1, + .num_sinks = 1, + .latency = MJPEG_PIPELINE_LATENCY, + .prepare = pipeline_cvf_mjpeg_decode_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_split_screen = { + .pipeline_string = + "appsrc name=source0 is-live=true" + " ! video/x-raw, format=NV12, width=1280, height=800, framerate=30/1 ! queue max-size-bytes=0 max-size-buffers=1 max-size-time=0 leaky=2 ! overlaysink name=videosink0 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source1 is-live=true" + " ! video/x-raw, format=NV12, width=1280, height=800, framerate=30/1 ! queue max-size-bytes=0 max-size-buffers=1 max-size-time=0 leaky=2 ! overlaysink name=videosink1 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source2 is-live=true" + " ! video/x-raw, format=NV12, width=1280, height=800, framerate=30/1 ! queue max-size-bytes=0 max-size-buffers=1 max-size-time=0 leaky=2 ! overlaysink name=videosink2 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source3 is-live=true" + " ! video/x-raw, format=NV12, width=1280, height=800, framerate=30/1 ! queue max-size-bytes=0 max-size-buffers=1 max-size-time=0 leaky=2 ! overlaysink name=videosink3 force-aspect-ratio=true sync=true max-lateness=0", + .num_sources = 4, + .num_sinks = 0, + .latency = OVERLAY_PIPELINE_LATENCY, + .prepare = pipeline_video_overlay_prepare, +}; + +struct gstreamer_pipeline_definition pipeline_split_screen_compo = { + .pipeline_string = + "imxcompositor_g2d name=comp sink_1::xpos=1440 sink_1::ypos=0 sink_2::xpos=0 sink_2::ypos=810 sink_3::xpos=1440 sink_3::ypos=810 ! overlaysink force-aspect-ratio=true sync=false" + " appsrc name=source0 is-live=true" + " ! video/x-raw, format=NV12, width=1280, height=800, framerate=30/1 ! comp.sink_0" + " appsrc name=source1 is-live=true" + " ! video/x-raw, format=NV12, width=1280, height=800, framerate=30/1 ! comp.sink_1" + " appsrc name=source2 is-live=true" + " ! video/x-raw, format=NV12, width=1280, height=800, framerate=30/1 ! comp.sink_2" + " appsrc name=source3 is-live=true" + " ! video/x-raw, format=NV12, width=1280, height=800, framerate=30/1 ! comp.sink_3", + .num_sources = 4, + .num_sinks = 0, + .latency = OVERLAY_PIPELINE_LATENCY, + .prepare = gst_pipeline_prepare_appsrcs, +}; + +struct gstreamer_pipeline_definition pipeline_cvf_mjpeg_four_cameras_compo = { + .pipeline_string = + "imxcompositor_ipu name=comp sink_1::xpos=1440 sink_1::ypos=0 sink_2::xpos=0 sink_2::ypos=810 sink_3::xpos=1440 sink_3::ypos=810 ! overlaysink force-aspect-ratio=true sync=true" + " appsrc name=source0 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! comp.sink_0" + " appsrc name=source1 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! comp.sink_1" + " appsrc name=source2 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! comp.sink_2" + " appsrc name=source3 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! comp.sink_3", + .num_sources = 4, + .num_sinks = 0, + .latency = MJPEG_PIPELINE_LATENCY, + .prepare = gst_pipeline_prepare_appsrcs, +}; + +struct gstreamer_pipeline_definition pipeline_listener_debug = { + .pipeline_string = + "appsrc name=source0 is-live=true" + " ! filesink name=filesink location=/var/avb_listener_dump", + .num_sources = 1, + .num_sinks = 0, + .prepare = gst_pipeline_prepare_appsrcs_and_filesink, +}; diff --git a/apps/linux/common/gst_pipeline_definitions.h b/apps/linux/common/gst_pipeline_definitions.h new file mode 100644 index 0000000..a6b9926 --- /dev/null +++ b/apps/linux/common/gst_pipeline_definitions.h @@ -0,0 +1,67 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _GST_PIPELINE_DEFINITIONS_H_ +#define _GST_PIPELINE_DEFINITIONS_H_ + +#include + +struct gstreamer_pipeline; + +struct gstreamer_pipeline_definition { + char const * const pipeline_string; + unsigned int const num_sources; + unsigned int const num_sinks; + GstClockTime const latency; + + int (*prepare)(struct gstreamer_pipeline *gst_pipeline); +}; + +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_h264; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_h264_preview; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_preview; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_preview; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_audio_mp3; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_audio_m4a; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_6_audio_mp3; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_6_audio_m4a; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_6_audio_wav; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_audio_mp3; +extern struct gstreamer_pipeline_definition pipeline_talker_file_61883_4_61883_6_audio_m4a; +extern struct gstreamer_pipeline_definition pipeline_talker_file_cvf_h264; +extern struct gstreamer_pipeline_definition pipeline_talker_file_cvf_h264_preview; + +extern struct gstreamer_pipeline_definition pipeline_listener_61883_4_audio_video; +extern struct gstreamer_pipeline_definition pipeline_listener_61883_4_audio_only; +extern struct gstreamer_pipeline_definition pipeline_listener_61883_4_video_only; +extern struct gstreamer_pipeline_definition pipeline_listener_61883_6; +extern struct gstreamer_pipeline_definition pipeline_listener_cvf_mjpeg; +extern struct gstreamer_pipeline_definition pipeline_listener_cvf_h264; +extern struct gstreamer_pipeline_definition pipeline_cvf_mjpeg_four_cameras; +extern struct gstreamer_pipeline_definition pipeline_cvf_mjpeg_decode_only; +extern struct gstreamer_pipeline_definition pipeline_split_screen; +extern struct gstreamer_pipeline_definition pipeline_split_screen_compo; +extern struct gstreamer_pipeline_definition pipeline_cvf_mjpeg_four_cameras_compo; +extern struct gstreamer_pipeline_definition pipeline_listener_debug; + +#define TSDEMUX_LATENCY 100000000 /* Must match latency compiled in gstreamer plugin */ +#define ALSA_LATENCY 55000000 /* Must match the sum of buffer time (size) and period time in alsasink plugin */ +#define MPEGTS_PIPELINE_LATENCY (TSDEMUX_LATENCY + ALSA_LATENCY) /*MPEGTS pipeline have tsdemux plugin and alsasink plugin advertising their minimum latency*/ +#define H264_PIPELINE_LATENCY 0 /*H264 pipeline have no plugin advertising a minimum latency*/ + +#define LOCAL_PTS_OFFSET 150000000 /* PTS offset to compensate for global pipeline processing time: 150ms (in ns) */ +#define DEFAULT_PTS_OFFSET (LOCAL_PTS_OFFSET + MPEGTS_PIPELINE_LATENCY) /*Default and common PTS offset to guarantee sync between different listener pipelines*/ +#define MAX_PTS_OFFSET (1 * GST_SECOND) + +#define MJPEG_PIPELINE_LATENCY 8000000 +#define SALSA_LATENCY 33000000 +#define CVF_MJPEG_PTS_OFFSET (SALSA_LATENCY + MJPEG_PIPELINE_LATENCY) +#define OVERLAY_PIPELINE_LATENCY 2000000 + +#endif /* _GST_PIPELINE_DEFINITIONS_H_ */ diff --git a/apps/linux/common/gstreamer.c b/apps/linux/common/gstreamer.c new file mode 100644 index 0000000..9557add --- /dev/null +++ b/apps/linux/common/gstreamer.c @@ -0,0 +1,747 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020, 2022-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gstreamer.h" +#include "time.h" + +#define DUMP_GST_THREADS 0 + +void gst_pipeline_set_alsasink_config(struct gstreamer_pipeline *gst) +{ + const char* GstAudioBaseSinkSlaveMethodNames[] = { + "GST_AUDIO_BASE_SINK_SLAVE_RESAMPLE", + "GST_AUDIO_BASE_SINK_SLAVE_SKEW", + "GST_AUDIO_BASE_SINK_SLAVE_NONE", + "GST_AUDIO_BASE_SINK_SLAVE_CUSTOM"}; + + if (gst->audio_sink != NULL) { + g_object_set(G_OBJECT(gst->audio_sink), + "slave-method", + gst->config.sink_slave_method, + NULL); + + printf("Set pipeline audio_sink slave-method : %s\n", GstAudioBaseSinkSlaveMethodNames[gst->config.sink_slave_method]); + + g_object_set(G_OBJECT(gst->audio_sink), + "device", + gst->config.alsa_device, + NULL); + + printf("Set pipeline audio_sink device : %s\n", gst->config.alsa_device); + } else { + printf("Cannot set pipeline audio_sink configuration (gst->audio_sink is NULL)\n"); + } +} + +static GstBusSyncReply sync_bus_handler (GstBus * bus, GstMessage * message, struct gstreamer_pipeline *gst) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_STREAM_STATUS: + { + GstStreamStatusType type; + GstElement *owner; + const GValue *val; +#if DUMP_GST_THREADS + gchar *path; +#endif + GstTask *task = NULL; + + gst_message_parse_stream_status (message, &type, &owner); + + val = gst_message_get_stream_status_object (message); +#if DUMP_GST_THREADS + g_message ("type: %d", type); + path = gst_object_get_path_string (GST_MESSAGE_SRC (message)); + g_message ("source: %s", path); + g_free (path); + path = gst_object_get_path_string (GST_OBJECT (owner)); + g_message ("owner: %s", path); + g_free (path); + + if (G_VALUE_HOLDS_OBJECT (val)) { + g_message ("object: type %s, value %p", G_VALUE_TYPE_NAME (val), + g_value_get_object (val)); + } else if (G_VALUE_HOLDS_POINTER (val)) { + g_message ("object: type %s, value %p", G_VALUE_TYPE_NAME (val), + g_value_get_pointer (val)); + } else if (G_IS_VALUE (val)) { + g_message ("object: type %s", G_VALUE_TYPE_NAME (val)); + } else { + g_message ("object: (null)"); + break; + } +#endif + /* see if we know how to deal with this object */ + if (G_VALUE_TYPE (val) == GST_TYPE_TASK) { + task = g_value_get_object (val); + } + + switch (type) { + case GST_STREAM_STATUS_TYPE_CREATE: + if (task) { + printf("GST_STREAM_STATUS_TYPE_CREATE message received: set pool (%p) for task (%p) \n", gst->pool, task); + gst_task_set_pool (task, gst->pool); + } + break; + case GST_STREAM_STATUS_TYPE_ENTER: + break; + case GST_STREAM_STATUS_TYPE_LEAVE: + break; + default: + break; + } + break; + } + case GST_MESSAGE_ASYNC_DONE: + { + pthread_mutex_lock(&gst->msg_lock); + gst->async_msg_received = 1; + pthread_mutex_unlock(&gst->msg_lock); + + break; + } + default: + break; + } +/* pass all messages on the async queue */ +return GST_BUS_PASS; +} + + +static void gst_get_latency(struct gstreamer_pipeline *gst) +{ + GstQuery *q; + + q = gst_query_new_latency(); + if (gst_element_query(gst->pipeline, q)) { + gboolean live; + GstClockTime minlat, maxlat; + + gst_query_parse_latency(q, &live, &minlat, &maxlat); + + printf("Pipeline latency: %ju-%ju ns\n", minlat, maxlat); + } + + gst_query_unref(q); +} + +void gst_set_listener_latency(struct gstreamer_pipeline *gst) +{ + if (gst->video_sink) { + g_object_set(G_OBJECT(gst->video_sink), + "ts-offset", gst->listener.pts_offset - gst->definition->latency, + NULL); + + printf("Set video sink ts-offset: %ju\n", gst->listener.pts_offset - gst->definition->latency); + + } + + if (gst->audio_sink) { + g_object_set(G_OBJECT(gst->audio_sink), + "ts-offset", gst->listener.pts_offset - gst->definition->latency, + NULL); + + printf("Set audio sink ts-offset: %ju\n", gst->listener.pts_offset - gst->definition->latency); + } +} + + +void gst_blank_screen(char *device_str) +{ + int rc; + uint64_t now, then; + GstElement *video_sink; + GstElement *pipeline; + GstBus *bus; + GstMessage *msg; + + gettime_us(&now); + + pipeline = gst_parse_launch("videotestsrc pattern=black num-buffers=4 !video/x-raw,format=YV12 !imxv4l2sink name=videosink overlay-width=2000 overlay-height=2000", NULL); + if (!pipeline) + goto err_pipeline; + + video_sink = gst_bin_get_by_name(GST_BIN(pipeline), "videosink"); + if (!video_sink) + goto err_video; + + g_object_set(G_OBJECT(video_sink), "device", device_str, NULL); + + rc = gst_element_set_state(pipeline, GST_STATE_PLAYING); + if (rc == GST_STATE_CHANGE_FAILURE) + goto err_state; + + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + if (!bus) + goto err_bus; + + msg = gst_bus_timed_pop_filtered(bus, GST_SECOND, GST_MESSAGE_EOS); + if (msg) + gst_message_unref(msg); + + gst_element_set_state(pipeline, GST_STATE_NULL); + + gettime_us(&then); + + printf("Blanking screen took %" PRId64 " us\n", then - now); + + gst_object_unref(bus); + +err_bus: +err_state: + gst_object_unref(video_sink); + +err_video: + gst_object_unref(pipeline); + +err_pipeline: + return; +} + +static int gst_pipeline_prepare_clock(struct gstreamer_pipeline *gst) +{ + gst->clock = g_object_new (GST_TYPE_SYSTEM_CLOCK, "name", "GstSystemClock", NULL); + if (!gst->clock) { + printf("Error: could not obtain system clock.\n"); + goto err; + } + g_object_set(G_OBJECT(gst->clock), "clock-type", GST_CLOCK_TYPE_REALTIME, NULL); + + gst->time = gst_clock_get_time(gst->clock); + gst_pipeline_use_clock(GST_PIPELINE(gst->pipeline), gst->clock); + +#if 0 + if (gst->direction == GST_DIRECTION_LISTENER) { + /* Enable to use absolute gPTP time as the running-time of the pipeline, useful for synchronization debugging */ + gst_element_set_start_time(gst->pipeline, GST_CLOCK_TIME_NONE); + gst_element_set_base_time(gst->pipeline, 0); + } +#endif + + return 0; + +err: + return -1; +} + +int gst_pipeline_prepare_videosink(struct gstreamer_pipeline *gst) +{ + gchar *factory_name; + factory_name = GST_OBJECT_NAME (gst_element_get_factory(gst->video_sink)); + + if (!strcmp(factory_name, "glimagesink")) { + + GValue render_rectangle = G_VALUE_INIT; + GValue val = G_VALUE_INIT; + g_value_init (&val, G_TYPE_INT); + g_value_init (&render_rectangle, GST_TYPE_ARRAY); + + g_value_set_int (&val, 0); + gst_value_array_append_value (&render_rectangle, &val); + g_value_set_int (&val, 0); + gst_value_array_append_value (&render_rectangle, &val); + g_value_set_int (&val, gst->config.width); + gst_value_array_append_value (&render_rectangle, &val); + g_value_set_int (&val, gst->config.height); + gst_value_array_append_value (&render_rectangle, &val); + + g_value_unset (&val); + + g_object_set_property (G_OBJECT(gst->video_sink), "render-rectangle", + &render_rectangle); + + g_value_unset (&render_rectangle); + + } else if (!strcmp(factory_name, "imxv4l2sink")) { + + gst_blank_screen(gst->config.device); + + g_object_set(G_OBJECT(gst->video_sink), + "device", gst->config.device, + "crop-width", gst->config.crop_width, + "crop-height", gst->config.crop_height, + "overlay-width", gst->config.width, + "overlay-height", gst->config.height, + NULL); + } + + if (!gst->config.sync_render_to_clock) + printf("This pipeline (%p) will not sync to clock on video rendering \n", gst); + + g_object_set(G_OBJECT(gst->video_sink), + "sync", gst->config.sync_render_to_clock, + NULL); + + return 0; +} + +int gst_pipeline_prepare_appsrcs(struct gstreamer_pipeline *gst) +{ + unsigned int i; + + for (i = 0; i < gst->definition->num_sources; i++) { + GstAppSrc *source = gst->source[i].source; + + g_object_set(G_OBJECT(source), "format", GST_FORMAT_TIME, NULL); + } + + return 0; +} + +int gst_pipeline_prepare_appsrcs_and_filesink(struct gstreamer_pipeline *gst) +{ + unsigned int i; + GstElement *element; + + for (i = 0; i < gst->definition->num_sources; i++) { + GstAppSrc *source = gst->source[i].source; + + g_object_set(G_OBJECT(source), "format", GST_FORMAT_TIME, NULL); + } + + element = gst_bin_get_by_name(GST_BIN(gst->pipeline), "filesink"); + if (!element) { + printf("File sink element not found in pipeline\n"); + return -1; + } + + g_object_set(G_OBJECT(element), "location", gst->config.listener.debug_file_dump_location, NULL); + gst_object_unref(element); + + return 0; +} + +int gst_pipeline_prepare_filesrc(struct gstreamer_pipeline *gst) +{ + GstElement *element; + + element = gst_bin_get_by_name(GST_BIN(gst->pipeline), "filesrc"); + if (!element) { + printf("File source element not found in pipeline\n"); + goto err; + } + + g_object_set(G_OBJECT(element), "location", gst->config.talker.file_src_location, NULL); + gst_object_unref(element); + + return 0; + +err: + return -1; +} + +static int gst_get_sources_sinks(struct gstreamer_pipeline *gst) +{ + int i, j = 0, rc; + + for (i = 0; i < gst->definition->num_sources; i++) { + GstAppSrc *source; + char source_name[16]; + + rc = snprintf(source_name, 16, "source%d", i); + if ((rc < 0) || (rc >= 16)) { + printf("Error %d while generating source name \n", rc); + goto err; + } + + source = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(gst->pipeline), source_name)); + if (!source) { + printf("gst_bin_get_by_name(%s) failed\n", source_name); + goto err; + } + + gst->source[i].source = source; + } + + for (j = 0; j < gst->definition->num_sinks; j++) { + GstAppSink *sink; + char sink_name[16]; + + rc = snprintf(sink_name, 16, "sink%d", j); + if ((rc < 0) || (rc >= 16)) { + printf("Error %d while generating sink name \n", rc); + goto err; + } + + sink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(gst->pipeline), sink_name)); + if (!sink) { + printf("gst_bin_get_by_name(%s) failed\n", sink_name); + goto err; + } + + gst->sink[j].sink = sink; + } + + + return 0; + +err: + while (i > 0) { + gst_object_unref(gst->source[i - 1].source); + i--; + } + + while (j > 0) { + gst_object_unref(gst->sink[j - 1].sink); + j--; + } + + return -1; +} + +static void gst_release_sources_sinks(struct gstreamer_pipeline *gst) +{ + int i; + + for (i = 0; i < gst->definition->num_sources; i++) + gst_object_unref(gst->source[i].source); + + for (i = 0; i < gst->definition->num_sinks; i++) + gst_object_unref(gst->sink[i].sink); +} + +static void gst_teardown_pipeline(struct gstreamer_pipeline *gst) +{ + if (gst->video_sink) { + g_object_unref(gst->video_sink); + gst->video_sink = NULL; + } + + if (gst->audio_sink) { + g_object_unref(gst->audio_sink); + gst->audio_sink = NULL; + } +} + +int gst_stop_pipeline(struct gstreamer_pipeline *gst) +{ + int rc; + + rc = gst_element_set_state(gst->pipeline, GST_STATE_NULL); + if (rc == GST_STATE_CHANGE_FAILURE) + printf("Unable to set the pipeline to the NULL state.\n"); + else if (rc == GST_STATE_CHANGE_ASYNC) { + printf("Changing Asynchronously to the NULL state.\n"); + rc = gst_element_get_state (gst->pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); + if (rc != GST_STATE_CHANGE_SUCCESS) + printf("Unable to set the pipeline synchronously to the NULL state.\n"); + } + + if (gst->custom_gst_pipeline_teardown) + gst->custom_gst_pipeline_teardown(gst); + + gst_release_sources_sinks(gst); + + gst_teardown_pipeline(gst); + + gst_object_unref(gst->clock); + + gst_object_unref(gst->bus); + + gst_object_unref(gst->pipeline); + + return rc; +} + +int gst_setup_pipeline(struct gstreamer_pipeline *gst, int priority, unsigned int direction) +{ + gst->direction = direction; + + gst->pipeline = gst_parse_launch((const gchar *)gst->definition->pipeline_string, NULL); + if (!gst->pipeline) { + printf("gst_parse_launch() failed\n"); + goto err_pipeline; + } + + if (gst_get_sources_sinks(gst) < 0) + goto err_src_sink; + + if (gst_pipeline_prepare_clock(gst) < 0) + goto err_clock; + + if (gst->definition->prepare(gst) < 0) + goto err_setup; + + gst->bus = gst_pipeline_get_bus(GST_PIPELINE(gst->pipeline)); + if (!gst->bus) { + printf("gst_pipeline_get_bus() failed\n"); + goto err_bus; + } + + if (gst->custom_gst_pipeline_setup && gst->custom_gst_pipeline_setup(gst)) { + printf("custom_gst_pipeline_setup() failed\n"); + goto error_custom_setup; + } + + return 0; + +error_custom_setup: + gst_object_unref(gst->bus); + +err_bus: + gst_teardown_pipeline(gst); + +err_setup: + gst_object_unref(gst->clock); + +err_clock: + gst_release_sources_sinks(gst); + +err_src_sink: + gst_object_unref(gst->pipeline); + +err_pipeline: + return -1; +} + +int gst_play_pipeline(struct gstreamer_pipeline *gst) +{ + int rc; + + gst_bus_set_flushing(gst->bus, TRUE); + gst_bus_set_flushing(gst->bus, FALSE); + + printf("GStreamer : enable the verbose output for the gst-pipeline \n"); + g_signal_connect( gst->pipeline, "deep-notify", G_CALLBACK( gst_object_default_deep_notify ), NULL ); + + printf("GStreamer : Install sync_bus_handler \n"); + + gst_bus_set_sync_handler (gst->bus, (GstBusSyncHandler) sync_bus_handler, gst, +NULL); + + pthread_mutex_lock(&gst->msg_lock); + gst->async_msg_received = 0; + pthread_mutex_unlock(&gst->msg_lock); + + rc = gst_element_set_state(GST_ELEMENT(gst->pipeline), GST_STATE_PLAYING); + if (rc == GST_STATE_CHANGE_FAILURE) { + printf("Unable to set the pipeline to the playing state.\n"); + goto err_state; + } + + gst->basetime = GST_CLOCK_TIME_NONE; + + return 0; + +err_state: + gst_stop_pipeline(gst); + + return -1; +} + +int gst_start_pipeline(struct gstreamer_pipeline *gst, int priority, unsigned int direction) +{ + int rc; + + printf("%s : Starting pipeline: %s \n", __func__, gst->definition->pipeline_string); + + rc = gst_setup_pipeline(gst, priority, direction); + if (rc < 0) + goto err_setup; + + rc = gst_play_pipeline(gst); + + return rc; + +err_setup: + return -1; +} + +void gst_process_bus_messages(struct gstreamer_pipeline *gst) +{ + GstMessage *msg = NULL; + GError *err; + gchar *debug_info; + + while (gst_bus_have_pending(gst->bus)) { + msg = gst_bus_pop_filtered(gst->bus, GST_MESSAGE_ERROR | GST_MESSAGE_WARNING | GST_MESSAGE_EOS | GST_MESSAGE_STATE_CHANGED); + + if (!msg) + break; + + switch (GST_MESSAGE_TYPE(msg)) { + + case GST_MESSAGE_STATE_CHANGED: { + GstState old_state, new_state, pending_state; + + gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); + + /* Only check pipeline state change messages */ + if (GST_MESSAGE_SRC (msg) != GST_OBJECT_CAST (gst->pipeline)) + break; + + + if ( new_state == GST_STATE_PLAYING) { + g_print ("Element %s changed state from %s to %s (target %s)\n", GST_OBJECT_NAME(msg->src), + gst_element_state_get_name(old_state), gst_element_state_get_name(new_state), gst_element_state_get_name(pending_state)); + gst_get_latency(gst); + } + + break; + } + + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &err, &debug_info); + + printf("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message); + printf("Debugging information: %s\n", debug_info ? debug_info : "none"); + + g_clear_error(&err); + g_free(debug_info); + + break; + + case GST_MESSAGE_WARNING: + gst_message_parse_warning(msg, &err, &debug_info); + + printf("Warning received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message); + printf("Debugging information: %s\n", debug_info ? debug_info : "none"); + + g_clear_error(&err); + g_free(debug_info); + + break; + + case GST_MESSAGE_INFO: + gst_message_parse_info(msg, &err, &debug_info); + printf("Info received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); + printf("Debugging information: %s\n", debug_info ? debug_info : "none"); + g_clear_error(&err); + g_free(debug_info); + break; + + case GST_MESSAGE_EOS: + printf("End-Of-Stream reached.\n"); + break; + + default: + /* We should not reach here because we only asked for ERRORs and EOS */ + break; + } + + gst_message_unref (msg); + } +} + +void gstreamer_init(void) +{ + gst_init(NULL, NULL); + +} + +void gstreamer_reset(void) +{ + gst_deinit(); +} + +/* + * scandir will skip the file if input_file_filter_mp4 returns 0, + * and add it to the list otherwise. + */ +static int input_file_filter_mp4(const struct dirent *file) +{ + if (!strcasestr(file->d_name, ".mp4")) + return 0; + else + return 1; + +} + +int gst_build_media_file_list(struct gstreamer_pipeline_config *gst_config) +{ + struct stat status; + struct dirent **file_list; + int rc = 0; + int n = 1; + int i = 0; + int len; + + + if (stat(gst_config->talker.input_media_file_name, &status) < 0) { + printf("Couldn't get file status for %s, got error %s\n", gst_config->talker.input_media_file_name, strerror(errno)); + goto err; + } + + if (S_ISDIR(status.st_mode)) { + n = scandir(gst_config->talker.input_media_file_name, &file_list, input_file_filter_mp4, alphasort); + if (n < 0) { + printf("Couldn't scan directory %s, got error %s\n", gst_config->talker.input_media_file_name, strerror(errno)); + goto err; + } + if (n == 0) { + printf("Didn't find any media files in directory %s\n", gst_config->talker.input_media_file_name); + goto err; + } + + gst_config->talker.n_input_media_files = n; + gst_config->talker.input_media_files = malloc(n * sizeof(char *)); + + if (!gst_config->talker.input_media_files) { + printf("Error while allocating gst talker's %d input media files\n", n); + goto err_inputs_alloc; + } + + for (i = 0; i < n; i++) { + len = strlen(gst_config->talker.input_media_file_name) + strlen(file_list[i]->d_name) + 1; + gst_config->talker.input_media_files[i] = malloc(len); + + if (!gst_config->talker.input_media_files[i]) { + printf("Error while allocating gst talker's input media files at index %d\n", i); + goto err_input_member_alloc; + } + + rc = snprintf(gst_config->talker.input_media_files[i], len, "%s%s", gst_config->talker.input_media_file_name, file_list[i]->d_name); + if ((rc < 0) || (rc >= len)) { + printf("Error %d while generating filenames\n", rc); + goto err_name_gen; + } + + } + + } else { + gst_config->talker.n_input_media_files = 1; + gst_config->talker.input_media_files = malloc(sizeof(char *)); + + if (!gst_config->talker.input_media_files) { + printf("Error while allocating gst talker's input media files\n"); + goto err_inputs_alloc; + } + + *gst_config->talker.input_media_files = gst_config->talker.input_media_file_name; + } + + return 0; + +err_name_gen: + free(gst_config->talker.input_media_files[i]); + +err_input_member_alloc: + while (i--) { + free(gst_config->talker.input_media_files[i]); + } + + free(gst_config->talker.input_media_files); + +err_inputs_alloc: +err: + return -1; +} diff --git a/apps/linux/common/gstreamer.h b/apps/linux/common/gstreamer.h new file mode 100644 index 0000000..6999fe6 --- /dev/null +++ b/apps/linux/common/gstreamer.h @@ -0,0 +1,172 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _GSTREAMER_H_ +#define _GSTREAMER_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#include "gst_pipeline_definitions.h" + +#define V4L2_LVDS_DEVICE_FILE "/dev/video16" +#define V4L2_HDMI_DEVICE_FILE "/dev/video18" +#define DEFAULT_LVDS_HEIGHT 768 +#define DEFAULT_LVDS_WIDTH 1024 +#define DEFAULT_HDMI_HEIGHT 1080 +#define DEFAULT_HDMI_WIDTH 1920 + +#define GST_DIRECTION_LISTENER 0 +#define GST_DIRECTION_TALKER 1 + +#define GST_THREADS_PRIORITY 1 +#define GST_THREADS_SCHED_POLICY SCHED_FIFO + + +#ifdef __cplusplus +extern "C" { +#endif + +#define GST_MAX_SOURCES 4 +#define GST_MAX_SINKS 3 + +struct gstreamer_sink { + void *data; + + GstAppSink *sink; +}; + +struct gstreamer_source { + void *data; + + GstAppSrc *source; +}; + +struct stream_ids_map { + uint64_t stream_id; + unsigned int source_index; + unsigned int sink_index; +}; + +/*General config flags for talker and/or listener */ +#define GST_TYPE_VIDEO (1 << 0) +#define GST_TYPE_AUDIO (1 << 1) +#define GST_TYPE_LISTENER (1 << 2) +#define GST_TYPE_TALKER (1 << 3) +#define GST_TYPE_MULTI_TALKER (1 << 4) +/*Specific flags for talker or listener*/ +#define GST_FLAG_CAMERA (1 << 0) +#define GST_FLAG_BEV (1 << 1) +#define GST_FLAG_PREVIEW (1 << 2) +#define GST_FLAG_DEBUG (1 << 3) + +struct gstreamer_pipeline_config { + unsigned int type; + char *device; + char *alsa_device; + unsigned int width; + unsigned int height; + unsigned int crop_width; + unsigned int crop_height; + unsigned int nstreams; // Number of streams that this pipeline will handle + unsigned int configured; + GstAudioBaseSinkSlaveMethod sink_slave_method; + + unsigned int sync_render_to_clock; + struct { + unsigned int flags; + GstClockTime pts_offset; + unsigned int camera_type; + unsigned long long h264_stream_id; + struct stream_ids_map stream_ids_mappping[GST_MAX_SOURCES]; + char *debug_file_dump_location; + } listener; + + struct { + unsigned int flags; + char *input_media_file_name; + char **input_media_files; + unsigned char input_media_file_index; + unsigned int n_input_media_files; + char *file_src_location; + unsigned long long preview_ts_offset; + } talker; +}; + + +struct gstreamer_pipeline { + struct gstreamer_pipeline_config config; + struct gstreamer_pipeline_definition *definition; + + unsigned int direction; + + struct gstreamer_source source[GST_MAX_SOURCES]; + struct gstreamer_sink sink[GST_MAX_SINKS]; + + struct { + unsigned int sync; + struct talker_gst_media *gst; + struct talker_gst_multi_app *stream[GST_MAX_SINKS]; + unsigned int nb_streams; + } talker; + + struct { + GstClockTime pts_offset; + unsigned long long local_pts_offset; + } listener; + + GstElement *pipeline; + GstBus *bus; + GstClock *clock; + GstElement *video_sink; + GstElement *audio_sink; + GstClockTime time; + GstClockTime basetime; + GstTaskPool *pool; + unsigned int async_msg_received; + pthread_t event_loop_tid; + pthread_mutex_t msg_lock; + int (*custom_gst_pipeline_setup)(struct gstreamer_pipeline *gst); + void (*custom_gst_pipeline_teardown)(struct gstreamer_pipeline *gst); +}; + + +void gst_pipeline_set_alsasink_config(struct gstreamer_pipeline *gst); +void gst_blank_screen(char *device_str); +void gst_set_listener_latency(struct gstreamer_pipeline *gst); +int gst_pipeline_prepare_videosink(struct gstreamer_pipeline *gst); +int gst_pipeline_prepare_appsrcs(struct gstreamer_pipeline *gst); +int gst_pipeline_prepare_filesrc(struct gstreamer_pipeline *gst); +int gst_pipeline_prepare_appsrcs_and_filesink(struct gstreamer_pipeline *gst); + +int gst_setup_pipeline(struct gstreamer_pipeline *gst, int priority, unsigned int direction); +int gst_play_pipeline(struct gstreamer_pipeline *gst); +int gst_start_pipeline(struct gstreamer_pipeline *gst, int priority, unsigned int direction); + +void gst_blank_screen(char *device_str); + +void gst_process_bus_messages(struct gstreamer_pipeline *gst); + +int gst_stop_pipeline(struct gstreamer_pipeline *gst); + +void gstreamer_init(void); +void gstreamer_reset(void); + +int gst_build_media_file_list(struct gstreamer_pipeline_config *gst_config); + + +#ifdef __cplusplus +} +#endif + +#endif /* _GSTREAMER_H_ */ diff --git a/apps/linux/common/gstreamer.inc b/apps/linux/common/gstreamer.inc new file mode 100644 index 0000000..12303cc --- /dev/null +++ b/apps/linux/common/gstreamer.inc @@ -0,0 +1,6 @@ +# Common Makefile definitions for applications using gstreamer libraries + +GST_INCLUDES=-I$(STAGING_DIR)/usr/include -I$(STAGING_DIR)/usr/include/gstreamer-1.0 -I$(STAGING_DIR)/usr/lib/gstreamer-1.0/include -I$(STAGING_DIR)/usr/include/glib-2.0 -I$(STAGING_DIR)/usr/lib/glib-2.0/include +GST_LIBS= -lglib-2.0 -lgobject-2.0 -lpcre -lffi -lgthread-2.0 -lgmodule-2.0 -lgstreamer-1.0 -lgstapp-1.0 -lgstbase-1.0 -L$(STAGING_DIR)/usr/lib + +GST_CFLAGS=$(GST_LIBS) $(GST_INCLUDES) diff --git a/apps/linux/common/gstreamer_custom_rt_pool.c b/apps/linux/common/gstreamer_custom_rt_pool.c new file mode 100644 index 0000000..bf5b7cb --- /dev/null +++ b/apps/linux/common/gstreamer_custom_rt_pool.c @@ -0,0 +1,122 @@ +/* + * Copyright 2018-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include +#include "gstreamer_custom_rt_pool.h" + +/* --- standard type macros --- */ +#define TYPE_AVB_RT_POOL (avb_rt_pool_get_type ()) +#define AVB_RT_POOL(pool) (G_TYPE_CHECK_INSTANCE_CAST ((pool), TYPE_AVB_RT_POOL, AvbRTPool)) +#define TEST_IS_AVB_RT_POOL(pool) (G_TYPE_CHECK_INSTANCE_TYPE ((pool), TYPE_AVB_RT_POOL)) +#define AVB_RT_POOL_CLASS(pclass) (G_TYPE_CHECK_CLASS_CAST ((pclass), TYPE_AVB_RT_POOL, AvbRTPoolClass)) +#define TEST_IS_AVB_RT_POOL_CLASS(pclass) (G_TYPE_CHECK_CLASS_TYPE ((pclass), TYPE_AVB_RT_POOL)) +#define AVB_RT_POOL_GET_CLASS(pool) (G_TYPE_INSTANCE_GET_CLASS ((pool), TYPE_AVB_RT_POOL, AvbRTPoolClass)) +#define AVB_RT_POOL_CAST(pool) ((AvbRTPool*)(pool)) + +typedef struct _AvbRTPool { + GstTaskPool object; +} AvbRTPool; + +typedef struct _AvbRTPoolClass { + GstTaskPoolClass parent_class; +} AvbRTPoolClass; + +typedef struct _AvbRTthreadID { + pthread_t thread; +} AvbRTthreadID; + +GType avb_rt_pool_get_type (void); + +G_DEFINE_TYPE (AvbRTPool, avb_rt_pool, GST_TYPE_TASK_POOL); + +static void avb_rt_pool_finalize(GObject * object); + +static void avb_rt_thread_pool_prepare(GstTaskPool * pool, GError ** error) +{ +} + +static void avb_rt_thread_pool_cleanup(GstTaskPool * pool) +{ +} + +static gpointer avb_rt_thread_push(GstTaskPool * pool, GstTaskPoolFunction func, gpointer data, GError ** error) +{ + AvbRTthreadID *tid; + gint res; + pthread_attr_t attr; + struct sched_param param; + + tid = g_slice_new0(AvbRTthreadID); + + pthread_attr_init(&attr); + + if ((res = pthread_attr_setschedpolicy(&attr, GST_THREADS_SCHED_POLICY)) != 0) + g_warning("setschedpolicy: failure: %p", g_strerror (res)); + + param.sched_priority = GST_THREADS_PRIORITY; + + if ((res = pthread_attr_setschedparam(&attr, ¶m)) != 0) + g_warning("setschedparam: failure: %p", g_strerror (res)); + + if ((res = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + g_warning("setinheritsched: failure: %p", g_strerror (res)); + + res = pthread_create(&tid->thread, &attr, (void *(*)(void *)) func, data); + + if (res != 0) { + g_set_error(error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN, + "Error creating thread: %s", g_strerror (res)); + g_slice_free(AvbRTthreadID, tid); + tid = NULL; + } + + return tid; +} + +static void avb_rt_thread_join(GstTaskPool * pool, gpointer id) +{ + AvbRTthreadID *tid = (AvbRTthreadID *) id; + + pthread_join(tid->thread, NULL); + + g_slice_free(AvbRTthreadID, tid); +} + +static void avb_rt_pool_class_init(AvbRTPoolClass * klass) +{ + GstTaskPoolClass *gsttaskpool_class; + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + gsttaskpool_class = (GstTaskPoolClass *) klass; + + gobject_class->finalize = GST_DEBUG_FUNCPTR(avb_rt_pool_finalize); + + gsttaskpool_class->prepare = avb_rt_thread_pool_prepare; + gsttaskpool_class->cleanup = avb_rt_thread_pool_cleanup; + gsttaskpool_class->push = avb_rt_thread_push; + gsttaskpool_class->join = avb_rt_thread_join; +} + +static void avb_rt_pool_init(AvbRTPool * pool) +{ +} + +static void avb_rt_pool_finalize(GObject * object) +{ + G_OBJECT_CLASS(avb_rt_pool_parent_class)->finalize(object); +} + +GstTaskPool * avb_rt_pool_new(void) +{ + GstTaskPool *pool; + + pool = g_object_new(TYPE_AVB_RT_POOL, NULL); + + return pool; +} diff --git a/apps/linux/common/gstreamer_custom_rt_pool.h b/apps/linux/common/gstreamer_custom_rt_pool.h new file mode 100644 index 0000000..805ee88 --- /dev/null +++ b/apps/linux/common/gstreamer_custom_rt_pool.h @@ -0,0 +1,16 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __CUSTOM_RT_POOL_H__ +#define __CUSTOM_RT_POOL_H__ + +#include +#include "gstreamer.h" + +GstTaskPool * avb_rt_pool_new (void); + +#endif /* __CUSTOM_RT_POOL_H__ */ diff --git a/apps/linux/common/gstreamer_multisink.c b/apps/linux/common/gstreamer_multisink.c new file mode 100644 index 0000000..685ae52 --- /dev/null +++ b/apps/linux/common/gstreamer_multisink.c @@ -0,0 +1,758 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include +#include + +#include +#include "common.h" +#include "gstreamer_multisink.h" + +#define minimum(a,b) ((a)<(b)?(a):(b)) + + +/* Offset was determined empirically to avoid late buffers */ +#define TALKER_PRESENTATION_OFFSET 15000000 + +const char *gst_state[] = { + STR(GST_STATE_STARTING), + STR(GST_STATE_STARTED), + STR(GST_STATE_STOPPED), + STR(GST_STATE_LOOP) +}; + +const char *stream_state[] = { + STR(STREAM_STATE_CONNECTING), + STR(STREAM_STATE_CONNECTED), + STR(STREAM_STATE_DISCONNECTING), + STR(STREAM_STATE_DISCONNECTED), + STR(STREAM_STATE_WAIT_DATA), + STR(STREAM_STATE_WAIT_START), + STR(STREAM_STATE_LOOP), + STR(STREAM_STATE_STOPPED) +}; + +const char *stream_event[] = { + STR(STREAM_EVENT_CONNECT), + STR(STREAM_EVENT_DISCONNECT), + STR(STREAM_EVENT_DATA), + STR(STREAM_EVENT_TIMER), + STR(STREAM_EVENT_LOOP_DONE), + STR(STREAM_EVENT_PLAY), + STR(STREAM_EVENT_STOP) +}; + +const char *gst_event[] = { + STR(GST_EVENT_START), + STR(GST_EVENT_STOP), + STR(GST_EVENT_LOOP), + STR(GST_EVENT_TIMER) +}; + +static void stream_stats_show(struct stats *s) +{ + struct talker_gst_multi_app *stream = s->priv; + + printf("stream(%d) min delay %d/ mean delay %d/ max delay %d\n", stream->index, stream->delay.min, stream->delay.mean, stream->delay.max); +} + + +static GstFlowReturn talker_gst_multi_new_sample_handler(GstAppSink *sink, gpointer data) +{ + struct talker_gst_multi_app *stream = data; + pthread_mutex_lock(&stream->samples_lock); + stream->samples++; + pthread_mutex_unlock(&stream->samples_lock); + return GST_FLOW_OK; +} + + +int talker_gst_multi_data_handler(struct talker_gst_multi_app *stream) +{ + struct talker_gst_media *gst = stream->gst; + struct avb_event event; + int nbytes = 0; + int rc = 0; + uint64_t now = 0; + unsigned int remaining, written = 0; + unsigned int event_n; + unsigned long long offset; + unsigned long long read_samples = 0; + + if (stream->state == STREAM_STATE_CONNECTED) + remaining = stream->batch_size; + else { + /* exit when there are no samples to read or they are in the future */ + remaining = -1; + } + + while (remaining) { + if (stream->current_size == 0) { + + if (!(stream->count % 10000)) + printf("stream(%d) bytes: %llu\n", stream->index, stream->byte_count); + + stream->count++; + + pthread_mutex_lock(&stream->samples_lock); + read_samples = stream->samples; + pthread_mutex_unlock(&stream->samples_lock); + + if (read_samples) + stream->current_sample = gst_app_sink_pull_sample(GST_APP_SINK(stream->sink)); + else + stream->current_sample = NULL; + + if (!stream->current_sample) { +// printf("%s stream(%d) gst_app_sink_pull_sample() failed\n", __func__, _stream->index); + break; + } + + pthread_mutex_lock(&stream->samples_lock); + stream->samples--; + pthread_mutex_unlock(&stream->samples_lock); + + stream->current_buffer = gst_sample_get_buffer(stream->current_sample); + if (!stream->current_buffer) { + printf("%s stream(%d) gst_sample_get_buffer() failed\n", __func__, stream->index); + goto exit_unref; + } + + if (gst_buffer_map(stream->current_buffer, &stream->current_info, GST_MAP_READ)) + stream->current_data = stream->current_info.data; + else { + printf("%s stream(%d) gst_buffer_map() failed\n", __func__, stream->index); + goto exit_unref; + } + + if (!stream->current_data) { + printf("%s stream(%d) couldn't get data buffer\n", __func__, stream->index); + goto exit_unmap; + } + + stream->current_size = stream->current_info.size; + } + + nbytes = minimum(stream->current_size, remaining); + + event.index = 0; + event.event_mask = 0; + event_n = 0; + + if ((stream->current_size == stream->current_info.size) && GST_CLOCK_TIME_IS_VALID(GST_BUFFER_PTS(stream->current_buffer))) { + + gst->gst_pipeline->basetime = gst_element_get_base_time(GST_ELEMENT(gst->gst_pipeline->pipeline)); + if(!GST_CLOCK_TIME_IS_VALID(gst->gst_pipeline->basetime) || !gst->gst_pipeline->basetime) { + printf("%s stream(%d) Bad basetime: %"GST_TIME_FORMAT" \n", __func__, stream->index, GST_TIME_ARGS(gst->gst_pipeline->basetime)); + break; + } + + gettime_ns(&now); + + if (stream->state == STREAM_STATE_CONNECTED) { + offset = TALKER_PRESENTATION_OFFSET + avb_stream_presentation_offset(stream->stream_h); + + if (!stream->audio || !stream->byte_count) { + event.event_mask = AVTP_SYNC; + event.ts = (gst->gst_pipeline->basetime + GST_BUFFER_PTS(stream->current_buffer) + offset) & 0xffffffff; + event_n = 1; + stream->last_ts = event.ts; + } + + stats_update(&stream->delay, (int)(gst->gst_pipeline->basetime + GST_BUFFER_PTS(stream->current_buffer) + offset) - now); + + if (stream->count < 10) + printf("stream(%d) now: %"GST_TIME_FORMAT"(%u) count: %lld ts: %"GST_TIME_FORMAT" (%u) delta: %"GST_STIME_FORMAT" basetime: %"GST_TIME_FORMAT" buffer ts: %"GST_TIME_FORMAT" size: %u\n", + stream->index, + GST_TIME_ARGS(now), (unsigned int)now, + stream->byte_count, + GST_TIME_ARGS(gst->gst_pipeline->basetime + GST_BUFFER_PTS(stream->current_buffer) + offset), + (unsigned int)(gst->gst_pipeline->basetime + GST_BUFFER_PTS(stream->current_buffer) + offset), + GST_STIME_ARGS((GstClockTimeDiff)((gst->gst_pipeline->basetime + GST_BUFFER_PTS(stream->current_buffer) + offset) - now)), + GST_TIME_ARGS(gst->gst_pipeline->basetime), GST_TIME_ARGS(GST_BUFFER_PTS(stream->current_buffer)), + stream->current_size); + } + } + + if (stream->state == STREAM_STATE_CONNECTED) { + if (stream->params.format.u.s.subtype_u.cvf.subtype == CVF_FORMAT_SUBTYPE_H264) { + /* In some cases, Gstreamer will output invalid timestamp for buffers + * So use the last valid one */ + if ((stream->current_size == stream->current_info.size) && !(GST_CLOCK_TIME_IS_VALID(GST_BUFFER_PTS(stream->current_buffer)))) { + event.event_mask |= AVTP_SYNC; + event.ts = stream->last_ts; + event_n = 1; + } + + if ((stream->params.format.u.s.subtype_u.cvf.subtype == CVF_FORMAT_SUBTYPE_H264) && (stream->current_size == nbytes)) { + event.event_mask |= AVTP_FRAME_END; + event_n = 1; + } + +// printf(" %s : h264 stream sending nbytes %d event_n %d event.event_mask %x event-ts %"GST_TIME_FORMAT" GST Buffer PTS %"GST_TIME_FORMAT" GST Basetime %"GST_TIME_FORMAT"<<<<< \n", __func__, nbytes, event_n, event.event_mask, GST_TIME_ARGS((event.ts)), GST_TIME_ARGS(GST_BUFFER_PTS(stream->current_buffer)), GST_TIME_ARGS((gst->gst_pipeline->basetime))); + + rc = avb_stream_h264_send(stream->stream_h, stream->current_data, nbytes, &event, event_n); + } else { + // TODO use iov version of API + rc = avb_stream_send(stream->stream_h, stream->current_data, nbytes, &event, event_n); + } + + if (rc != nbytes) { + if (rc < 0) { + if (rc == -GENAVB_ERR_STREAM_TX_NOT_ENOUGH_DATA) { + /* This h264 specific, meaning that we sent less data than the API needs to decide on the packetization mode + * and we need to come back later (next poll) with more data (a batch size) or the full NALU*/ + rc = 0; + } else { + printf("%s failed: %s \n", + (stream->params.format.u.s.subtype_u.cvf.subtype == CVF_FORMAT_SUBTYPE_H264) ? "avb_stream_h264_send" : "avb_stream_send", + avb_strerror(rc)); + + stream->current_size = 0; + goto exit_unmap; + } + } + + printf("%s incomplete (sent %d instead of %d) \n", + (stream->params.format.u.s.subtype_u.cvf.subtype == CVF_FORMAT_SUBTYPE_H264) ? "avb_stream_h264_send" : "avb_stream_send", + rc, nbytes); + + nbytes = rc; + + /* Incomplete send, lets wait again before re-send */ + stream->byte_count += nbytes; + written += nbytes; + remaining -= nbytes; + stream->current_size -= nbytes; + stream->current_data += nbytes; + break; + } + + stream->byte_count += nbytes; + } + + written += nbytes; + remaining -= nbytes; + stream->current_size -= nbytes; + stream->current_data += nbytes; + + if (stream->current_size == 0) { + gst_buffer_unmap(stream->current_buffer, &stream->current_info); + gst_sample_unref(stream->current_sample); + stream->current_data = NULL; + } + } + + rc = written; + + goto exit; + +exit_unmap: + gst_buffer_unmap(stream->current_buffer, &stream->current_info); + +exit_unref: + gst_sample_unref(stream->current_sample); + rc = -1; + +exit: + return rc; +} + +int talker_gst_multi_fsm(struct talker_gst_media *gst, enum gst_event event) +{ + GstAppSinkCallbacks callbacks = { NULL }; + enum gst_state old_state = gst->state; + unsigned int print = 1; + unsigned int active; + unsigned int async_msg_received; + int i; + int rc = 0; + +start: + switch (gst->state) { + case GST_STATE_STARTING: + + if (gst_start_pipeline(gst->gst_pipeline, GST_PRIORITY, GST_DIRECTION_TALKER) < 0) { + printf("gst_start_pipeline() failed\n"); + rc = -1; + break; + } + + for (i = 0; i < gst->gst_pipeline->definition->num_sinks; i++) { + struct talker_gst_multi_app *stream = gst->gst_pipeline->sink[i].data; + + stream->sink = stream->gst->gst_pipeline->sink[stream->sink_index].sink; + gst_app_sink_set_max_buffers(stream->sink, 1000); + pthread_mutex_lock(&stream->samples_lock); + stream->samples = 0; + pthread_mutex_unlock(&stream->samples_lock); + callbacks.new_sample = talker_gst_multi_new_sample_handler; + gst_app_sink_set_callbacks(stream->sink, &callbacks, stream, NULL); + } + + gst->state = GST_STATE_STARTED; + /* fall through */ + + case GST_STATE_STARTED: + switch (event) { + case GST_EVENT_STOP: + /* Check that all streams are in DISCONNECTED state, then switch to STOPPED */ + active = 0; + for (i = 0; i < gst->gst_pipeline->definition->num_sinks; i++) { + struct talker_gst_multi_app *stream = gst->gst_pipeline->sink[i].data; + + if ((stream->state != STREAM_STATE_DISCONNECTED) && (stream->state != STREAM_STATE_STOPPED)) + active = 1; + } + + if (active) + break; + + gst_stop_pipeline(gst->gst_pipeline); + gst->state = GST_STATE_STOPPED; + + break; + + case GST_EVENT_LOOP: + /* Check that no stream is in CONNECTED/WAIT_FOR_DATA state, then switch to LOOP */ + active = 0; + for (i = 0; i < gst->gst_pipeline->definition->num_sinks; i++) { + struct talker_gst_multi_app *stream = gst->gst_pipeline->sink[i].data; + + if ((stream->state != STREAM_STATE_DISCONNECTED) && + (stream->state != STREAM_STATE_LOOP)) + active = 1; + } + + if (active) + break; + + gst_stop_pipeline(gst->gst_pipeline); + gst->timer_count = 0; + gst->state = GST_STATE_LOOP; + + break; + + case GST_EVENT_TIMER: + print = 0; + + /* Read and drop buffers for DISCONNECTED streams */ + for (i = 0; i < gst->gst_pipeline->definition->num_sinks; i++) { + struct talker_gst_multi_app *stream = gst->gst_pipeline->sink[i].data; + pthread_mutex_lock(&stream->gst->gst_pipeline->msg_lock); + async_msg_received = stream->gst->gst_pipeline->async_msg_received; + pthread_mutex_unlock(&stream->gst->gst_pipeline->msg_lock); + + if (stream->state == STREAM_STATE_DISCONNECTED && async_msg_received) + rc = talker_gst_multi_data_handler((struct talker_gst_multi_app *)gst->gst_pipeline->sink[i].data); + } + + break; + + case GST_EVENT_START: + break; + + default: + rc = -1; + break; + } + + break; + + case GST_STATE_STOPPED: + switch (event) { + case GST_EVENT_START: + gst->state = GST_STATE_STARTING; + goto start; + + break; + + case GST_EVENT_TIMER: + print = 0; + break; + + default: + rc = -1; + break; + } + + break; + + case GST_STATE_LOOP: + switch (event) { + case GST_EVENT_TIMER: +// printf("%s timer %d %d\n", __func__, gst->timer_count, PIPELINE_LOOP_TIMEOUT); + + /* Wait before restarting the pipeline and setting stream state */ + if (gst->timer_count++ > PIPELINE_LOOP_TIMEOUT) { + for (i = 0; i < gst->gst_pipeline->definition->num_sinks; i++) { + struct talker_gst_multi_app *stream = gst->gst_pipeline->sink[i].data; + + if (stream->state == STREAM_STATE_LOOP) + talker_gst_multi_stream_fsm(stream, STREAM_EVENT_LOOP_DONE); + } + + break; + } else + print = 0; + + break; + + case GST_EVENT_STOP: + /* Check that all streams are in DISCONNECTED state, then switch to STOPPED */ + active = 0; + for (i = 0; i < gst->gst_pipeline->definition->num_sinks; i++) { + struct talker_gst_multi_app *stream = gst->gst_pipeline->sink[i].data; + + if ((stream->state != STREAM_STATE_DISCONNECTED) && (stream->state != STREAM_STATE_STOPPED)) + active = 1; + } + + if (active) + break; + + gst->state = GST_STATE_STOPPED; + + break; + + case GST_EVENT_START: + gst->state = GST_STATE_STARTING; + goto start; + + break; + + default: + rc = -1; + break; + } + + break; + + default: + rc = -1; + break; + } + + if (print || rc < 0) + printf("%s %p old state %s current state %s event %s rc %d\n", __func__, gst, gst_state[old_state], gst_state[gst->state], gst_event[event], rc); + + return rc; +} + + +int talker_gst_multi_stream_fsm(struct talker_gst_multi_app *stream, enum stream_event event) +{ + enum stream_state old_state = stream->state; + unsigned int print = 1; + unsigned int async_msg_received; + int rc = 0; + +start: + switch (stream->state) { + case STREAM_STATE_DISCONNECTING: + stream->state = STREAM_STATE_DISCONNECTED; + + if (talker_gst_multi_fsm(stream->gst, GST_EVENT_STOP) < 0) { + rc = -1; + break; + } + /* fall through */ + + case STREAM_STATE_DISCONNECTED: + switch (event) { + case STREAM_EVENT_CONNECT: + stream->state = STREAM_STATE_CONNECTING; + goto start; + break; + + case STREAM_EVENT_TIMER: + print = 0; + break; + + case STREAM_EVENT_DISCONNECT: + break; + + default: + rc = -1; + break; + } + + break; + + case STREAM_STATE_CONNECTING: + + if (talker_gst_multi_fsm(stream->gst, GST_EVENT_START) < 0) { + rc = -1; + break; + } + + stream->byte_count = 0; + stream->count = 0; + stats_init(&stream->delay, 9, stream, stream_stats_show); + stream->state = STREAM_STATE_WAIT_START; + + break; + + case STREAM_STATE_CONNECTED: + switch (event) { + case STREAM_EVENT_TIMER: + print = 0; + break; + + case STREAM_EVENT_DATA: + print = 0; + + rc = talker_gst_multi_data_handler(stream); + + if (!rc) { + /* No data read */ + stream->state = STREAM_STATE_WAIT_DATA; + stream->timer_count = 0; + stream->stream_poll_set(stream->stream_poll_data, 0); + } else if (rc < 0) { + stream->state = STREAM_STATE_DISCONNECTING; + stream->stream_poll_set(stream->stream_poll_data, 0); + printf("[ERROR] %s: stream(%d) encountered a fatal error while writing data to stack ... Disconnect \n", __func__, stream->index); + goto start; + } + + break; + + case STREAM_EVENT_DISCONNECT: + stream->state = STREAM_STATE_DISCONNECTING; + goto start; + break; + + case STREAM_EVENT_CONNECT: + break; + + case STREAM_EVENT_STOP: + stream->state = STREAM_STATE_STOPPED; + if (talker_gst_multi_fsm(stream->gst, GST_EVENT_STOP) < 0) { + rc = -1; + break; + } + + stream->stream_poll_set(stream->stream_poll_data, 0); + talker_stream_flush(stream->stream_h, &stream->params); + break; + + case STREAM_EVENT_PLAY: + print = 0; + break; + + default: + rc = -1; + break; + } + + break; + + case STREAM_STATE_WAIT_DATA: + switch (event) { + case STREAM_EVENT_TIMER: + print = 0; + + pthread_mutex_lock(&stream->samples_lock); + rc = stream->samples; + pthread_mutex_unlock(&stream->samples_lock); + + if (rc > 0) { + stream->timer_count = 0; + stream->state = STREAM_STATE_CONNECTED; + stream->stream_poll_set(stream->stream_poll_data, 1); + } else { + stream->timer_count++; + + /* Pipeline was already launched, we are likely looping at the end + * Flush the stream ASAP,so that a "looping" listener don't get + * a late sample from previous file as a first new sample */ + if ( stream->timer_count == STREAM_FLUSH_TIMEOUT) + talker_stream_flush(stream->stream_h, &stream->params); + + if (stream->timer_count > STREAM_WAIT_DATA_TIMEOUT) { + print = 1; + + stream->state = STREAM_STATE_LOOP; + printf("Bytes sent: %llu\n", stream->byte_count); + talker_gst_multi_fsm(stream->gst, GST_EVENT_LOOP); + } + } + + break; + + case STREAM_EVENT_DISCONNECT: + stream->state = STREAM_STATE_DISCONNECTING; + goto start; + break; + + case STREAM_EVENT_STOP: + stream->state = STREAM_STATE_STOPPED; + + if (talker_gst_multi_fsm(stream->gst, GST_EVENT_STOP) < 0) { + rc = -1; + break; + } + + stream->stream_poll_set(stream->stream_poll_data, 0); + talker_stream_flush(stream->stream_h, &stream->params); + + break; + + case STREAM_EVENT_PLAY: + print = 0; + break; + + default: + rc = -1; + break; + } + + break; + + case STREAM_STATE_WAIT_START: + switch (event) { + case STREAM_EVENT_DATA: + print = 0; + stream->stream_poll_set(stream->stream_poll_data, 0); + + break; + case STREAM_EVENT_TIMER: + print = 0; + + pthread_mutex_lock(&stream->gst->gst_pipeline->msg_lock); + async_msg_received = stream->gst->gst_pipeline->async_msg_received; + pthread_mutex_unlock(&stream->gst->gst_pipeline->msg_lock); + + if (async_msg_received) { + stream->timer_count = 0; + stream->state = STREAM_STATE_WAIT_DATA; + goto start; + } else { + stream->timer_count++; + + /* Waiting for the pipeline to finish its state transition to PLAYING*/ + if (stream->timer_count > STREAM_PIPELINE_START_TIMEOUT) { + printf("[ERROR] Pipeline taking too long to go to PLAYING... stop it \n"); + print = 1; + stream->state = STREAM_STATE_STOPPED; + if (talker_gst_multi_fsm(stream->gst, GST_EVENT_STOP) < 0) { + rc = -1; + break; + } + } + } + + break; + + case STREAM_EVENT_DISCONNECT: + stream->state = STREAM_STATE_DISCONNECTING; + goto start; + break; + + case STREAM_EVENT_STOP: + stream->state = STREAM_STATE_STOPPED; + + if (talker_gst_multi_fsm(stream->gst, GST_EVENT_STOP) < 0) { + rc = -1; + break; + } + + break; + + case STREAM_EVENT_PLAY: + print = 0; + break; + + default: + rc = -1; + break; + } + + break; + + case STREAM_STATE_LOOP: + switch (event) { + case STREAM_EVENT_TIMER: + print = 0; + /* Wait until all streams connected to the same pipeline finish looping */ + break; + + case STREAM_EVENT_DISCONNECT: + stream->state = STREAM_STATE_DISCONNECTING; + goto start; + + break; + + case STREAM_EVENT_LOOP_DONE: + stream->state = STREAM_STATE_CONNECTING; + goto start; + break; + + case STREAM_EVENT_STOP: + stream->state = STREAM_STATE_STOPPED; + if (talker_gst_multi_fsm(stream->gst, GST_EVENT_STOP) < 0) { + rc = -1; + break; + } + + stream->stream_poll_set(stream->stream_poll_data, 0); + talker_stream_flush(stream->stream_h, &stream->params); + break; + + case STREAM_EVENT_PLAY: + print = 0; + break; + + default: + rc = -1; + break; + } + + break; + + case STREAM_STATE_STOPPED: + switch (event) { + case STREAM_EVENT_TIMER: + print = 0; + break; + + case STREAM_EVENT_DISCONNECT: + stream->state = STREAM_STATE_DISCONNECTING; + goto start; + break; + + case STREAM_EVENT_STOP: + print = 0; + break; + + case STREAM_EVENT_PLAY: + stream->state = STREAM_STATE_CONNECTING; + goto start; + break; + + default: + rc = -1; + break; + } + + break; + + default: + rc = -1; + break; + } + + if (print || rc < 0) + printf("%s stream(%d) old %s current %s event %s %d\n", + __func__, stream->index, + stream_state[old_state], stream_state[stream->state], stream_event[event], rc); + + return rc; +} diff --git a/apps/linux/common/gstreamer_multisink.h b/apps/linux/common/gstreamer_multisink.h new file mode 100644 index 0000000..bf1d4e2 --- /dev/null +++ b/apps/linux/common/gstreamer_multisink.h @@ -0,0 +1,150 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _GSTREAMER_MULTISINK_H_ +#define _GSTREAMER_MULTISINK_H_ + +#include +#include + +#include "gstreamer.h" +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define APP_MAX_SUPPORTED_STREAMS 3 +#define APP_MAX_ACTIVE_STREAMS 3 + +#define STR(a) [a] = #a + +#define OPT_TYPE_DEFAULT (1 << 0) +#define OPT_TYPE_PREVIEW (1 << 1) +#define OPT_TYPE_VIDEO_ONLY (1 << 2) + +#define GST_PRIORITY 1 + +#define POLL_TIMEOUT_MS 10 +#define PIPELINE_LOOP_TIMEOUT (4000/POLL_TIMEOUT_MS) +#define STREAM_WAIT_DATA_TIMEOUT (1000/POLL_TIMEOUT_MS + 1) +#define STREAM_PIPELINE_START_TIMEOUT (10000/POLL_TIMEOUT_MS + 1) +#define STREAM_FLUSH_TIMEOUT (200/POLL_TIMEOUT_MS) +#define PREVNEXT_STOP_DURATION (1) // Stop stream and wait 1s when changing tracks before starting stream again + +#define MAX_GST_MULTI_HANDLERS 1 +#define MAX_GST_MULTI_HANDLERS_STREAMS (MAX_GST_MULTI_HANDLERS * GST_MAX_SINKS) + +enum gst_state { + GST_STATE_STARTING, /**< transition state to GST_STATE_STARTED */ + GST_STATE_STARTED, /**< pipeline is started. At least one stream is started. */ + GST_STATE_STOPPED, /**< pipeline is stopped. All streams are stopped. */ + GST_STATE_LOOP /**< pipeline is stopped. All streams are looping. Waiting for timeout before switching to GST_STATE_STARTING. */ +}; + +enum stream_state { + STREAM_STATE_CONNECTING, /**< transition state to CONNECTED */ + STREAM_STATE_CONNECTED, /**< avb stream is open and being polled, actively reading from media stack */ + STREAM_STATE_DISCONNECTING, /**< transition state to DISCONNECTED */ + STREAM_STATE_DISCONNECTED, /**< avb stream is closed, not reading from media stack */ + STREAM_STATE_WAIT_DATA, /**< avb stream is open but not polled, actively reading from media stack (based on a timer). On timeout transition to STREAM_STATE_LOOP */ + STREAM_STATE_WAIT_START, /**< avb stream is open but not polled, waiting for the pipeline to finish its start. On aync msg reception from pipeline, transition to STREAM_STATE_WAIT_DATA or on timeout expiration transition to STREAM_STATE_DISCONNECTED */ + STREAM_STATE_LOOP, /**< avb stream is open but not polled, not reading from media stack. On timeout, loop media file and transition to STREAM_STATE_CONNECTING */ + STREAM_STATE_STOPPED /**< avb stream is open but stopped, not reading from media stack. On play, transition to STREAM_STATE_CONNECTING. */ +}; + +enum stream_event { + STREAM_EVENT_CONNECT, /**< Connect event from control handler */ + STREAM_EVENT_DISCONNECT, /**< Disconnect event from control handler */ + STREAM_EVENT_DATA, /**< Data event from stream file descriptor */ + STREAM_EVENT_TIMER, /**< Timer event from thread loop */ + STREAM_EVENT_LOOP_DONE, /**< Loop done event from pipeline state machine */ + STREAM_EVENT_PLAY, /**< Play event from external AVDECC controller */ + STREAM_EVENT_STOP /**< Stop event from external AVDECC controller */ +}; + +enum gst_event { + GST_EVENT_START, /**< start event from stream state machine */ + GST_EVENT_STOP, /**< stop event from stream state machine */ + GST_EVENT_LOOP, /**< loop event from gst state machine, when all streams are in looping state */ + GST_EVENT_TIMER /**< timer event from thread loop timeout */ +}; + +struct talker_gst_multi_app { //TODO: merge with struct media_stream + struct talker_gst_media *gst; + int index; + + unsigned int sink_index; + + unsigned int audio; + + GstAppSink *sink; + GstSample *current_sample; + GstBuffer *current_buffer; + GstMapInfo current_info; + void *current_data; + unsigned int current_size; + unsigned long long samples; + unsigned int last_ts; /*Used for H264 Stream : + Gstreamer sometimes output buffers with an invalid PTS , so use the last valid one */ + + unsigned int timer_count; + + struct stats delay; + unsigned long long byte_count; + unsigned int count; + + enum stream_state state; + + unsigned int ts_parser_enabled; + + /* Handler for stream poll set*/ + void (*stream_poll_set) (void *, int); + void *stream_poll_data; + + struct avb_stream_handle *stream_h; + struct avb_stream_params params; + unsigned int batch_size; + pthread_mutex_t samples_lock; +}; + +struct talker_gst_media { //TODO: merge with struct media_stream and talker_gst_multi_app + struct gstreamer_pipeline *gst_pipeline; + + pthread_mutex_t stream_lock; + + enum gst_state state; + + unsigned int timer_count; +}; + +struct gstreamer_talker_multi_handler { + int configured_streams; + int connected_streams; + struct talker_gst_media *gst_media; + struct gstreamer_pipeline gst_pipeline; + int timer_fd; + void *thread_timer; + struct talker_gst_multi_app *multi_app[GST_MAX_SINKS]; + struct gstreamer_stream *talkers[GST_MAX_SINKS]; +}; + +struct gstreamer_listener_multi_handler { + int configured_streams; + int type; + struct gstreamer_pipeline gst_pipeline; +}; + +int talker_gst_multi_data_handler(struct talker_gst_multi_app *stream); +int talker_gst_multi_stream_fsm(struct talker_gst_multi_app *stream, enum stream_event event); +int talker_gst_multi_fsm(struct talker_gst_media *gst, enum gst_event event); + +#ifdef __cplusplus +} +#endif + +#endif /* _GSTREAMER_MULTISINK_H_ */ diff --git a/apps/linux/common/gstreamer_single.c b/apps/linux/common/gstreamer_single.c new file mode 100644 index 0000000..6ec39b1 --- /dev/null +++ b/apps/linux/common/gstreamer_single.c @@ -0,0 +1,835 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include "gst_pipeline_definitions.h" +#include "gstreamer_single.h" + + +void stream_init_stats(struct gstreamer_stream *stream) +{ + stats_init(&stream->stream_stats.write_delay, 31, stream, NULL); + stats_init(&stream->stream_stats.delay, 31, stream, NULL); + stats_init(&stream->stream_stats.period, 31, stream, NULL); + stats_init(&stream->stream_stats.rate, 31, stream, NULL); + stats_init(&stream->stream_stats.mpeg_ts.pcr_delay, 31, stream, NULL); + stats_init(&stream->stream_stats.mpeg_ts.pcr_period, 31, stream, NULL); + + stream->stream_stats.pkt_lost = 0; + stream->stream_stats.ts_err = 0; + stream->stream_stats.mr = 0; + stream->started = 0; + stream->pipe_source.byte_count = 0; + stream->pipe_source.dropping = 0; + stream->stream_stats.byte_count_prev = 0; + gettime_s_monotonic(&stream->pipe_source.tlast); + stream->ts_parser.pcr_count = 0; + stream->pipe_source.count = 0; + stream->pipe_source.late_count = 0; + stream->pipe_source.ontime_count = 0; +} + +void stream_update_stats(struct gstreamer_stream *stream, unsigned long long byte_count, unsigned long long now, unsigned long long buffer_pts) +{ + int period = 0, rate = 0; + + stats_update(&stream->stream_stats.delay, (int)(buffer_pts - now)); + + if (byte_count) { + period = buffer_pts - stream->stream_stats.buffer_pts_prev; + + if (period) { + rate = ((byte_count - stream->stream_stats.byte_count_prev) * NSECS_PER_SEC) / period; + + stats_update(&stream->stream_stats.period, period); + + stats_update(&stream->stream_stats.rate, rate); + + stream->stream_stats.buffer_pts_prev = buffer_pts; + stream->stream_stats.byte_count_prev = byte_count; + } + } else { + stream->stream_stats.buffer_pts_prev = buffer_pts; + stream->stream_stats.byte_count_prev = byte_count; + } + + if ((stream->stream_stats.delay.current_count < 100) && (stream->pipe_source.byte_count < 1000000)) { + printf("now: %"GST_TIME_FORMAT"(%u) count: %llu ts: %"GST_TIME_FORMAT"(%u) delay: %"GST_STIME_FORMAT" period: %d rate: %d\n", + GST_TIME_ARGS(now), (unsigned int)now, + byte_count, + GST_TIME_ARGS(buffer_pts), (unsigned int)buffer_pts, + GST_STIME_ARGS((long long)buffer_pts - (long long)now), + period, + rate); + } +} + +void stream_dump_stats(struct gstreamer_stream *stream) +{ + stats_compute(&stream->stream_stats.write_delay); + stats_compute(&stream->stream_stats.delay); + stats_compute(&stream->stream_stats.period); + stats_compute(&stream->stream_stats.rate); + + printf("stream(%u): write delay(ns): %7d/%7d/%7d delay(ns): %9d/%9d/%9d period(ns): %9d/%9d/%9d rate(bytes/s): %7d/%7d/%7d pkt_lost: %7llu ts_err: %7llu mr: %7llu\n", stream->source_index, + stream->stream_stats.write_delay.min, stream->stream_stats.write_delay.mean, stream->stream_stats.write_delay.max, + stream->stream_stats.delay.min, stream->stream_stats.delay.mean, stream->stream_stats.delay.max, + stream->stream_stats.period.min, stream->stream_stats.period.mean, stream->stream_stats.period.max, + stream->stream_stats.rate.min, stream->stream_stats.rate.mean, stream->stream_stats.rate.max, + stream->stream_stats.pkt_lost, stream->stream_stats.ts_err, stream->stream_stats.mr + ); + + stats_reset(&stream->stream_stats.write_delay); + stats_reset(&stream->stream_stats.delay); + stats_reset(&stream->stream_stats.period); + stats_reset(&stream->stream_stats.rate); + + if (avdecc_format_is_61883_4(&stream->params.format)) { + stats_compute(&stream->stream_stats.mpeg_ts.pcr_delay); + stats_compute(&stream->stream_stats.mpeg_ts.pcr_period); + + printf("stream(%u): pcr delay(ns): (%lld) %7d/%7d/%7d period(ns): %7d/%7d/%7d\n", stream->source_index, + stream->ts_parser.buffer_pts0 - stream->ts_parser.pcr0, + stream->stream_stats.mpeg_ts.pcr_delay.min, stream->stream_stats.mpeg_ts.pcr_delay.mean, stream->stream_stats.mpeg_ts.pcr_delay.max, + stream->stream_stats.mpeg_ts.pcr_period.min, stream->stream_stats.mpeg_ts.pcr_period.mean, stream->stream_stats.mpeg_ts.pcr_period.max + ); + + stats_reset(&stream->stream_stats.mpeg_ts.pcr_delay); + stats_reset(&stream->stream_stats.mpeg_ts.pcr_period); + } +} + +void dump_stream_infos(struct gstreamer_stream *stream) +{ + print_stream_id(stream->params.stream_id); + + printf("mode: %s\n", (stream->params.direction == AVTP_DIRECTION_LISTENER)? "LISTENER":"TALKER"); +} + +void stream_61883_4_update_stats(struct gstreamer_stream *stream, unsigned char *buf, unsigned long long buffer_pts) +{ + unsigned long long pcr; + int period; + + if (ts_parser_is_pcr(buf, &pcr)) { + + if (!stream->ts_parser.pcr_count) { + stream->ts_parser.pcr0 = pcr; + stream->ts_parser.buffer_pts0 = buffer_pts; + } + + stats_update(&stream->stream_stats.mpeg_ts.pcr_delay, (pcr - stream->ts_parser.pcr0) - (buffer_pts - stream->ts_parser.buffer_pts0)); + + if (stream->ts_parser.pcr_count > 1) { + period = pcr - stream->stream_stats.mpeg_ts.pcr_prev; + stats_update(&stream->stream_stats.mpeg_ts.pcr_period, period); + } else + period = 0; + + if (stream->ts_parser.pcr_count < 10) { + printf("pcr(ns): %llu(%lld) ts(ns): %llu(%lld) pcr delay(ns): %lld period(ns): %d\n", + stream->ts_parser.pcr0, pcr - stream->ts_parser.pcr0, + stream->ts_parser.buffer_pts0, buffer_pts - stream->ts_parser.buffer_pts0, + (pcr - stream->ts_parser.pcr0) - (buffer_pts - stream->ts_parser.buffer_pts0), period); + } + + stream->stream_stats.mpeg_ts.pcr_prev = pcr; + stream->ts_parser.pcr_count++; + } +} + + +#define CVF_MJPEG_SPLASH_CAPTURE 0 + +#if CVF_MJPEG_SPLASH_CAPTURE +/* Splash screen file format: records of 32-bit frame_size followed by frame_size bytes + */ +#define CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD CVF_MJPEG_SPLASH_MAX_FRAME_SIZE +#define CVF_MJPEG_CAPTURE_FILENAME "/tmp/cvf_mjpeg_splash_screen.mjpg" + +static void cvf_mjpeg_splash_capture(struct gstreamer_stream *stream) +{ + static unsigned int capture_splash_previous_frame_size = 0; + static int capture_splash_fd = -1; + unsigned int frame_size = stream->pipe_source.buffer_byte_count; + + // Only save the splash screen from the first camera + if (stream->source_index == 0) { + + // Start a new splash screen + if ((capture_splash_previous_frame_size > CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD) && (frame_size < CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD)) { + printf("Starting splash screen\n"); + capture_splash_fd = open(CVF_MJPEG_CAPTURE_FILENAME, O_WRONLY | O_CREAT | O_TRUNC); + if (capture_splash_fd < 0) + printf("stream(%u): Could not open capture file\n", stream->source_index); + } + + // Continuing the current splash screen + if ((frame_size < CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD) && (capture_splash_fd >= 0)) { + write(capture_splash_fd, &frame_size, 4); + write(capture_splash_fd, stream->pipe_source.info.data, frame_size); + } + + // Close the current splash screen + if ((capture_splash_previous_frame_size < CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD) && (frame_size > CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD)) { + close(capture_splash_fd); + printf("Closing splash screen\n"); + } + + capture_splash_previous_frame_size = frame_size; + //printf("stream(%d) frame size %d\n", stream->source_index, stream->gst_stream.buffer_byte_count); + } + +} +#endif + +static int cvf_mjpeg_splash_read_frame(int fd, unsigned char *buf, unsigned int *frame_size) +{ + int rc; + + /*Read first 4 bytes containing the frame size*/ + rc = read(fd, frame_size, 4); + if ( rc != 4 ) { + printf("Can not read frame size (%d, expected 4)\n", rc); + return -1; + } + + if (*frame_size > CVF_MJPEG_SPLASH_MAX_FRAME_SIZE) { + printf("Frame size read (%u) exceeding max %u \n", *frame_size, CVF_MJPEG_SPLASH_MAX_FRAME_SIZE); + return -1; + } + + rc = read(fd, buf, *frame_size); + if (rc != *frame_size) { + printf("Short read while warming up Gstreamer pipeline (%d, expected %d)\n", rc, *frame_size); + return -1; + } + + return 0; +} + +int gst_cvf_mjpeg_warm_up_pipeline(struct gstreamer_pipeline *gst, unsigned int nframes) +{ + GstBuffer *buffer; + GstMapInfo info; + GstState state = GST_STATE_NULL; + unsigned char *data_buf; + int splash_fd; + int rc = 0; + unsigned char *buf; + unsigned int frame_size, i, count; + unsigned long long gsttime = 0; + + printf("Warming up pipeline\n"); + + buf = malloc(CVF_MJPEG_SPLASH_MAX_FRAME_SIZE); + if (!buf) { + printf("Couldn't not allocate buffer to warm up Gstreamer pipeline\n"); + return -1; + } + + splash_fd = open(CVF_MJPEG_SPLASH_FILENAME, O_RDONLY); + if (splash_fd < 0) { + printf("Couldn't not warm up Gstreamer pipeline: %s(%d)\n", strerror(errno), errno); + rc = 0; + goto err_open; + } + + + // Wait for pipeline to become ready. + rc = gst_element_get_state(gst->pipeline, &state, NULL, 0); + while ((state != GST_STATE_PLAYING) && (state != GST_STATE_PAUSED)) { + usleep(10000); // 10ms + rc = gst_element_get_state(gst->pipeline, &state, NULL, 0); + + } + printf("Pipeline ready (state %d)\n", state); + + gsttime = gst_clock_get_time(gst->clock) - gst_element_get_base_time(GST_ELEMENT(gst->pipeline)); + + if (cvf_mjpeg_splash_read_frame(splash_fd, buf, &frame_size) < 0) { + printf("Error reading first frame \n"); + rc = -1; + goto exit; + } + + count = 0; + while (count < nframes) { + printf ("Reading frame %d ts %llu base_time %" G_GUINT64_FORMAT " now %" G_GUINT64_FORMAT "\n", + count, gsttime, gst_element_get_base_time(GST_ELEMENT(gst->pipeline)), gst_clock_get_time(gst->clock)); + + if (cvf_mjpeg_splash_read_frame(splash_fd, buf, &frame_size) < 0) { + printf("Error reading frame %d \n", count); + rc = -1; + goto exit; + } + + for (i = 0; i < gst->definition->num_sources; i++) { + /* Create a new empty buffer */ + buffer = gst_buffer_new_allocate(NULL, frame_size, NULL); + if (!buffer) { + printf("Couldn't allocate Gstreamer buffer\n"); + rc = -1; + goto exit; + } + + buffer = gst_buffer_make_writable(buffer); + + if (gst_buffer_map(buffer, &info, GST_MAP_WRITE)) + data_buf = info.data; + else + data_buf = NULL; + + if (!data_buf) { + printf("Couldn't get data buffer\n"); + + gst_buffer_unref(buffer); + + rc = -1; + goto exit; + } + + /* copy data from file + */ + memcpy(data_buf, buf, frame_size); + gst_buffer_unmap(buffer, &info); + gst_buffer_set_size(buffer, frame_size); + + + GST_BUFFER_PTS(buffer) = gsttime + MJPEG_PIPELINE_LATENCY; // Arbitrary delay + + rc = gst_app_src_push_buffer(gst->source[i].source, buffer); + if (rc != GST_FLOW_OK) { + if (rc == GST_FLOW_FLUSHING) + printf("Pipeline not in PAUSED or PLAYING state\n"); + else + printf("End-of-Stream occurred\n"); + + rc = -1; + goto exit; + } else + rc = 1; + } + + gsttime += NSECS_PER_SEC / CVF_MJPEG_SPLASH_FPS; + usleep(USECS_PER_SEC / CVF_MJPEG_SPLASH_FPS); + count++; + } + + + +exit: + close(splash_fd); +err_open: + free(buf); + + return rc; +} + +#define CVF_TS_VALID_WINDOW 35000000 // 35ms +int listener_gst_handler_cvf(struct gstreamer_stream *stream, unsigned int events) +{ + struct gstreamer_pipeline *gst = stream->pipe_source.gst_pipeline; + unsigned int event_len; + struct avb_event event[EVENT_BUF_SZ]; + int nbytes; + int rc = 1; + unsigned long long gsttime; + unsigned long long base_ts; + avb_s32 ts_diff; + unsigned int pushed = 0; + uint64_t start = 0, end = 0; + time_t tnow; + + tnow = time(NULL); + if ((tnow - stream->pipe_source.tlast) > 10) { + stream_dump_stats(stream); + stream->pipe_source.tlast = tnow; + } + + gettime_ns(&start); + + while (pushed < stream->batch_size) { + if (!(stream->pipe_source.count % 10000)) + printf("stream(%u): bytes: %llu\n", stream->source_index, stream->pipe_source.byte_count); + + stream->pipe_source.count++; + + /* Create a new empty buffer if needed */ + if (!stream->pipe_source.buffer) { + stream->pipe_source.buffer = gst_buffer_new(); + if (!stream->pipe_source.buffer) { + printf("stream(%u): Couldn't allocate Gstreamer buffer\n", stream->source_index); + //gst_memory_unref(memory); + rc = -1; + goto exit; + } + } + + if (!stream->pipe_source.memory) { + stream->pipe_source.memory = gst_allocator_alloc(NULL, GST_MEMORY_OBJ_SIZE, NULL); + if (!stream->pipe_source.memory) { + printf("stream(%u): Couldn't allocate Gstreamer memory object\n", stream->source_index); + gst_buffer_unref(stream->pipe_source.buffer); + rc = -1; + goto exit; + } + + if (gst_memory_map(stream->pipe_source.memory, &stream->pipe_source.info, GST_MAP_WRITE)) + stream->pipe_source.data_buf = stream->pipe_source.info.data; + else + stream->pipe_source.data_buf = NULL; + + if (!stream->pipe_source.data_buf) { + printf("stream(%u): Couldn't get data buffer\n", stream->source_index); + gst_buffer_unref(stream->pipe_source.buffer); + gst_memory_unref(stream->pipe_source.memory); + rc = -1; + goto exit; + } + + gst_buffer_append_memory(stream->pipe_source.buffer, stream->pipe_source.memory); + } + + stream->pipe_source.buffer = gst_buffer_make_writable(stream->pipe_source.buffer); + + + /* read data from stack... + * TODO: make a single call to genavb lib with an iovec of gst buffers + */ + event_len = EVENT_BUF_SZ; + nbytes = avb_stream_receive(stream->stream_h, stream->pipe_source.data_buf, GST_MEMORY_OBJ_SIZE - stream->pipe_source.memory_byte_count, event, &event_len); + //printf("Received %d bytes from genavb ... \n", nbytes); + + if (nbytes < 0) { + printf("stream(%u): avb_stream_receive() failed: %s\n", stream->source_index, avb_strerror(nbytes)); + + stream->pipe_source.buffer_byte_count += stream->pipe_source.memory_byte_count; + gst_memory_unmap(stream->pipe_source.memory, &stream->pipe_source.info); + gst_buffer_unref(stream->pipe_source.buffer); + stream->pipe_source.buffer_byte_count = 0; + stream->pipe_source.memory_byte_count = 0; + stream->pipe_source.buffer = NULL; + stream->pipe_source.memory = NULL; + + rc = nbytes; + goto exit; + } + if (nbytes == 0) { + goto exit; + } + + stream->data_received = 1; + + if (!stream->pipe_source.dropping) { + pushed += nbytes; + stream->pipe_source.data_buf += nbytes; + stream->pipe_source.memory_byte_count += nbytes; + } + + gsttime = gst_clock_get_time(gst->clock); + if (gsttime == gst->time) { + printf("ERROR: Clock jumped into the past, Gstreamer pipeline likely stalled...\n"); + //FIXME + } + gst->time = gsttime; + + /* FIXME ? Doesn't filter packets that are too late or too early, which usually occur when gptp has been disrupted*/ + if (event_len != 0) { + // printf("stream(%u) event_len(%d) mask0 0x%x maskn 0x%x size %d nbytes %d\n",stream->source_index, + // event_len, event[0].event_mask, event[event_len - 1].event_mask, stream->gst_stream.buffer_byte_count, nbytes); + + if (event[0].event_mask & AVTP_MEDIA_CLOCK_RESTART) { + stream->pipe_source.dropping = 1; + stream->stream_stats.mr++; + } + + if (event[0].event_mask & AVTP_PACKET_LOST) { + stream->pipe_source.dropping = 1; + stream->stream_stats.pkt_lost++; + } + + if (event[event_len - 1].event_mask & AVTP_END_OF_FRAME) { + stream->pipe_source.buffer_byte_count += stream->pipe_source.memory_byte_count; +#if CVF_MJPEG_SPLASH_CAPTURE + cvf_mjpeg_splash_capture(stream); +#endif + + gst_memory_unmap(stream->pipe_source.memory, &stream->pipe_source.info); + gst_memory_resize(stream->pipe_source.memory, 0, stream->pipe_source.memory_byte_count); + gst_buffer_set_size(stream->pipe_source.buffer, stream->pipe_source.buffer_byte_count); + + if (!(event[event_len - 1].event_mask & AVTP_TIMESTAMP_INVALID)) { + ts_diff = (avb_s32)event[event_len - 1].ts - (avb_s32)(gsttime & 0xffffffff) + SALSA_LATENCY; + if ((stream->params.format.u.s.subtype_u.cvf.subtype == CVF_FORMAT_SUBTYPE_H264) || ((ts_diff > (-CVF_TS_VALID_WINDOW / 2)) && (ts_diff < (CVF_TS_VALID_WINDOW / 2)))) { + base_ts = gsttime & 0xffffffff00000000; + GST_BUFFER_PTS(stream->pipe_source.buffer) = base_ts | event[event_len - 1].ts; + /* Handle 32bit wrap */ + if (avtp_after(event[event_len - 1].ts, gsttime & 0xffffffff)) { + /* Timestamp in the future */ + if ((event[event_len - 1].ts < (gsttime & 0xffffffff))) + GST_BUFFER_PTS(stream->pipe_source.buffer) += 0x100000000ULL; + } else { + /* Timestamp in the past */ + if ((event[event_len - 1].ts > (gsttime & 0xffffffff))) + GST_BUFFER_PTS(stream->pipe_source.buffer) -= 0x100000000ULL; + } + } else { + GST_BUFFER_PTS(stream->pipe_source.buffer) = gsttime - stream->pipe_source.gst_pipeline->listener.local_pts_offset; + stream->stream_stats.ts_err++; + } + } + + if (event[event_len - 1].event_mask & (AVTP_TIMESTAMP_INVALID | AVTP_TIMESTAMP_UNCERTAIN)) { + GST_BUFFER_PTS(stream->pipe_source.buffer) = gsttime - stream->pipe_source.gst_pipeline->listener.local_pts_offset; + stream->stream_stats.ts_err++; + } + + GST_BUFFER_PTS(stream->pipe_source.buffer) += stream->pipe_source.gst_pipeline->listener.local_pts_offset; + + stream_update_stats(stream, stream->pipe_source.byte_count, gsttime, GST_BUFFER_PTS(stream->pipe_source.buffer)); + + if (!GST_CLOCK_TIME_IS_VALID(gst->basetime)) + gst->basetime = gst_element_get_base_time(GST_ELEMENT(gst->pipeline)); + + if (GST_BUFFER_PTS(stream->pipe_source.buffer) < gst->basetime) { + printf("stream(%u): frame in the past, dropping (frame %" G_GUINT64_FORMAT ", basetime %" G_GUINT64_FORMAT " now %llu)\n", + stream->source_index, GST_BUFFER_PTS(stream->pipe_source.buffer), gst->basetime, gsttime); + stream->pipe_source.dropping = 1; + rc = 1; + } else { + GST_BUFFER_PTS(stream->pipe_source.buffer) -= gst->basetime; + } + + stream->pipe_source.byte_count += stream->pipe_source.buffer_byte_count; + + // printf("stream(%u): Handling frame with PTS %llu, basetime %llu now %llu size %d dropping %d)\n", + // stream->source_index, GST_BUFFER_PTS(stream->gst_stream.buffer), gst->basetime, gsttime, stream->gst_stream.buffer_byte_count, stream->gst_stream.dropping); + + if (stream->params.format.u.s.subtype_u.cvf.subtype != CVF_FORMAT_SUBTYPE_H264 && ((gsttime - stream->previous_frame_time) < 25000000)) + stream->pipe_source.dropping = 1; + if (!stream->pipe_source.dropping) { + stream->previous_frame_time = gst->time; + GST_BUFFER_DURATION(stream->pipe_source.buffer) = GST_CLOCK_TIME_NONE; + rc = gst_app_src_push_buffer(stream->pipe_source.source, stream->pipe_source.buffer); + //printf("stream(%d): Pushed %d bytes to Gstreamer ts %llu ts_diff %d\n", stream->source_index, stream->pipe_source.buffer_byte_count, GST_BUFFER_PTS(stream->pipe_source.buffer), ts_diff); + + if (rc != GST_FLOW_OK) { + if (rc == GST_FLOW_FLUSHING) + printf("stream(%u): Pipeline not in PAUSED or PLAYING state\n", stream->source_index); + else + printf("stream(%u): End-of-Stream occurred\n", stream->source_index); + + rc = -1; + } else { + rc = 1; + } + } else { + gst_buffer_unref(stream->pipe_source.buffer); + stream->pipe_source.dropping = 0; + } + + stream->pipe_source.buffer_byte_count = 0; + stream->pipe_source.memory_byte_count = 0; + stream->pipe_source.buffer = NULL; + stream->pipe_source.memory = NULL; + + goto exit; // We reached an end-of-frame, so there is not much data left and we might as well go back to sleep until another full batch. + + } + } + + if (stream->pipe_source.memory_byte_count >= (GST_MEMORY_OBJ_SIZE - MAX_PKT_SIZE_CVF)) { + gst_memory_unmap(stream->pipe_source.memory, &stream->pipe_source.info); + gst_memory_resize(stream->pipe_source.memory, 0, stream->pipe_source.memory_byte_count); + stream->pipe_source.buffer_byte_count += stream->pipe_source.memory_byte_count; + stream->pipe_source.memory_byte_count = 0; + stream->pipe_source.memory = NULL; + printf("stream(%u) Current buffer byte count: %d\n", stream->source_index, stream->pipe_source.buffer_byte_count); + } + } + +exit: + gettime_ns(&end); + + stats_update(&stream->stream_stats.write_delay, end - start); + + return rc; +} + +int listener_gst_handler(struct gstreamer_stream *stream, unsigned int events) +{ + struct gstreamer_pipeline *gst = stream->pipe_source.gst_pipeline; + unsigned int event_len; + GstBuffer *buffer; + GstMapInfo info; + unsigned char *data_buf; + struct avb_event event[EVENT_BUF_SZ]; + int nbytes; + int rc = 0; + unsigned long long base_ts; + unsigned long long gsttime; + unsigned int pushed = 0; + uint64_t start = 0, end = 0, tnow; + int delta; + uint64_t ts_offset; + + gettime_ns_monotonic(&start); + tnow = start / NSECS_PER_SEC; + if ((tnow - stream->pipe_source.tlast) > 10) { + stream_dump_stats(stream); + stream->pipe_source.tlast = tnow; + } + + while (pushed != stream->batch_size) { + if (!(stream->pipe_source.count % 10000)) + printf("bytes: %llu\n", stream->pipe_source.byte_count); + + stream->pipe_source.count++; + + /* Create a new empty buffer */ + buffer = gst_buffer_new_allocate(NULL, stream->frame_size, NULL); + if (!buffer) { + printf("Couldn't allocate Gstreamer buffer\n"); + rc = -1; + goto exit; + } + + buffer = gst_buffer_make_writable(buffer); + + if (gst_buffer_map(buffer, &info, GST_MAP_WRITE)) + data_buf = info.data; + else + data_buf = NULL; + + if (!data_buf) { + printf("Couldn't get data buffer\n"); + + gst_buffer_unref(buffer); + + rc = -1; + goto exit; + } + + /* read data from stack... + * TODO: make a single call to genavb lib with an iovec of gst buffers + */ + event_len = EVENT_BUF_SZ; + nbytes = avb_stream_receive(stream->stream_h, data_buf, stream->frame_size, event, &event_len); + gst_buffer_unmap(buffer, &info); + if (nbytes <= 0) { + if (nbytes < 0) + printf("avb_stream_receive() failed: %s\n", avb_strerror(nbytes)); + else + printf("avb_stream_receive() incomplete\n"); + + gst_buffer_unref(buffer); + + rc = nbytes; + goto exit; + } + + stream->data_received = 1; + + if (nbytes != stream->frame_size) + printf("Short read: GenAVB returned less data (%d) than requested (%d).\n", nbytes, stream->frame_size); + + gst_buffer_set_size(buffer, nbytes); + + if (event[0].event_mask & AVTP_MEDIA_CLOCK_RESTART) + printf ("AVTP media clock restarted\n"); + + if (event[0].event_mask & AVTP_PACKET_LOST) + printf ("AVTP packet lost\n"); + + if (!(event[0].event_mask & (AVTP_TIMESTAMP_INVALID | AVTP_TIMESTAMP_UNCERTAIN))) { + gsttime = gst_clock_get_time(gst->clock); + if (gsttime == gst->time) { + printf("Error: clock likely jumped into the past, restarting pipeline\n"); + gst_buffer_unref(buffer); + + gst_stop_pipeline(gst); + gst_start_pipeline(gst, GST_PRIORITY, GST_DIRECTION_LISTENER); + rc = -1; + goto exit; + } else + gst->time = gsttime; + base_ts = gsttime & 0xffffffff00000000; + + if (!GST_CLOCK_TIME_IS_VALID(gst->basetime)) + gst->basetime = gst_element_get_base_time(GST_ELEMENT(gst->pipeline)); + + /*Calculate the ts shift in ns based on the timestamp index for ieee61888_6 format*/ + if (avdecc_format_is_61883_6(&stream->params.format)){ + ts_offset = gst_util_uint64_scale (event[0].index, GST_SECOND, avdecc_fmt_sample_rate(&stream->params.format) * avdecc_fmt_sample_size(&stream->params.format)); + event[0].ts -= ts_offset; + } + + delta = (int)event[0].ts - (int)(gsttime & 0xffffffff); + + /* Filter packets that are too late or too early, which usually occur when gptp has been disrupted */ + if ((stream->pipe_source.dropping && ((delta < -AVTP_TS_MIN_DELTA) || (delta > AVTP_TS_MAX_DELTA))) || + (!stream->pipe_source.dropping && ((delta < -AVTP_TS_MIN_DELTA_STOP) || (delta > AVTP_TS_MAX_DELTA_STOP))) + ) { + stream->pipe_source.late_count++; + stream->pipe_source.ontime_count = 0; + + if (!stream->pipe_source.dropping) { + printf("stop playing, late buffer %"GST_TIME_FORMAT" %"GST_TIME_FORMAT" %"GST_STIME_FORMAT"\n", + GST_TIME_ARGS(event[0].ts), GST_TIME_ARGS((unsigned int)(gsttime & 0xffffffff)), GST_STIME_ARGS(delta)); + + stream->pipe_source.dropping = 1; + } + + gst_buffer_unref(buffer); + + goto exit; + } + + stream->pipe_source.ontime_count++; + + if (stream->pipe_source.dropping) { + if (stream->pipe_source.ontime_count > 1000) { + printf("start playing %llu\n", stream->pipe_source.late_count); + + stream->pipe_source.late_count = 0; + stream->pipe_source.dropping = 0; + } else { + gst_buffer_unref(buffer); + + goto exit; + } + } + + // TODO Handle event.index + GST_BUFFER_PTS(buffer) = base_ts | event[0].ts; + + /* Handle 32bit wrap */ + if (avtp_after(event[0].ts, gsttime & 0xffffffff)) { + /* Timestamp in the future */ + if ((event[0].ts < (gsttime & 0xffffffff))) + GST_BUFFER_PTS(buffer) += 0x100000000ULL; + } else { + /* Timestamp in the past */ + if ((event[0].ts > (gsttime & 0xffffffff))) + GST_BUFFER_PTS(buffer) -= 0x100000000ULL; + } + + stream_update_stats(stream, stream->pipe_source.byte_count + event[0].index, gsttime, GST_BUFFER_PTS(buffer)); + + if (avdecc_format_is_61883_4(&stream->params.format)) + stream_61883_4_update_stats(stream, data_buf, GST_BUFFER_PTS(buffer)); + + GST_BUFFER_PTS(buffer) += stream->pipe_source.gst_pipeline->listener.local_pts_offset; + + GST_BUFFER_PTS(buffer) -= gst->basetime; + } + + // TODO: with Gstreamer 1.x, pass a list of buffers in a single call + rc = gst_app_src_push_buffer(stream->pipe_source.source, buffer); + if (rc != GST_FLOW_OK) { + if (rc == GST_FLOW_FLUSHING) + printf("Pipeline not in PAUSED or PLAYING state\n"); + else + printf("End-of-Stream occurred\n"); + + rc = -1; + goto exit; + } else + rc = 1; + + pushed += nbytes; + stream->pipe_source.byte_count += nbytes; + } +exit: + gettime_ns_monotonic(&end); + + stats_update(&stream->stream_stats.write_delay, end - start); + + if (rc == 0) + rc = pushed; + + return rc; +} + +void apply_stream_params(struct gstreamer_stream *stream, struct avb_stream_params *stream_params) +{ + memcpy(&stream->params, stream_params, sizeof(struct avb_stream_params)); + + + if (stream_params->direction == AVTP_DIRECTION_TALKER) + stream->params.talker.latency = max(CFG_TALKER_LATENCY_NS, sr_class_interval_p(stream->params.stream_class) / sr_class_interval_q(stream->params.stream_class)); + + stream->params.clock_domain = AVB_CLOCK_DOMAIN_0; + + printf("%s : Setting %s to AVB Clock Domain: AVB_CLOCK_DOMAIN_0 (Received parameters %d) \n", __func__, (stream_params->direction == AVTP_DIRECTION_TALKER) ? "TALKER" : "LISTENER", stream_params->clock_domain); + + stream->flags = AVTP_NONBLOCK; + stream->previous_frame_time = 0; + + if (!avdecc_format_is_61883_6(&stream_params->format)) + stream->params.flags &= ~AVB_STREAM_FLAGS_MCR; + + switch (stream_params->format.u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + stream->batch_size = BATCH_SIZE; + if (stream_params->format.u.s.subtype_u.iec61883.sf == 0) { + printf("Unsupported 61883_IIDC format\n"); + break; + } else + switch (stream_params->format.u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_6: + stream->frame_size = stream->batch_size; + break; + + case IEC_61883_CIP_FMT_4: + stream->frame_size = avdecc_fmt_sample_size(&stream_params->format); + break; + + case IEC_61883_CIP_FMT_8: + default: + printf("Unsupported IEC-61883 format: %d\n", stream_params->format.u.s.subtype); + break; + } + + stream->pipe_source.dropping = 0; + + if (stream_params->direction == AVTP_DIRECTION_LISTENER) + stream->listener_gst_handler = listener_gst_handler; + + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + stream->batch_size = BATCH_SIZE_CVF; + stream->pipe_source.dropping = 1; // Always start by dropping frames, to ensure the data passed to Gstreamer starts on a frame boundary (by waiting for an EOF event) + + if (stream_params->direction == AVTP_DIRECTION_LISTENER) + stream->listener_gst_handler = listener_gst_handler_cvf; + + break; +#endif + + default: + printf("Unsupported AVTP subtype: %d\n", stream_params->format.u.s.subtype); + break; + } + /* FIX ME: PTS offset can't be too small because CVF case for camera. Need to be fixed if audio/video mode implemented + if (stream->gst_stream.gst->gst.pts_offset < (stream->gst_stream.gst->gst.pipeline_latency + stream->gst_stream.local_pts_offset)) { + stream->gst_stream.gst->gst.pts_offset = stream->gst_stream.gst->gst.pipeline_latency + stream->gst_stream.local_pts_offset; + printf("Warning: PTS offset too small, resetting to %lld ns.\n", stream->gst_stream.gst->gst.pipeline_latency + stream->gst_stream.local_pts_offset); + } + stream->gst_stream.gst->gst.pts_offset -= stream->gst_stream.local_pts_offset; + */ + stream->pipe_source.buffer = NULL; + stream->pipe_source.memory = NULL; + stream->pipe_source.buffer_byte_count = 0; + stream->pipe_source.memory_byte_count = 0; +} diff --git a/apps/linux/common/gstreamer_single.h b/apps/linux/common/gstreamer_single.h new file mode 100644 index 0000000..19586a7 --- /dev/null +++ b/apps/linux/common/gstreamer_single.h @@ -0,0 +1,145 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _GSTREAMER_SINGLE_H_ +#define _GSTREAMER_SINGLE_H_ + +#include +#include +#include + +#include +#include "stats.h" +#include "time.h" +#include "ts_parser.h" +#include "gstreamer.h" +#include "gst_pipeline_definitions.h" +#include "common.h" +#include "genavb/avdecc.h" + +#define FILENAME_SIZE SCHAR_MAX + +// We currently need a low batch size to improve timestamping and synchronization accuracy, +//so we set it to the minimum accepted value +#define BATCH_SIZE 2048 // in bytes +#define BATCH_SIZE_CVF 65536 //131072 //in bytes +#define MAX_PKT_SIZE_CVF 2000 + +#define AVTP_TS_MIN_DELTA 50000000 +#define AVTP_TS_MAX_DELTA 50000000 +#define AVTP_TS_MIN_DELTA_STOP 150000000 +#define AVTP_TS_MAX_DELTA_STOP 50000000 + +#define GST_MEMORY_OBJ_SIZE (300000) + +#define GST_PRIORITY 1 /* RT_FIFO priority to be used for the process */ + +/** + * Generic gstreamer stream parameters + */ +struct gstreamer_pipeline_source { + struct gstreamer_pipeline *gst_pipeline; + + unsigned long long byte_count; + unsigned int count; + unsigned long long late_count; + unsigned long long ontime_count; + + unsigned int dropping; + unsigned int pipeline_state; + + GstBuffer *buffer; + GstMapInfo info; + GstMemory *memory; + unsigned char *data_buf; + GstAppSrc *source; + GstAppSink *sink; + + int buffer_byte_count; + int memory_byte_count; + + uint64_t tlast; +}; + +/** + * Generic mpeg_ts stats + */ +struct mpeg_ts_stats { + + struct stats pcr_delay; + struct stats pcr_period; + unsigned long long pcr_prev; +}; + +/** + * Generic stream stats + */ +struct gstreamer_stats { + + unsigned long long byte_count_prev; + unsigned long long buffer_pts_prev; + + unsigned long long ts_err; + unsigned long long pkt_lost; + unsigned long long mr; + + struct stats write_delay; + struct stats delay; + struct stats period; + struct stats rate; + + struct mpeg_ts_stats mpeg_ts; +}; + +struct gstreamer_stream { + + unsigned int source_index; + + struct avb_stream_handle *stream_h; + + unsigned int created; + unsigned int data_received; + + void *thread; + + unsigned int state; + int started; + int stream_fd; + + struct avb_stream_params params; + unsigned int batch_size; + unsigned int frame_size; + unsigned int flags; + unsigned long long previous_frame_time; + + struct gstreamer_pipeline_source pipe_source; + struct gstreamer_stats stream_stats; + struct ts_parser ts_parser; + + int (*listener_gst_handler)(struct gstreamer_stream *stream, unsigned int events); + + void *data; /*Will point to talker_gst_multi_app for talker stream*/ + + int timer_fd; + void *thread_timer; +}; + +void stream_init_stats(struct gstreamer_stream *stream); +void stream_update_stats(struct gstreamer_stream *stream, unsigned long long byte_count, unsigned long long now, unsigned long long buffer_pts); +void stream_dump_stats(struct gstreamer_stream *stream); +void stream_61883_4_update_stats(struct gstreamer_stream *stream, unsigned char *buf, unsigned long long buffer_pts); +void dump_stream_infos(struct gstreamer_stream *stream); +int listener_gst_handler_cvf(struct gstreamer_stream *stream, unsigned int events); +int listener_gst_handler(struct gstreamer_stream *stream, unsigned int events); +void apply_stream_params(struct gstreamer_stream *stream, struct avb_stream_params *stream_params); + +#define CVF_MJPEG_SPLASH_FILENAME "/home/media/cvf_splash_screen.mjpg" +#define CVF_MJPEG_SPLASH_MAX_FRAME_SIZE 60000 +#define CVF_MJPEG_SPLASH_FPS 30 +int gst_cvf_mjpeg_warm_up_pipeline(struct gstreamer_pipeline *gst, unsigned int nframes); + +#endif /* _GSTREAMER_SINGLE_H_ */ diff --git a/apps/linux/common/helpers.h b/apps/linux/common/helpers.h new file mode 100644 index 0000000..a880ebb --- /dev/null +++ b/apps/linux/common/helpers.h @@ -0,0 +1,14 @@ +/* + * Copyright 2018, 2020, 2022, 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_HELPERS_H_ +#define _COMMON_HELPERS_H_ + +#include /* for offsetof() */ + +#define container_of(entry, type, member) ((type *)((unsigned char *)(entry) - offsetof(type, member))) + +#endif /* _COMMON_HELPERS_H_ */ diff --git a/apps/linux/common/log.c b/apps/linux/common/log.c new file mode 100644 index 0000000..b61ff73 --- /dev/null +++ b/apps/linux/common/log.c @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "log.h" +#include "time.h" + +#define DEFAULT_LOG_DIR "/var/log/" +#define DEFAULT_LOG_FILE "avb_media_app" + +FILE *g_logfile_hdl = NULL; +char g_timestamp_str[TS_STR_LEN] = {0}; + +int aar_log_init(char *log_file_path) +{ +#if LOG_LEVEL > VERBOSE_NONE + if (!log_file_path) /* no log file specified, use default. */ + g_logfile_hdl = fopen(DEFAULT_LOG_DIR DEFAULT_LOG_FILE, "a"); + else + g_logfile_hdl = fopen(log_file_path, "a"); + + if (!g_logfile_hdl) + return -1; + + setlinebuf(g_logfile_hdl); +#endif + return 0; +} + +void aar_log_exit(void) +{ + if (g_logfile_hdl) { + // Close log file and clear file handle. + fclose(g_logfile_hdl); + g_logfile_hdl = NULL; + } +} + +void aar_log_update_time(genavb_clock_id_t clock_id) +{ + uint64_t now; + + if (genavb_clock_gettime64(clock_id, &now) < 0) + return; + + snprintf(g_timestamp_str, TS_STR_LEN, "%" PRIu64 "", now / NSECS_PER_SEC); +} diff --git a/apps/linux/common/log.h b/apps/linux/common/log.h new file mode 100644 index 0000000..7772426 --- /dev/null +++ b/apps/linux/common/log.h @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __COMMON_LOG_H__ +#define __COMMON_LOG_H__ + +#include +#include +#include + +#include + +#define VERBOSE_NONE (0) +#define VERBOSE_ERROR (1) +#define VERBOSE_INFO (2) +#define VERBOSE_DEBUG (3) + + +#ifndef PRINT_LEVEL +#define PRINT_LEVEL VERBOSE_NONE +#endif /* PRINT_LEVEL */ + +#ifndef LOG_LEVEL +#define LOG_LEVEL VERBOSE_INFO +#endif /* LOG_LEVEL */ + +#define _LHF g_logfile_hdl +#define _LTS g_timestamp_str + +#define DBG_FILE_BASE(fmt, args...) \ + if (_LHF) { \ + fprintf(_LHF, "\nDBG %-11s %-32.32s " fmt, _LTS, __FUNCTION__, ##args); \ + } +#define INF_FILE_BASE(fmt, args...) \ + if (_LHF) { \ + fprintf(_LHF, "\nINFO %-11s %-32.32s " fmt, _LTS, __FUNCTION__, ##args); \ + } +#define _INF_FILE_BASE(fmt, args...) \ + if (_LHF) { \ + fprintf(_LHF, fmt, ##args); \ + } +#define ERR_FILE_BASE(fmt, args...) \ + if (_LHF) { \ + fprintf(_LHF, "\nERR %-11s %-32.32s " fmt, _LTS, __FUNCTION__, ##args); \ + } + +#define DBG_OUT_BASE(fmt, args...) printf("\nDBG %-11s %-32.32s " fmt, _LTS, __FUNCTION__, ##args) +#define INF_OUT_BASE(fmt, args...) printf("\nINFO %-11s %-32.32s " fmt, _LTS, __FUNCTION__, ##args) +#define _INF_OUT_BASE(fmt, args...) printf(fmt, ##args) +#define ERR_OUT_BASE(fmt, args...) printf("\nERR %-11s %-32.32s " fmt, _LTS, __FUNCTION__, ##args) + +#if LOG_LEVEL >= VERBOSE_DEBUG +#define DBG_LOG(fmt,args...) DBG_FILE_BASE(fmt, ##args) +#else +#define DBG_LOG(fmt,args...) +#endif /* LOG_LEVEL >= VERBOSE_DEBUG */ + +#if LOG_LEVEL >= VERBOSE_INFO +#define INF_LOG(fmt,args...) INF_FILE_BASE(fmt, ##args) +#define _INF_LOG(fmt,args...) _INF_FILE_BASE(fmt, ##args) +#else +#define INF_LOG(fmt,args...) +#define _INF_LOG(fmt,args...) +#endif /* LOG_LEVEL >= VERBOSE_INFO */ + +#if LOG_LEVEL >= VERBOSE_ERROR +#define ERR_LOG(fmt,args...) ERR_FILE_BASE(fmt, ##args) +#else +#define ERR_LOG(fmt,args...) +#endif /* LOG_LEVEL >= VERBOSE_ERROR */ + +#if PRINT_LEVEL >= VERBOSE_DEBUG +#define DBG_OUT(fmt,args...) DBG_OUT_BASE(fmt, ##args) +#else +#define DBG_OUT(fmt,args...) +#endif /* PRINT_LEVEL >= VERBOSE_DEBUG */ + +#if PRINT_LEVEL >= VERBOSE_INFO +#define INF_OUT(fmt,args...) INF_OUT_BASE(fmt, ##args) +#define _INF_OUT(fmt,args...) INF_OUT_BASE(fmt, ##args) +#else +#define INF_OUT(fmt,args...) +#define _INF_OUT(fmt,args...) +#endif /* PRINT_LEVEL >= VERBOSE_INFO */ + +#if PRINT_LEVEL >= VERBOSE_ERROR +#define ERR_OUT(fmt,args...) ERR_OUT_BASE(fmt, ##args) +#else +#define ERR_OUT(fmt,args...) +#endif /* PRINT_LEVEL >= VERBOSE_ERROR */ + +#define DBG(fmt,args...) do{DBG_OUT(fmt,##args);DBG_LOG(fmt,##args);}while(0) +#define INF(fmt,args...) do{INF_OUT(fmt,##args);INF_LOG(fmt,##args);}while(0) +#define _INF(fmt,args...) do{_INF_OUT(fmt,##args);_INF_LOG(fmt,##args);}while(0) +#define ERR(fmt,args...) do{ERR_OUT(fmt,##args);ERR_LOG(fmt,##args);}while(0) + +#define STREAM_STR_FMT "%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x" +#define STREAM_STR(_stream_id) _stream_id[0], _stream_id[1], _stream_id[2], _stream_id[3], _stream_id[4], _stream_id[5], _stream_id[6], _stream_id[7] + +#define MAC_STR_FMT "%02x-%02x-%02x-%02x-%02x-%02x" +#define MAC_STR(_dst_mac) _dst_mac[0], _dst_mac[1], _dst_mac[2], _dst_mac[3], _dst_mac[4], _dst_mac[5] + +#define TS_STR_LEN 30 +extern FILE *g_logfile_hdl; +extern char g_timestamp_str[TS_STR_LEN]; + +int aar_log_init(char *log_file_path); +void aar_log_exit(void); +void aar_log_update_time(genavb_clock_id_t clock_id); + +#endif /* __COMMON_LOG_H__ */ diff --git a/apps/linux/common/msrp.c b/apps/linux/common/msrp.c new file mode 100644 index 0000000..9e12af1 --- /dev/null +++ b/apps/linux/common/msrp.c @@ -0,0 +1,323 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "log.h" +#include +#include +#include + + +#define MAX_AVB_LISTENER_REGISTRATIONS 32 +#define MAX_AVB_TALKER_REGISTRATIONS 32 + +static struct avb_control_handle *s_msrp_handle = NULL; + +struct msrp_registration_t { + avb_u8 stream_id[8]; + unsigned int ref_count; +}; + +static struct msrp_registration_t msrp_listener_registrations[MAX_AVB_LISTENER_REGISTRATIONS]; +static struct msrp_registration_t msrp_talker_registrations[MAX_AVB_TALKER_REGISTRATIONS]; + +int msrp_init(struct avb_handle *s_avb_handle) +{ + int avb_result; + int rc; + + avb_result = avb_control_open(s_avb_handle, &s_msrp_handle, AVB_CTRL_MSRP); + if (avb_result != AVB_SUCCESS) { + ERR("avb_control_open() failed: %s", avb_strerror(avb_result)); + rc = -1; + goto err_control_open; + } + + memset(msrp_listener_registrations, 0, sizeof(msrp_listener_registrations)); + memset(msrp_talker_registrations, 0, sizeof(msrp_talker_registrations)); + + DBG("msrp control handle: %p", s_msrp_handle); + + return 0; + +err_control_open: + return rc; +} + +int msrp_exit(void) +{ + avb_control_close(s_msrp_handle); + + s_msrp_handle = NULL; + + return 0; +} + +static int _msrp_talker_deregister(const struct avb_stream_params *stream_params) +{ + struct avb_msg_talker_deregister talker_deregister; + struct avb_msg_talker_response talker_response; + unsigned int msg_type, msg_len; + int rc; + + DBG("stream_params: %p", stream_params); + talker_deregister.port = stream_params->port; + memcpy(talker_deregister.stream_id, stream_params->stream_id, 8); + + msg_type = AVB_MSG_TALKER_DEREGISTER; + msg_len = sizeof(talker_response); + rc = avb_control_send_sync(s_msrp_handle, &msg_type, &talker_deregister, sizeof(talker_deregister), &talker_response, &msg_len, 1000); + if ((rc != AVB_SUCCESS) || (msg_type != AVB_MSG_TALKER_RESPONSE) || (talker_response.status != AVB_SUCCESS)) { + ERR(STREAM_STR_FMT " failed: %s", STREAM_STR(stream_params->stream_id), avb_strerror(rc)); + + return -1; + } + + return 0; +} + +static int _msrp_talker_register(const struct avb_stream_params *stream_params) +{ + struct avb_msg_talker_register talker_register; + struct avb_msg_talker_response talker_response; + unsigned int max_frame_size, max_interval_frames; + unsigned int msg_type, msg_len; + int rc; + + DBG("stream_params: %p", stream_params); + talker_register.port = stream_params->port; + memcpy(talker_register.stream_id, stream_params->stream_id, 8); + + talker_register.params.stream_class = stream_params->stream_class; + memcpy(talker_register.params.destination_address, stream_params->dst_mac, 6); + talker_register.params.vlan_id = VLAN_VID_DEFAULT; + + if (stream_params->flags & GENAVB_STREAM_FLAGS_CUSTOM_TSPEC) { + max_frame_size = stream_params->talker.max_frame_size; + max_interval_frames = stream_params->talker.max_interval_frames; + } else + avdecc_fmt_tspec(&stream_params->format, stream_params->stream_class, &max_frame_size, &max_interval_frames); + + talker_register.params.max_frame_size = max_frame_size; + talker_register.params.max_interval_frames = max_interval_frames; + talker_register.params.accumulated_latency = 0; + talker_register.params.rank = NORMAL; + + msg_type = AVB_MSG_TALKER_REGISTER; + msg_len = sizeof(talker_response); + rc = avb_control_send_sync(s_msrp_handle, &msg_type, &talker_register, sizeof(talker_register), &talker_response, &msg_len, 1000); + if ((rc != AVB_SUCCESS) || (msg_type != AVB_MSG_TALKER_RESPONSE) || (talker_response.status != AVB_SUCCESS)) { + ERR(STREAM_STR_FMT " failed: %s", STREAM_STR(stream_params->stream_id), avb_strerror(rc)); + + return -1; + } + + return 0; +} + +static int _msrp_listener_deregister(const struct avb_stream_params *stream_params) +{ + struct avb_msg_listener_deregister listener_deregister; + struct avb_msg_listener_response listener_response; + unsigned int msg_type, msg_len; + int rc; + + DBG("stream_params: %p", stream_params); + listener_deregister.port = stream_params->port; + memcpy(listener_deregister.stream_id, stream_params->stream_id, 8); + + msg_type = AVB_MSG_LISTENER_DEREGISTER; + msg_len = sizeof(listener_response); + rc = avb_control_send_sync(s_msrp_handle, &msg_type, &listener_deregister, sizeof(listener_deregister), &listener_response, &msg_len, 1000); + if ((rc != AVB_SUCCESS) || (msg_type != AVB_MSG_LISTENER_RESPONSE) || (listener_response.status != AVB_SUCCESS)) { + ERR(STREAM_STR_FMT " failed: %s", STREAM_STR(stream_params->stream_id), avb_strerror(rc)); + + return -1; + } + + return 0; +} + +static int _msrp_listener_register(const struct avb_stream_params *stream_params) +{ + struct avb_msg_listener_register listener_register; + struct avb_msg_listener_response listener_response; + unsigned int msg_type, msg_len; + int rc; + + DBG("stream_params: %p", stream_params); + listener_register.port = stream_params->port; + memcpy(listener_register.stream_id, stream_params->stream_id, 8); + + msg_type = AVB_MSG_LISTENER_REGISTER; + msg_len = sizeof(listener_response); + rc = avb_control_send_sync(s_msrp_handle, &msg_type, &listener_register, sizeof(listener_register), &listener_response, &msg_len, 1000); + if ((rc != AVB_SUCCESS) || (msg_type != AVB_MSG_LISTENER_RESPONSE) || (listener_response.status != AVB_SUCCESS)) { + ERR(STREAM_STR_FMT " failed: %s", STREAM_STR(stream_params->stream_id), avb_strerror(rc)); + + return -1; + } + + return 0; +} + + +static struct msrp_registration_t *msrp_find_listener_registration(unsigned char *stream_id) +{ + struct msrp_registration_t *registration; + int i; + + for (i = 0; i < MAX_AVB_LISTENER_REGISTRATIONS; ++i) { + registration = &msrp_listener_registrations[i]; + + if (memcmp(registration->stream_id, stream_id, sizeof(registration->stream_id)) == 0) { + DBG("found matching registration %p at index %d for stream " STREAM_STR_FMT, registration, i, STREAM_STR(stream_id)); + return registration; + } + } + + // No existing registration, find a free one + for (i = 0; i < MAX_AVB_LISTENER_REGISTRATIONS; ++i) { + registration = &msrp_listener_registrations[i]; + + if (!registration->ref_count){ + memcpy(registration->stream_id, stream_id, sizeof(registration->stream_id)); + DBG("found free registration %p at index %d", registration, i); + return registration; + } + } + + return NULL; +} + +int msrp_listener_register(struct avb_stream_params *stream_params) +{ + struct msrp_registration_t *registration; + int result = 0; + + DBG("stream_id: " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + + registration = msrp_find_listener_registration(stream_params->stream_id); + + if (!registration) { + ERR("Couldn't find free registration for stream " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + result = -1; + goto err; + } + + if (!registration->ref_count) + result = _msrp_listener_register(stream_params); + + if (result == 0) + registration->ref_count++; + +err: + return result; +} + +void msrp_listener_deregister(struct avb_stream_params *stream_params) +{ + struct msrp_registration_t *registration; + + DBG("stream_id: " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + + registration = msrp_find_listener_registration(stream_params->stream_id); + + if (!registration) { + ERR("Couldn't find registration for stream " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + return; + } + + if (!registration->ref_count) { + ERR("Tried to deregister an unregistered MSRP listener " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + return; + } + + registration->ref_count--; + + if (!registration->ref_count) + _msrp_listener_deregister(stream_params); +} + +static struct msrp_registration_t *msrp_find_talker_registration(unsigned char *stream_id) +{ + struct msrp_registration_t *registration; + int i; + + for (i = 0; i < MAX_AVB_TALKER_REGISTRATIONS; ++i) { + registration = &msrp_talker_registrations[i]; + + if (memcmp(registration->stream_id, stream_id, sizeof(registration->stream_id)) == 0) { + DBG("found matching registration %p at index %d for stream " STREAM_STR_FMT, registration, i, STREAM_STR(stream_id)); + return registration; + } + } + + // No existing registration, find a free one + for (i = 0; i < MAX_AVB_TALKER_REGISTRATIONS; ++i) { + registration = &msrp_talker_registrations[i]; + + if (!registration->ref_count){ + memcpy(registration->stream_id, stream_id, sizeof(registration->stream_id)); + DBG("found free registration %p at index %d", registration, i); + return registration; + } + } + + return NULL; +} + + +int msrp_talker_register(struct avb_stream_params *stream_params) +{ + struct msrp_registration_t *registration; + int result = 0; + + DBG("stream_id: " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + + registration = msrp_find_talker_registration(stream_params->stream_id); + + if (!registration) { + ERR("Couldn't find free registration for stream " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + result = -1; + goto err; + } + + if (!registration->ref_count) + result = _msrp_talker_register(stream_params); + + if (result == 0) + registration->ref_count++; + +err: + return result; +} + +void msrp_talker_deregister(struct avb_stream_params *stream_params) +{ + struct msrp_registration_t *registration; + + DBG("stream_id: " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + registration = msrp_find_talker_registration(stream_params->stream_id); + + if (!registration) { + ERR("Couldn't find registration for stream " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + return; + } + + if (!registration->ref_count) { + ERR("Tried to deregister an unregistered MSRP listener " STREAM_STR_FMT, STREAM_STR(stream_params->stream_id)); + return; + } + + registration->ref_count--; + + if (!registration->ref_count) + _msrp_talker_deregister(stream_params); +} diff --git a/apps/linux/common/msrp.h b/apps/linux/common/msrp.h new file mode 100644 index 0000000..77802a0 --- /dev/null +++ b/apps/linux/common/msrp.h @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __MSRP_H__ +#define __MSRP_H__ + + +/** + * @addtogroup avb_stream + * @{ + */ + +/** + * @brief Initialize AVB stack and AVB resources. + * + * @return 0 if success or negative error code. + */ +int msrp_init(struct avb_handle *s_avb_handle); + +/** + * @brief De-initialize AVB stack and AVB resources. + * + * @return 0 if success or negative error code. + */ +int msrp_exit(void); + +int msrp_listener_register(struct avb_stream_params *stream_params); +void msrp_listener_deregister(struct avb_stream_params *stream_params); +int msrp_talker_register(struct avb_stream_params *stream_params); +void msrp_talker_deregister(struct avb_stream_params *stream_params); + +/** @} */ +#endif /* __AVB_STREAM_H__ */ diff --git a/apps/linux/common/open62541.inc b/apps/linux/common/open62541.inc new file mode 100644 index 0000000..0d17028 --- /dev/null +++ b/apps/linux/common/open62541.inc @@ -0,0 +1,6 @@ +# Common Makefile definitions for applications using open62541 (opcua) library + +OPCUA_INCLUDES= -I$(STAGING_DIR)/usr/include/open62541 +OPCUA_LIBS= -lopen62541 -L$(STAGING_DIR)/usr/lib + +OPCUA_CFLAGS=$(OPCUA_LIBS) $(OPCUA_INCLUDES) diff --git a/apps/linux/common/stats.c b/apps/linux/common/stats.c new file mode 100644 index 0000000..24a1e8f --- /dev/null +++ b/apps/linux/common/stats.c @@ -0,0 +1,175 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "stats.h" +#include "log.h" + +#ifdef STATS_LOG +#define PRINT INF +#define _PRINT _INF +#else +#define PRINT printf +#define _PRINT printf +#endif + +void stats_reset(struct stats *s) +{ + s->current_count = 0; + s->current_min = 0x7fffffff; + s->current_mean = 0; + s->current_max = -0x7fffffff; + s->current_ms = 0; +} + +/** Example function to be passed to stats_init + * This function expects the priv field to be a character string. + * Usage: + * stats_init(s, log2_size, "your string", print_stats); + */ +void stats_print(struct stats *s) +{ + PRINT("stats(%p) %s min %d mean %d max %d rms^2 %llu stddev^2 %llu absmin %d absmax %d\n", + s, (char *)s->priv, s->min, s->mean, s->max, s->ms, s->variance, s->abs_min, s->abs_max); +} + +/** Initialize a stats structure. + * @s: Pointer to structure to be initialized + * @log2_size: Set size to be reached before statistics are computed, expressed as a power of 2 + * @priv: private field for use by func + * @func: pointer to the function to be called when stats are computed + * + */ +void stats_init(struct stats *s, unsigned int log2_size, void *priv, void (*func)(struct stats *s)) +{ + s->log2_size = log2_size; + s->priv = priv; + s->func = func; + + s->abs_min = 0x7fffffff; + s->abs_max = -0x7fffffff; + + stats_reset(s); +} + +/** Update stats with a given sample. + * @s: handler for the stats being monitored + * @val: sample to be added + * + * This function adds a sample to the set. + * If the number of accumulated samples is equal to the requested size of the set, the following indicators will be computed: + * . minimum observed value, + * . maximum observed value, + * . mean value, + * . square of the RMS (i.e. mean of the squares) + * . square of the standard deviation (i.e. variance) + */ +void stats_update(struct stats *s, int val) +{ + s->current_count++; + + s->current_mean += val; + s->current_ms += (long long)val * val; + + if (val < s->current_min) { + s->current_min = val; + if (val < s->abs_min) + s->abs_min = val; + } + + if (val > s->current_max) { + s->current_max = val; + if (val > s->abs_max) + s->abs_max = val; + } + + + if (s->current_count == (1U << s->log2_size)) { + s->ms = s->current_ms >> s->log2_size; + s->variance = s->ms - ((s->current_mean * s->current_mean) >> (2*s->log2_size)); + s->mean = s->current_mean >> s->log2_size; + + s->min = s->current_min; + s->max = s->current_max; + + if (s->func) + s->func(s); + + stats_reset(s); + } +} + +/** Compute current stats event if set size hasn't been reached yet. + * @s: handler for the stats being monitored + * + * This function computes current statistics for the stats: + * . minimum observed value, + * . maximum observed value, + * . mean value, + * . square of the RMS (i.e. mean of the squares) + * . square of the standard deviation (i.e. variance) + */ +void stats_compute(struct stats *s) +{ + if (s->current_count) { + s->ms = s->current_ms / s->current_count; + s->variance = s->ms - (s->current_mean * s->current_mean) / ((long long)s->current_count * s->current_count); + s->mean = s->current_mean / s->current_count; + } else { + s->mean = 0; + s->ms = 0; + s->variance = 0; + } + + s->min = s->current_min; + s->max = s->current_max; +} + +int hist_init(struct hist *hist, unsigned int n_slots, unsigned slot_size) +{ + /* One extra slot for last bucket. + */ + if ((n_slots + 1) > MAX_SLOTS) + return -1; + + hist->n_slots = n_slots + 1; + hist->slot_size = slot_size; + + return 0; +} + +void hist_update(struct hist *hist, unsigned int value) +{ + unsigned int slot = value / hist->slot_size; + + if (slot >= hist->n_slots) + slot = hist->n_slots - 1; + + hist->slots[slot]++; +} + +void hist_reset(struct hist *hist) +{ + unsigned int i; + + for (i = 0; i < (hist->n_slots + 1); i++) + hist->slots[i] = 0; +} + +void hist_print(struct hist *hist) +{ + unsigned int i; + + PRINT("n_slot %d slot_size %d \n", hist->n_slots, hist->slot_size); + + for (i = 0; i < (hist->n_slots + 1); i++) + _PRINT("%u ", hist->slots[i]); + + _PRINT("\n"); +} diff --git a/apps/linux/common/stats.h b/apps/linux/common/stats.h new file mode 100644 index 0000000..927b82a --- /dev/null +++ b/apps/linux/common/stats.h @@ -0,0 +1,54 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_STATS_H_ +#define _COMMON_STATS_H_ + +struct stats { + unsigned int log2_size; + unsigned int current_count; + + int current_min; + int current_max; + long long current_mean; + unsigned long long current_ms; + + /* Stats snapshot */ + int min; + int max; + int mean; + unsigned long long ms; + unsigned long long variance; + + /* absolute min/max (never reset) */ + int abs_min; + int abs_max; + + void *priv; + void (*func)(struct stats *s); +}; + +#define MAX_SLOTS 256 + +struct hist { + unsigned int slots[MAX_SLOTS]; + unsigned int n_slots; + unsigned int slot_size; +}; + +void stats_reset(struct stats *s); +void stats_print(struct stats *s); +void stats_init(struct stats *s, unsigned int log2_size, void *priv, void (*func)(struct stats *s)); +void stats_update(struct stats *s, int val); +void stats_compute(struct stats *s); + +int hist_init(struct hist *hist, unsigned int n_slots, unsigned slot_size); +void hist_update(struct hist *hist, unsigned int value); +void hist_reset(struct hist *hist); + +void hist_print(struct hist *hist); +#endif /* _COMMON_STATS_H_ */ diff --git a/apps/linux/common/stream_stats.c b/apps/linux/common/stream_stats.c new file mode 100644 index 0000000..cad1710 --- /dev/null +++ b/apps/linux/common/stream_stats.c @@ -0,0 +1,100 @@ +/* + * Copyright 2017-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "log.h" +#include "stream_stats.h" +#include "stats.h" + + +void stream_stats_dump(struct stream_stats *stats) +{ + aar_avb_counter_stats_t *avb_counter_stats = &stats->avb_stats.counter_stats; + + INF_LOG("avb_route(%p) statistic", (void *)stats->app_ptr); + + // AVB counter stats + INF_LOG(" avb(%p) tx_err: %d, rx_err: %d, batch_tx: %d, batch_rx: %d", + (void *)stats->avb_stream_ptr, avb_counter_stats->tx_err, + avb_counter_stats->rx_err, avb_counter_stats->batch_tx, + avb_counter_stats->batch_rx); + + stats_compute(&stats->avb_stats.gptp_2cont_wakeup); + // Print stats + INF_LOG(" avb(%p) inter wakeup time (ns): %d/%d/%d", + (void *)stats->avb_stream_ptr, stats->avb_stats.gptp_2cont_wakeup.min, + stats->avb_stats.gptp_2cont_wakeup.mean, + stats->avb_stats.gptp_2cont_wakeup.max); + + // Check if listener side + if (stats->is_listener) { + stats_compute(&stats->avb_stats.event_2cont_wakeup); + stats_compute(&stats->avb_stats.event_gptp); + + INF_LOG(" avb(%p) listener inter event_ts (ns): %d/%d/%d", + (void *)stats->avb_stream_ptr, + stats->avb_stats.event_2cont_wakeup.min, + stats->avb_stats.event_2cont_wakeup.mean, + stats->avb_stats.event_2cont_wakeup.max); + INF_LOG(" avb(%p) listener event_ts-now (ns): %d/%d/%d", + (void *)stats->avb_stream_ptr, + stats->avb_stats.event_gptp.min, + stats->avb_stats.event_gptp.mean, + stats->avb_stats.event_gptp.max); + } +} + + +/* Must be called last, after updating the specific stats first. */ +int stream_stats_store(struct stream_stats *stats, aar_avb_stats_t *avb_stats) +{ + memcpy(&stats->avb_stats, avb_stats, sizeof(aar_avb_stats_t)); + + stats_reset(&avb_stats->gptp_2cont_wakeup); + stats_reset(&avb_stats->event_2cont_wakeup); + stats_reset(&avb_stats->event_gptp); + + stream_stats_set_updated(stats); + + // Let data thread knows that stat is snapshoot ok + stream_stats_set_time(stats); + + return 0; +} + +int stream_stats_is_time(struct stream_stats *stats) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) + return 0; + + // Dump statistic after each 10s + if (stats->flags & APP_FLAG_FIRST_HANDLE) { + // First handle, just store the time stamp + stats->last_stat_dump_time = ts.tv_sec; + + stats->flags &= ~APP_FLAG_FIRST_HANDLE; + + return 0; + } else if ((ts.tv_sec - stats->last_stat_dump_time) < stats->period) { + return 0; + } + + return 1; +} + +void stream_stats_set_time(struct stream_stats *stats) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) + return; + + stats->last_stat_dump_time = ts.tv_sec; +} diff --git a/apps/linux/common/stream_stats.h b/apps/linux/common/stream_stats.h new file mode 100644 index 0000000..cf5baa9 --- /dev/null +++ b/apps/linux/common/stream_stats.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __STREAM_STATS_H__ +#define __STREAM_STATS_H__ + +#include "avb_stream_config.h" +#include "stats.h" + +#define APP_FLAG_FIRST_HANDLE (1 << 0) + +struct stream_stats { + unsigned long app_ptr; /**< AVB route data pointer value, just for printout */ + unsigned long avb_stream_ptr; /**< AVB stream pointer value, just for printout */ + int is_listener; /**< This is statistic for listener route or not */ + + unsigned int period; + unsigned int flags; + unsigned int last_stat_dump_time; + + char is_updated_flag; /**< Flag indicate statistic is updated by data thread */ + aar_avb_stats_t avb_stats; /**< AVB statistic structure */ +}; + + +void stream_stats_dump(struct stream_stats *stats); +int stream_stats_store(struct stream_stats *stats, aar_avb_stats_t *avb_stats); +int stream_stats_is_time(struct stream_stats *stats); +void stream_stats_set_time(struct stream_stats *stats); + + +static inline int stream_stats_is_updated(struct stream_stats *stats) +{ + return stats->is_updated_flag; +} + +static inline void stream_stats_set_updated(struct stream_stats *stats) +{ + stats->is_updated_flag = 1; +} + + +#endif /* __STREAM_STATS_H__ */ diff --git a/apps/linux/common/thread.c b/apps/linux/common/thread.c new file mode 100644 index 0000000..cbc5ae2 --- /dev/null +++ b/apps/linux/common/thread.c @@ -0,0 +1,791 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "thread.h" +#include "time.h" + +#define THREAD_STATS_PERIOD_NS (10llu * NSECS_PER_SEC) +#define THREAD_HANDLER_TIMEOUT_MS (100llu) +#define THREAD_HANDLER_TIMEOUT_NS (THREAD_HANDLER_TIMEOUT_MS * NSECS_PER_MSEC) + +static pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; + +/** Thread handle function. + * + * After thread starts, this function will check the registered file descriptor for incoming data. + * If any data came, it will read from input then transfer to output. This function only exit + * whenever error occurs or exit_flag of thread is set. + * + * @param[in] param Index of thread + * + * @return handler result + */ +static void *s_data_thread_handle(void *param); + +/** Count number of used slot in thread + * + * @param[in] thread Pointer of thread that want to check + * + * @return number of used slot + */ +static int thread_count_used_slot(thr_thread_t *thread); + +/** Check thread matches specified capabilities. + * + * @param[in] thread Pointer to thread that need to check + * @param[in] capabilities Required capabilities + * + * @return 0 if the thread does not match required capabilities + * @return 1 if the thread matches required capabilities + */ +static int thread_check_capability_match(thr_thread_t *thread, int capabilities); + +/** Get first idle slot of thread + * + * @param[in] thread Pointer to thread + * + * @return pointer to idle slot, NULL if do not have any idle slot. + */ +static thr_thread_slot_t *thread_get_first_idle_slot(thr_thread_t *thread); + +/** Check for timed out slots and execute its handlers + * + * @param[in] thread Pointer to thread + * @param[in] timeout_time The timeout value in ns + * + * @return pointer to idle slot, NULL if do not have any idle slot. + */ +static void thread_check_timeout_slots(thr_thread_t *thread, unsigned timeout_time); + +/** + * @brief Get the thread that contains given slot + * + * @param slot The pointer of slot + * + * @return Pointer to thread that contains the slot, else NULL + */ +static thr_thread_t *thread_get_from_slot(thr_thread_slot_t *slot); + +/** + * @brief Initialize data of thread slot + * + * @param thread The thread pointer + */ +static void thread_slot_init_data(thr_thread_t *thread); + +/** + * @brief De-initialize data of thread slot + * + * @param thread The thread pointer + */ +static void thread_slot_deinit_data(thr_thread_t *thread); + +/** + * @brief Sleep handler based on epoll_wait + * + * @param thread The thread pointer + * + * @return n >= 0 if n events received. + * 0 if call interrupted. + * -1 on other errors. + */ +int thread_sleep_epoll(thr_thread_t *thread_ptr, struct epoll_event *recv_events) +{ + int n; + + // Wait fd events for each 100ms + n = epoll_wait(thread_ptr->poll_fd, recv_events, thread_ptr->max_slots, THREAD_HANDLER_TIMEOUT_MS); + if (n < 0) { + if (errno == EINTR) + return 0; + else { + // Error occurs + ERR("epoll_wait(%d) failed: %s", thread_ptr->poll_fd, strerror(errno)); + return -1; + } + } else + return n; +} + +int thread_sleep_nanosleep(thr_thread_t *thread_ptr, struct epoll_event *recv_events) +{ + unsigned int ready_slots = 0, total_wakeup_slots = 0; + unsigned int next_wakeup_slots[MAX_THREAD_SLOTS]; + struct thread_sleep_nanosleep_data *nsleep; + uint64_t sched_next = UINT64_MAX; + struct timespec next_time; + int i, rc; + + for (i = 0; i < thread_ptr->max_slots; i++) { + if (thread_ptr->slots[i].is_used) { + nsleep = (struct thread_sleep_nanosleep_data *)thread_ptr->slots[i].sleep_data; + if (nsleep->is_armed && (nsleep->next <= sched_next)) { + + if (nsleep->next == sched_next) + next_wakeup_slots[total_wakeup_slots++] = i; + else { + sched_next = nsleep->next; + next_wakeup_slots[0] = i; + total_wakeup_slots = 1; + } + } + } + } + + DBG("total_wakeup_slots(%d) sched_next(%" PRIu64 ")", total_wakeup_slots, sched_next); + if (total_wakeup_slots == 0) { // Wait 100ms if no scheduled wake-up + next_time.tv_sec = 0; + next_time.tv_nsec = THREAD_HANDLER_TIMEOUT_NS; + rc = clock_nanosleep(CLOCK_REALTIME, 0, &next_time, NULL); + + } else { + next_time.tv_sec = sched_next / (NSECS_PER_SEC); + next_time.tv_nsec = sched_next % (NSECS_PER_SEC); + rc = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next_time, NULL); + } + + if (rc > 0) { + if (rc == EINTR) + return 0; + else { + // Error occurs + ERR("clock_nanosleep for time (%" PRIu64 ") failed.", sched_next); + return -1; + } + } else { + for (i = 0; i < total_wakeup_slots; i++) { + //thr_thread_slot_t *slot = recv_events[i].data.ptr; + unsigned int slot_index = next_wakeup_slots[i]; + thr_thread_slot_t *slot = &thread_ptr->slots[slot_index]; + + // check if the slot was not removed while sleeping + // FIXME this is not completely safe, as the slot can be reused while sleeping. + if (!thread_ptr->slots[i].is_used) + continue; + + nsleep = (struct thread_sleep_nanosleep_data *)slot->sleep_data; + // check if the timer was not stopped while sleeping; + if (!nsleep->is_armed) + continue; + + // this slot is now event ready + recv_events[ready_slots].data.ptr = (void *)slot; + recv_events[ready_slots].events = EPOLLIN; // needed to make sure the time-out count gets reset + ready_slots++; + + // make sure we don't get woken up again for previous events + nsleep->is_armed = 0; + } + + return ready_slots; + } +} + +int thread_init(void) +{ + int count; + int result; + + for (count = 0; count < MAX_THREADS; ++count) { + thr_thread_t *thread_ptr = &g_thread_array[count]; + + // Create epoll for the thread + result = epoll_create1(0); + if (result < 0) { + ERR("Create epoll handle failed. Error %s", strerror(errno)); + return -1; + } + thread_ptr->poll_fd = result; + + DBG("epoll %d fd %d created", count, thread_ptr->poll_fd); + + // Init statistics + stats_init(&thread_ptr->stats.epoll_event, 31, thread_ptr, NULL); + stats_init(&thread_ptr->stats.time_2epoll, 31, thread_ptr, NULL); + stats_init(&thread_ptr->stats.processing_time, 31, thread_ptr, NULL); + + thread_ptr->is_first_poll = 1; + thread_ptr->last_poll_time = 0; + thread_ptr->exit_flag = 0; + thread_ptr->num_slots = 0; + + // Default sleep handler + thread_ptr->sleep_handler = thread_sleep_epoll; + + // Slot data initialization + thread_slot_init_data(thread_ptr); + + result = pthread_create(&thread_ptr->id, NULL, &s_data_thread_handle, (void *)(intptr_t) count); + if (result != 0) { + ERR("thread %d create failed, error %s", count, strerror(result)); + return -1; + } + DBG("Thread %d created with id %d", count, (int)thread_ptr->id); + + } + + // Initialize mutex + pthread_mutex_init(&thread_mutex, NULL); + + return 0; +} + +int thread_exit(void) +{ + int count; + int result; + void *retval; + + for (count = 0; count < MAX_THREADS; count++) { + thr_thread_t *thread_ptr = &g_thread_array[count]; + + // Request thread quit + thread_ptr->exit_flag = 1; + + // join thread + pthread_join(thread_ptr->id, &retval); + DBG("thread %d id %d joint with result %d", count, (int)thread_ptr->id, (int)(intptr_t)retval); + + // Close epoll + result = close(thread_ptr->poll_fd); + if (result != 0) { + ERR("epoll %d failed to close, error %s", thread_ptr->poll_fd, strerror(errno)); + } else { + DBG("epoll %d closed", thread_ptr->poll_fd); + } + + // De-init slot data + thread_slot_deinit_data(thread_ptr); + } + pthread_mutex_destroy(&thread_mutex); + + return 0; +} + +static void __thread_print_stats(thr_thread_t *thread_ptr) +{ + thr_thread_stat_t *stats = &thread_ptr->stats_snap; + + if (stats->pending) { + INF_LOG("Thread (%p) epoll count: %d, epoll number: %d/%d/%d, epoll time: %6d/%7d/%7d processing: %6d/%7d/%7d", + thread_ptr, stats->epoll_event.current_count, stats->epoll_event.min, + stats->epoll_event.mean, stats->epoll_event.max, stats->time_2epoll.min, + stats->time_2epoll.mean, stats->time_2epoll.max, stats->processing_time.min, + stats->processing_time.mean, stats->processing_time.max); + stats->pending = false; + } +} + +void thread_print_stats() +{ + int i; + + for (i = 0; i < MAX_THREADS; i++) { + thr_thread_t *thread_ptr = &g_thread_array[i]; + + if (thread_ptr->num_slots > 0) + __thread_print_stats(thread_ptr); + } +} + +static void thread_dump_stats(thr_thread_t *thread_ptr) +{ + thr_thread_stat_t *stats = &thread_ptr->stats; + thr_thread_stat_t *stats_snap = &thread_ptr->stats_snap; + + // Thread global statistics + if (thread_ptr->num_slots > 0) { + stats_compute(&stats->epoll_event); + stats_compute(&stats->time_2epoll); + stats_compute(&stats->processing_time); + memcpy(stats_snap, stats, sizeof(thr_thread_stat_t)); + stats_reset(&stats->time_2epoll); + stats_reset(&stats->epoll_event); + stats_reset(&stats->processing_time); + stats_snap->pending = true; + } +} + +static void *s_data_thread_handle(void *param) +{ + intptr_t final_result = 0; + cpu_set_t cpu_set; + int thread_index = (int)(intptr_t)param; + thr_thread_t *thread_ptr = &g_thread_array[thread_index]; + struct epoll_event recv_events[MAX_THREAD_SLOTS]; + struct sched_param thread_param = { + .sched_priority = thread_ptr->priority, + }; + unsigned int poll_time; + struct timespec ts; + unsigned long long tlast_timeout = 0, now = 0, tlast_stat = 0; + + DBG("thread %d, cpu %d, poll_fd: %d, max_slots: %d", thread_index, thread_ptr->cpu_core, thread_ptr->poll_fd, thread_ptr->max_slots); + // If the max_slots is 0, means the thread is not be used. Just quit. + if (0 >= thread_ptr->max_slots) { + INF("The thread %d on cpu %d will not be used because max_slots = 0", thread_index, thread_ptr->cpu_core); + goto lb_exit; + } + + // Make thread more real time + if (sched_setscheduler(0, SCHED_FIFO, &thread_param) < 0) { + ERR("%d - sched_setscheduler failed with error %d - %s", thread_index, errno, strerror(errno)); + final_result = -1; + goto lb_exit; + } + + // Specify which core number that the thread will run on + CPU_ZERO(&cpu_set); + CPU_SET(thread_ptr->cpu_core, &cpu_set); + if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) == -1) + ERR("%d - sched_setaffinity failed with error %d - %s", thread_index, errno, strerror(errno)); + + // Loop until exit flag of the thread is set + while (!thread_ptr->exit_flag) { + int n, i, res; + + n = thread_ptr->sleep_handler(thread_ptr, recv_events); + + if (n < 0) { + // Error occurs + ERR("%d - sleep_handler failed", thread_index); + final_result = -1; + goto lb_exit; + } + + res = clock_gettime(CLOCK_MONOTONIC, &ts); + + if (res == 0) { + now = (unsigned long long) ts.tv_sec * NSECS_PER_SEC + ts.tv_nsec; + + if ((now - tlast_stat) >= THREAD_STATS_PERIOD_NS) { + // Dump statistic each 10s + thread_dump_stats(thread_ptr); + tlast_stat = now; + } + + if (!tlast_timeout) + tlast_timeout = now; + + if (now - tlast_timeout >= THREAD_HANDLER_TIMEOUT_NS) { + /*Check if we have timeout handlers to execute, every 100 ms*/ + thread_check_timeout_slots(thread_ptr, now - tlast_timeout); + tlast_timeout = now; + } + } else { + ERR("clock_gettime() failed: %s", strerror(errno)); + } + + /* Update events stats */ + stats_update(&thread_ptr->stats.epoll_event, n); + + /* If we got interrupted or timedout, loop again */ + if (n == 0) + continue; + + + if (0 != res) { + ERR("could not get clock real-time, error: %s", strerror(errno)); + } else { + poll_time = (unsigned long long)ts.tv_sec*NSECS_PER_SEC + ts.tv_nsec; + if (!thread_ptr->is_first_poll) { + stats_update(&thread_ptr->stats.time_2epoll, poll_time - thread_ptr->last_poll_time); + // DBG("%p time_2epoll: %d - %d - %d", thread_ptr, thread_ptr->time_2epoll.current_count, poll_time - thread_ptr->last_poll_time, thread_ptr->time_2epoll.min); + // DBG("poll: %d, %d", n, poll_time - last_poll_time); + } else { + thread_ptr->is_first_poll = 0; + } + thread_ptr->last_poll_time = poll_time; + } + + // Events received, process them + for (i = 0; i < n; ++i) { + int events = recv_events[i].events; + int fd = recv_events[i].data.fd; + thr_thread_slot_t *slot = (thr_thread_slot_t *)recv_events[i].data.ptr; + + if (events & EPOLLERR) { + ERR("%d - fd %d with poll error", thread_index, fd); + continue; + } + + if (!slot) + continue; + + /* Reset timeout counter*/ + if (events & (EPOLLIN | EPOLLOUT)) + slot->timeout_count = 0; + + slot->handler(slot->data, events); + } + + res = clock_gettime(CLOCK_MONOTONIC, &ts); + if (res == 0) { + poll_time = (unsigned long long)ts.tv_sec * NSECS_PER_SEC + ts.tv_nsec; + stats_update(&thread_ptr->stats.processing_time, poll_time - thread_ptr->last_poll_time); + } else { + ERR("clock get real-time failed"); + } + + sched_yield(); + } + +lb_exit: + return (void *)final_result; +} + +static int thread_count_used_slot(thr_thread_t *thread) +{ + int result = 0; + int i; + + for (i = 0; i < thread->max_slots; ++i) { + if (thread->slots[i].is_used != 0) { + result ++; + } + } + + return result; +} + +static int thread_check_capability_match(thr_thread_t *thread, int capabilities) +{ + int tmp = thread->thread_capabilities; + + // if not match capacity, return 0 + if ((tmp & capabilities) != capabilities) { + return 0; + } + + return 1; +} + +static thr_thread_slot_t *thread_get_first_idle_slot(thr_thread_t *thread) +{ + int i; + + for (i = 0; i < thread->max_slots; ++i) { + if (!thread->slots[i].is_used) { + return &thread->slots[i]; + } + } + + return NULL; +} + +static void thread_check_timeout_slots(thr_thread_t *thread, unsigned int timeout_time) +{ + int i; + + for (i = 0; i < thread->max_slots; ++i) { + if (thread->slots[i].is_used && thread->slots[i].timeout_handler) { + thread->slots[i].timeout_count += timeout_time; + if(thread->slots[i].timeout_count > thread->slots[i].max_timeout) + thread->slots[i].timeout_handler(thread->slots[i].data); + } + } + +} + +int __thread_slot_add(int capabilities, int fd, unsigned int events, void *data, + int (*handler)(void *data, unsigned int events), + int (*timeout_handler)(void *data), + int max_timeout, thr_thread_slot_t **slot, + int (*sleep_handler)(thr_thread_t *thread_ptr, struct epoll_event *recv_events), + void *sleep_data) +{ + thr_thread_t *thread_ptr = NULL; + thr_thread_slot_t *thread_slot_ptr = NULL; + int i; + int tmp; + struct epoll_event event; + + if ((events != 0) && (sleep_handler != thread_sleep_epoll)) { + ERR("Cannot use both epoll and custom sleep handler"); + return -1; + } + + // Lock data + pthread_mutex_lock(&thread_mutex); + + // Find best idle slot + for (i = 0; i < MAX_THREADS; ++i) { + thr_thread_t *cur_thread = &g_thread_array[i]; + + if (!thread_check_capability_match(cur_thread, capabilities)) { + // Thread does not match capabilities + continue; + } + + if ((cur_thread->num_slots != 0) && (cur_thread->sleep_handler != sleep_handler)) { + //Thread already actively using another sleep handler + continue; + } + + tmp = thread_count_used_slot(cur_thread); + if (tmp < cur_thread->max_slots) { + // This thread has idle slot + thread_ptr = cur_thread; + INF("Using thread number %d", i); + break; + } + } + + DBG("thread_ptr (%p)", thread_ptr); + if (!thread_ptr) { + // No thread found + ERR("No thread found for capabilities 0x%x", capabilities); + pthread_mutex_unlock(&thread_mutex); + return -1; + } + + DBG("CPU core: %d", thread_ptr->cpu_core); + thread_slot_ptr = thread_get_first_idle_slot(thread_ptr); + DBG("thread_slot_ptr (%p)", thread_slot_ptr); + if (!thread_slot_ptr) { + // No slot found + ERR("No thread slot found"); + pthread_mutex_unlock(&thread_mutex); + return -1; + } + + DBG("Update slot (%p) info, fd: %d, data: %p, handler: %p", thread_slot_ptr, fd, data, handler); + // Update slot information + thread_slot_ptr->is_used = 1; + thread_slot_ptr->data = data; + thread_slot_ptr->handler = handler; + thread_slot_ptr->timeout_count = 0; + thread_slot_ptr->max_timeout = max_timeout; + thread_slot_ptr->timeout_handler = timeout_handler; + // Unlock the slot lock for first time + pthread_mutex_unlock(&thread_slot_ptr->slot_lock); + + // Update output value + DBG("slot (%p) -> %p", slot, thread_slot_ptr); + thread_ptr->num_slots++; + *slot = thread_slot_ptr; + + // Always set the sleep fields with the provided ones + thread_ptr->sleep_handler = sleep_handler; + thread_slot_ptr->sleep_data = sleep_data; + + if (events != 0) { + // If events is provided, add slot into epoll monitor + thread_slot_ptr->fd = fd; + event.data.fd = fd; + event.data.ptr = thread_slot_ptr; + event.events = (events & (EPOLLIN | EPOLLOUT)) | EPOLLERR | EPOLLHUP; + + DBG("Add fd %d into epoll %d", fd, thread_ptr->poll_fd); + if (-1 == epoll_ctl(thread_ptr->poll_fd, EPOLL_CTL_ADD, fd, &event)) { + // epoll add fd error. Clear is_used flag and return NULL + ERR("epoll_ctl failed, %s", strerror(errno)); + thread_slot_ptr->is_used = 0; + thread_ptr->num_slots--; + *slot = NULL; + pthread_mutex_unlock(&thread_mutex); + return -1; + } + INF("Add fd %d into epoll of thread %p", fd, thread_ptr); + } else { + // This is a nanosleep handler + INF("Enabled slot %p of thread %p with nanosleep handler", thread_slot_ptr, thread_ptr); + } + + thread_slot_ptr->is_enabled = 1; + + pthread_mutex_unlock(&thread_mutex); + + return 0; +} + +int thread_slot_add(int capabilities, int fd, unsigned int events, void *data, + int (*handler)(void *data, unsigned int events), + int (*timeout_handler)(void *data), + int max_timeout, thr_thread_slot_t **slot) +{ + return __thread_slot_add(capabilities, fd, events, data, + handler, timeout_handler, max_timeout, slot, thread_sleep_epoll, NULL); +} + +int thread_slot_set_events(thr_thread_slot_t *slot, int enable, unsigned int req_events) +{ + struct epoll_event event; + thr_thread_t *thread; + + // Lock data + pthread_mutex_lock(&thread_mutex); + + thread = thread_get_from_slot(slot); + if (!thread) { + ERR("Thread not found for slot %p", slot); + pthread_mutex_unlock(&thread_mutex); + return -1; + } + + if(enable) { + + if (slot->is_enabled) { + pthread_mutex_unlock(&thread_mutex); + return 0; + } + + // add slot into epoll monitor + + event.data.fd = slot->fd; + event.data.ptr = slot; + event.events = (req_events & (EPOLLIN | EPOLLOUT)) | EPOLLERR | EPOLLHUP; + + DBG("Add fd %d into epoll %d", slot->fd, thread->poll_fd); + if (-1 == epoll_ctl(thread->poll_fd, EPOLL_CTL_ADD, slot->fd, &event)) { + // epoll add fd error. Clear is_used flag and return NULL + ERR("epoll_ctl failed, %s", strerror(errno)); + pthread_mutex_unlock(&thread_mutex); + return -1; + } + + slot->is_enabled = 1; + } else { + + if (!slot->is_enabled) { + pthread_mutex_unlock(&thread_mutex); + return 0; + } + // remove slot from epoll monitor + DBG("remove fd %d from epoll", slot->fd); + if (-1 == epoll_ctl (thread->poll_fd, EPOLL_CTL_DEL, slot->fd, NULL)) { + // epoll delete fd error. + ERR("remove the fd %d failed", slot->fd); + pthread_mutex_unlock(&thread_mutex); + return -1; + } + slot->is_enabled = 0; + } + + pthread_mutex_unlock(&thread_mutex); + return 0; +} + +int thread_slot_free(thr_thread_slot_t *slot) +{ + thr_thread_t *thread; + // Lock data + pthread_mutex_lock(&thread_mutex); + + thread = thread_get_from_slot(slot); + if (!thread) { + ERR("Thread not found for slot %p", slot); + pthread_mutex_unlock(&thread_mutex); + return -1; + } + + if (!slot->is_enabled) + goto deinit; + + if (thread->sleep_handler == thread_sleep_epoll) { + // remove slot from epoll monitor + DBG("remove fd %d from epoll", slot->fd); + if (-1 == epoll_ctl (thread->poll_fd, EPOLL_CTL_DEL, slot->fd, NULL)) { + // epoll delete fd error. + ERR("remove the fd %d failed", slot->fd); + pthread_mutex_unlock(&thread_mutex); + return -1; + } + } else { + //sleep_handler is thread_sleep_nanosleep + struct thread_sleep_nanosleep_data *nsleep; + + nsleep = (struct thread_sleep_nanosleep_data *)slot->sleep_data; + + nsleep->is_armed = 0; + + } + +deinit: + // epoll fd is removed + slot->is_used = 0; + thread->num_slots --; + slot->is_enabled = 0; + slot->timeout_count = 0; + slot->max_timeout = 0; + slot->timeout_handler = NULL; + + pthread_mutex_unlock(&thread_mutex); + + return 0; +} + +static thr_thread_t *thread_get_from_slot(thr_thread_slot_t *slot) +{ + int i; + int j; + + for (i = 0; i < MAX_THREADS; ++i) { + thr_thread_t *cur_thread = &g_thread_array[i]; + for (j = 0; j < cur_thread->max_slots; ++j) { + thr_thread_slot_t *cur_slot = &cur_thread->slots[j]; + + if (!cur_slot->is_used) { + continue; + } + + if (cur_slot == slot) { + return cur_thread; + } + } + } + + return NULL; +} + +static inline void thread_slot_reset_data(thr_thread_slot_t *slot) +{ + slot->is_used = 0; + slot->fd = 0; + slot->data = NULL; + slot->handler = NULL; + slot->timeout_count = 0; + slot->max_timeout = 0; + slot->timeout_handler = NULL; +} + +static void thread_slot_init_data(thr_thread_t *thread) +{ + int i; + + for (i = 0; i < MAX_THREAD_SLOTS; ++i) { + thr_thread_slot_t *slot = &thread->slots[i]; + + // Reset data of slot + thread_slot_reset_data(slot); + // Initialize mutex lock + pthread_mutex_init(&slot->slot_lock, NULL); + } +} + +static void thread_slot_deinit_data(thr_thread_t *thread) +{ + int i; + + for (i = 0; i < MAX_THREAD_SLOTS; ++i) { + thr_thread_slot_t *slot = &thread->slots[i]; + + // Reset data of slot + thread_slot_reset_data(slot); + // Destroy mutex lock + pthread_mutex_destroy(&slot->slot_lock); + } +} diff --git a/apps/linux/common/thread.h b/apps/linux/common/thread.h new file mode 100644 index 0000000..396a011 --- /dev/null +++ b/apps/linux/common/thread.h @@ -0,0 +1,146 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __THREAD_MANAGEMENT_H__ +#define __THREAD_MANAGEMENT_H__ + +#include +#include "thread_config.h" + +extern thr_thread_t g_thread_array[MAX_THREADS]; + +/** + * @addtogroup thread + * @{ + */ + +/** Create all routing required threads. + * + * @details This function will create four threads (equals to number of CPU cores) to routing + * data between AVB stream and ALSA device. Each thread executes on a specified CPU + * core. + * + * @return 0 if success or negative error code + */ +int thread_init(); + +/** Terminate all routing threads, free resources. + * + * @details This function will terminate all threads are created by @ref thread_init function. + * + * @return 0 if success or negative error code + */ +int thread_exit(); + +/** Add a route slot into thread (generic version). + * + * @details This function finds a thread with a free slot and matching thread capabilities + * parameter then populates this new slot with its information. + * + * @param[in] capabilities Required capabilities of the slot + * @param[in] fd Input file descriptor of input source. Used by default epoll sleep + * handler, ignored otherwise. + * @param[in] events Events epoll should listen to. If non-zero, sleep_handler must be set + * to thread_sleep_epoll or an error will be returned. + * @param[in] data Private data of the slot, this data will be passed to handler + * function whenever this function is called. + * @param[in] handler Handler function, will be called to process data whenever fd has + * input data. + * @param[in] timeout_handler Handler function will be executed in case epoll timed out + * @param[in] max_timeout Max timeout (ns) since last poll before timeout handler is executed + * @param[out] slot When thread slot is successful added, this will contains created + * thread slot information + * @param[in] sleep_handler Function to be called to put the thread to sleep. A thread with + * matching capabilities will only be selected if the requested + * sleep_handler matches the one already in place. + * @param[in] sleep_data Optional slot-specific data to be used by the sleep handler. + * + * @return 0 if success or negative error code + */ + +int __thread_slot_add(int capabilities, int fd, unsigned int events, void *data, + int (*handler)(void *data, unsigned int events), + int (*timeout_handler)(void *data), + int max_timeout, thr_thread_slot_t **slot, + int (*sleep_handler)(thr_thread_t *thread_ptr, struct epoll_event *recv_events), + void *sleep_data); + +/** Add a route slot into thread (default epoll version). + * + * @details This function finds a thread with a free slot and matching thread capabilities + * parameter then populates this new slot with its information, using the default + * epoll sleep_handler. + * + * @param[in] capabilities Required capabilities of the slot + * @param[in] fd Input file descriptor of input source + * * @param[in] events Events epoll should listen to. + * @param[in] data Private data of the slot, this data will be passed to handler + * function whenever this function is called. + * @param[in] handler Handler function, will be called to process data whenever fd has + * input data. + * @param[in] timeout_handler Handler function will be executed in case epoll timed out + * @param[in] max_timeout Max timeout (ns) since last poll before timeout handler is executed + * @param[out] slot When thread slot is successful added, this will contains created + * thread slot information + * + * @return 0 if success or negative error code + */ +int thread_slot_add(int capabilities, int fd, unsigned int events, void *data, + int (*handler)(void *data, unsigned int events), + int (*timeout_handler)(void *data), + int max_timeout, thr_thread_slot_t **slot); + +/** Free a thread slot and its resources. + * + * @details This function will remove the slot fd from monitor fds. + * + * @param[in] slot Pointer to slot that you want to remove. + * + * @return 0 if success or negative error code + */ +int thread_slot_free(thr_thread_slot_t *slot); + +/** Modify the fd monitor of a thread slot. + * + * @details This function will enable or disable the requested events on the monitored fd + * of the slot. + * + * @param[in] slot Pointer to slot that you want to remove. + * @param[in] enable flag to enable or disable. + * @param[in] req_events The events to enbale/disable: EPOLLIN and/or EPOLLOUT + * + * @return 0 if success or negative error code + */ +int thread_slot_set_events(thr_thread_slot_t *slot, int enable, unsigned int req_events); + +/** Print global thread scheduling statistics + * + * @details This function prints all threads statistics when available. + * Should be polled regularly. + * + * @return None + */ +void thread_print_stats(); + +/** Sleep handler based on clock_nanosleep, to be used with thread framework. + * + */ +int thread_sleep_nanosleep(thr_thread_t *thread_ptr, struct epoll_event *recv_events); + +/** Private data for clock_nanosleep sleep handler + * + */ +struct thread_sleep_nanosleep_data { + uint64_t next; /**< Absolute time of next event, in nanoseconds. */ + bool is_armed; /**< Whether the slot is armed (must fire an event when reaching next). */ +}; + +/** + * @} + */ + +#endif /* __THREAD_MANAGEMENT_H__ */ diff --git a/apps/linux/common/thread_config.h b/apps/linux/common/thread_config.h new file mode 100644 index 0000000..5d3082a --- /dev/null +++ b/apps/linux/common/thread_config.h @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __THREAD_CONFIG_H__ +#define __THREAD_CONFIG_H__ + +#include +#include +#include +#include "stats.h" +/** + * @addtogroup thread + * @{ + */ +#define MAX_THREADS 8 /**< Maximum number of threads can be used for data transfer */ +#define MAX_THREAD_SLOTS 10 /**< Maximum number of slots in threads */ +/** + * @brief Thread capability mask + */ +typedef enum _THREAD_CAPABILITY { + THR_CAP_STREAM_TALKER = 0x01, /**< Thread can process AVB talker stream */ + THR_CAP_STREAM_LISTENER = 0x02, /**< Thread can process AVB listener stream */ + THR_CAP_STREAM_AUDIO = 0x04, /**< Thread can process AVB stream has format AAF */ + THR_CAP_STREAM_CRF = 0x08, /**< Thread can process AVB stream has format CRF */ + THR_CAP_STREAM_VIDEO = 0x10, + THR_CAP_ALSA = 0x20, + THR_CAP_GSTREAMER = 0x40, + THR_CAP_GSTREAMER_SYNC = 0x80, + THR_CAP_STATS = 0x100, + THR_CAP_TIMER = 0x200, + THR_CAP_CONTROLLED = 0x400, + THR_CAP_GST_MULTI = 0x800, + THR_CAP_GST_BUS_TIMER = 0x1000, + THR_CAP_TSN_LOOP = 0x2000, + THR_CAP_TSN_PT = 0x4000 +} thr_capability_t; + +/** + * @brief Thread slot structure. + */ +typedef struct _THREAD_SLOT { + char is_used; /**< This flag specifies whether the current slot is used or not */ + char is_enabled; /**< This flag specifies whether the current fd is enabled for monitoring or not*/ + int fd; /**< File descriptor of slot, this used to mornitoring input events */ + void *data; /**< User parameter for slot handler */ + int (*handler)(void *data, unsigned int events); /**< Slot handler function */ + unsigned int max_timeout; /**< Maximum time since last poll before executing the timeout handler */ + unsigned int timeout_count; /**< Time since last poll */ + int (*timeout_handler) (void *data); /**< Timeout handler */ + void *sleep_data; /**< parameters to pass to the sleep handler. */ + pthread_mutex_t slot_lock; /**< Thread slot mutex lock */ +} thr_thread_slot_t; + +typedef struct _THREAD_STAT { + struct stats epoll_event; /**< Number of events ready when epoll returns */ + struct stats time_2epoll; /**< Time between two continuous epoll returns */ + struct stats processing_time; /**< Time to process all ready epoll events */ + bool pending; +} thr_thread_stat_t; +/** + * @brief Thread data structure, store require information for each thread + */ +typedef struct _THREAD_THREAD_DATA { + pthread_t id; /**< ID of the thread */ + int poll_fd; /**< File descriptor of epoll */ + int thread_capabilities; /**< Capabilities of this thread. Can combine flags of @thr_capability_t */ + char cpu_core; /**< CPU core number that thread will run on */ + int priority; /**< Thread priority */ + char max_slots; /**< Max number of slots in a thread */ + char exit_flag; /**< If this flag is set, the thread will try to terminate itself */ + thr_thread_slot_t slots[MAX_THREAD_SLOTS]; /**< Array of data slots */ + int num_slots; /**< Number of used slots */ + int (*sleep_handler)(struct _THREAD_THREAD_DATA *thread_ptr, struct epoll_event *recv_events); /**< function to call to put thread to sleep */ + + char is_first_poll; /**< Indicate the first poll loop of thread handle */ + unsigned int last_poll_time; /**< Time of the last poll */ + thr_thread_stat_t stats; + thr_thread_stat_t stats_snap; +} thr_thread_t; + +/** @} */ +#endif /* __THREAD_CONFIG_H__ */ diff --git a/apps/linux/common/time.c b/apps/linux/common/time.c new file mode 100644 index 0000000..8751914 --- /dev/null +++ b/apps/linux/common/time.c @@ -0,0 +1,73 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "time.h" + +int gettime_ns(uint64_t *ns) +{ + struct timespec now; + int rc; + + rc = clock_gettime(CLOCK_REALTIME, &now); + if (rc < 0) { + printf("clock_gettime() failed: %s\n", strerror(errno)); + return rc; + } + + *ns = (uint64_t)now.tv_sec * NSECS_PER_SEC + now.tv_nsec; + + return 0; +} + +int gettime_us(uint64_t *us) +{ + int rc; + + rc = gettime_ns(us); + *us /= 1000; + + return rc; +} + +int gettime_ns_monotonic(uint64_t *ns) +{ + struct timespec now; + int rc; + + rc = clock_gettime(CLOCK_MONOTONIC, &now); + if (rc < 0) { + printf("clock_gettime() failed: %s\n", strerror(errno)); + return rc; + } + + *ns = (uint64_t)now.tv_sec * NSECS_PER_SEC + now.tv_nsec; + + return 0; +} + +int gettime_s_monotonic(uint64_t *secs) +{ + struct timespec now; + int rc; + + rc = clock_gettime(CLOCK_MONOTONIC, &now); + if (rc < 0) { + printf("clock_gettime() failed: %s\n", strerror(errno)); + return rc; + } + + *secs = (uint64_t)now.tv_sec; + + return 0; +} diff --git a/apps/linux/common/time.h b/apps/linux/common/time.h new file mode 100644 index 0000000..ed2ebf1 --- /dev/null +++ b/apps/linux/common/time.h @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_TIME_H_ +#define _COMMON_TIME_H_ + +#include + +#define NSECS_PER_SEC 1000000000 +#define NSECS_PER_MSEC 1000000 +#define USECS_PER_SEC 1000000 +#define MSECS_PER_SEC 1000 + +int gettime_us(uint64_t *us); +int gettime_ns(uint64_t *ns); +int gettime_ns_monotonic(uint64_t *ns); +int gettime_s_monotonic(uint64_t *secs); + +#endif /* _COMMON_TIME_H_ */ diff --git a/apps/linux/common/timer.c b/apps/linux/common/timer.c new file mode 100644 index 0000000..6a5dd16 --- /dev/null +++ b/apps/linux/common/timer.c @@ -0,0 +1,100 @@ +/* + * Copyright 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "time.h" + +#include +#include +#include +#include + +int sleep_ns(uint64_t ns) +{ + struct timespec req, rem; + + req.tv_sec = ns / NSECS_PER_SEC; + req.tv_nsec = ns - req.tv_sec * NSECS_PER_SEC; + +retry: + if (nanosleep(&req, &rem) < 0) { + if (errno == EINTR) { + req = rem; + goto retry; + } else { + return -1; + } + } + + return 0; +} + +int sleep_ms(uint64_t ms) +{ + return sleep_ns(ms * NSECS_PER_MSEC); +} + +static int __create_timerfd_periodic(int clkid, int flags) +{ + int timer_fd; + + timer_fd = timerfd_create(clkid, flags); + if (timer_fd < 0) { + printf("%s timerfd_create() failed %s\n", __func__, strerror(errno)); + return -1; + } + + return timer_fd; +} + +int create_timerfd_periodic(int clk_id) +{ + return __create_timerfd_periodic(clk_id, 0); +} + +int create_timerfd_periodic_abs(int clk_id) +{ + return __create_timerfd_periodic(clk_id, 0); +} + +static int __start_timerfd_periodic(int fd, int flags, time_t val_secs, long val_nsecs, + time_t it_secs, long it_nsecs) +{ + struct itimerspec timer; + + timer.it_interval.tv_sec = it_secs; + timer.it_interval.tv_nsec = it_nsecs; + timer.it_value.tv_sec = val_secs; + timer.it_value.tv_nsec = val_nsecs; + + if (timerfd_settime(fd, flags, &timer, NULL) < 0) { + printf("%s timerfd_settime() failed %s\n", __func__, strerror(errno)); + return -1; + } + + return 0; +} + +int start_timerfd_periodic(int fd, time_t it_secs, long it_nsecs) +{ + return __start_timerfd_periodic(fd, 0, it_secs, it_nsecs, it_secs, it_nsecs); +} + +int start_timerfd_periodic_abs(int fd, time_t val_secs, long val_nsecs, + time_t it_secs, long it_nsecs) +{ + int flags = TFD_TIMER_ABSTIME; + +#ifdef TFD_TIMER_CANCEL_ON_SET + flags |= TFD_TIMER_CANCEL_ON_SET; +#endif + + return __start_timerfd_periodic(fd, flags, val_secs, val_nsecs, it_secs, it_nsecs); +} + +int stop_timerfd(int fd) +{ + return __start_timerfd_periodic(fd, 0, 0, 0, 0, 0); +} diff --git a/apps/linux/common/timer.h b/apps/linux/common/timer.h new file mode 100644 index 0000000..efeb6fb --- /dev/null +++ b/apps/linux/common/timer.h @@ -0,0 +1,22 @@ +/* + * Copyright 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _COMMON_TIMER_H_ +#define _COMMON_TIMER_H_ + +#include + +int sleep_ms(uint64_t ms); +int sleep_ns(uint64_t ns); + +int create_timerfd_periodic(int clk_id); +int start_timerfd_periodic(int fd, time_t it_secs, long it_nsecs); +int create_timerfd_periodic_abs(int clk_id); +int start_timerfd_periodic_abs(int fd, time_t val_secs, long val_nsecs, + time_t it_secs, long it_nsecs); +int stop_timerfd(int fd); + +#endif /* _COMMON_TIMER_H_ */ diff --git a/apps/linux/common/ts_parser.c b/apps/linux/common/ts_parser.c new file mode 100644 index 0000000..a757aff --- /dev/null +++ b/apps/linux/common/ts_parser.c @@ -0,0 +1,377 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "time.h" +#include "ts_parser.h" + +#define TS_PARSER_DEBUG 1 + +#define SYNC_BYTE 0x47 + +#define SYSTEM_CLOCK_FREQUENCY 27000000 + +#define NSECS_PER_USEC 1000 +#define NSEC_PER_MSEC 1000000 +#define USEC_PER_SEC 1000000 + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; + +struct __attribute__ ((packed)) transport_header { + u8 sync_byte; + + u8 pid_high:5; + u8 priority:1; + u8 payload_unit_start_indicator:1; + u8 error_indication:1; + + u8 pid_low:8; + + u8 continuity_counter:4; + u8 adaptation_field_control:2; + u8 scrambling_control:2; +}; + +struct __attribute__ ((packed)) adaptation_header { + u8 length; + + u8 extension_flag:1; + u8 transport_private_data_flag:1; + u8 splicing_point_flag:1; + u8 opcr_flag:1; + u8 pcr_flag:1; + u8 elementary_stream_priority_indicator:1; + u8 random_acess_indicator:1; + u8 discontinuity_indicator:1; +}; + +struct __attribute__ ((packed)) pcr_header { + u32 pcr_clock_reference_base_high; + + u8 program_clock_reference_extension_high:1; + u8 reserved:6; + u8 pcr_clock_reference_base_low:1; + + u8 program_clock_reference_extension_low; +}; + +static inline u64 pcr_to_ns(unsigned long long pcr) +{ + return (pcr * (NSECS_PER_SEC/1000000)) / (SYSTEM_CLOCK_FREQUENCY/1000000); +} + +static inline u64 pcr_to_us(unsigned long long pcr) +{ + return (pcr * (USECS_PER_SEC/1000000)) / (SYSTEM_CLOCK_FREQUENCY/1000000); +} + +static int adaptation_field(struct ts_parser *p, void *buf) +{ + struct adaptation_header *a_hdr = buf; + +// printf("%x %x\n", a_hdr->length, a_hdr->pcr_flag); + + if (!a_hdr->length) + return 0; + + if (a_hdr->pcr_flag) { + struct pcr_header *p_hdr = (struct pcr_header *)(a_hdr + 1); + u64 pcr_base; + u16 pcr_ext; + u64 pcr; + + pcr_base = (((u64)ntohl(p_hdr->pcr_clock_reference_base_high)) << 1) | ((u64)p_hdr->pcr_clock_reference_base_low); + pcr_ext = (((u16)p_hdr->program_clock_reference_extension_high) << 8) | ((u16)p_hdr->program_clock_reference_extension_low); + pcr = pcr_base * 300 + pcr_ext; + + if (p->pcr_count == 1) { + printf("pcr init\n"); + gettime_ns(&p->t0_ns); + p->pcr0 = p->pcr[1]; + } + + p->pcr_count++; + + p->count[0] = p->count[1]; + p->pcr[0] = p->pcr[1]; + p->count[1] = p->byte_count; + p->pcr[1] = pcr; + + if (p->pcr_count >= 2) + p->transport_rate = ((p->count[1] - p->count[0]) * SYSTEM_CLOCK_FREQUENCY) / (p->pcr[1] - p->pcr[0]); + } + + return 0; +} + +static void print_pcr(unsigned long long byte, unsigned long long pcr, unsigned long long pcr_dt_ns) +{ +#if TS_PARSER_DEBUG >= 2 + printf("packet %llu, byte %llu, pcr %llu, dt %llu\n", byte / PES_SIZE + 1, byte, pcr, pcr_dt_ns); +#endif +} + +static void ts_parser_debug(struct ts_parser *p, unsigned long long dt_ns, unsigned long long byte) +{ +#if TS_PARSER_DEBUG >= 3 + printf("%10llu %10llu %10llu %10llu %10llu %10llu %10u\n", + p->count[0], byte, p->count[1], + pcr_to_ns(p->pcr[0] - p->pcr0), dt_ns, pcr_to_ns(p->pcr[1] - p->pcr0), p->transport_rate); +#endif +} + + +static int transport_packet(struct ts_parser *p, void *buf) +{ + struct transport_header *t_hdr = buf; + + if (t_hdr->sync_byte != SYNC_BYTE) + return -1; + +// printf("pid: %x\n", t_hdr->pid_low | (t_hdr->pid_high << 8)); + + if ((t_hdr->adaptation_field_control == 0x2) || (t_hdr->adaptation_field_control == 0x3)) + adaptation_field(p, (void *)(t_hdr + 1)); + + p->byte_count += PES_SIZE; + + return 0; +} + +void ts_parser_init(struct ts_parser *p) +{ + memset(p, 0, sizeof(*p)); +} + +int ts_parser_is_pcr(void *buf, u64 *pcr) +{ + struct transport_header *t_hdr = buf; + struct adaptation_header *a_hdr; + struct pcr_header *p_hdr; + u64 pcr_base; + u16 pcr_ext; + + if (t_hdr->sync_byte != SYNC_BYTE) + return 0; + +// printf("pid: %x\n", t_hdr->pid_low | (t_hdr->pid_high << 8)); + + if ((t_hdr->adaptation_field_control != 0x2) && (t_hdr->adaptation_field_control != 0x3)) + return 0; + + a_hdr = (struct adaptation_header *)(t_hdr + 1); + +// printf("%x %x\n", a_hdr->length, a_hdr->pcr_flag); + + if (!a_hdr->length) + return 0; + + if (!a_hdr->pcr_flag) + return 0; + + p_hdr = (struct pcr_header *)(a_hdr + 1); + + pcr_base = (((u64)ntohl(p_hdr->pcr_clock_reference_base_high)) << 1) | ((u64)p_hdr->pcr_clock_reference_base_low); + pcr_ext = (((u16)p_hdr->program_clock_reference_extension_high) << 8) | ((u16)p_hdr->program_clock_reference_extension_low); + + *pcr = pcr_base * 300 + pcr_ext; + + *pcr = pcr_to_ns(*pcr); + + return 1; +} + +static int ts_parser_update_pcr(struct ts_parser *p, void *buf, unsigned int *len, unsigned long long byte) +{ + unsigned int offset = 0; + int rc = 0; + + /* Parse buffer for PCR */ + while ((p->pcr_count < 2) || (byte > p->count[1])) { + if (*len < PES_SIZE) { + rc = -1; + break; + } + + transport_packet(p, buf + offset); + + offset += PES_SIZE; + *len -= PES_SIZE; + } + + *len = offset; + + return rc; +} + +/* Returns relative time for a given stream byte (in nanoseconds, based on PCR's) */ +static uint64_t ts_parser_get_dt_ns(struct ts_parser *p, unsigned long long byte) +{ + uint64_t dt_ns; + + if (p->pcr_count < 2) { + /* Not enough data */ + dt_ns = 0; + } else if (byte < p->count[0]) { + /* Data before first PCR */ + dt_ns = 0; + } else if (byte < p->count[1]) { + dt_ns = pcr_to_ns(p->pcr[0] - p->pcr0) + ((byte - p->count[0]) * NSECS_PER_SEC) / p->transport_rate; + + if (byte == p->count[0]) + print_pcr(byte, p->pcr[0], dt_ns); + + } else if (byte == p->count[1]) { + dt_ns = pcr_to_ns(p->pcr[1] - p->pcr0); + + print_pcr(byte, p->pcr[1], dt_ns); + } else { +#if TS_PARSER_DEBUG >= 1 + printf("extrapolating pcr\n"); +#endif + + dt_ns = pcr_to_ns(p->pcr[0] - p->pcr0) + ((byte - p->count[0]) * NSECS_PER_SEC) / p->transport_rate; + } + + return dt_ns; +} + +/* Returns system time remaining to the last known PCR (in us, modulo 2^32) */ +static int ts_parser_time_to_last_pcr(struct ts_parser *p) +{ + uint64_t t_us; + uint64_t dt_us; + + gettime_us(&t_us); + dt_us = t_us - p->t0_ns/NSECS_PER_USEC; + + if (p->pcr_count >= 2) + return pcr_to_us(p->pcr[1] - p->pcr0) - dt_us; + else + return 0; +} + +/* Returns absolute time for a given stream byte (in ns, modulo 2^32) */ +static unsigned int ts_parser_get_t_ns(struct ts_parser *p, unsigned long long byte) +{ + uint64_t dt_ns; + uint64_t t_ns; +#if TS_PARSER_DEBUG >= 1 + uint64_t sys_t_ns; + uint64_t sys_dt_ns; +#endif + + dt_ns = ts_parser_get_dt_ns(p, byte); + t_ns = p->t0_ns + dt_ns; + + ts_parser_debug(p, dt_ns, byte); + +#if TS_PARSER_DEBUG >= 1 + if (gettime_ns(&sys_t_ns) >= 0) { + sys_dt_ns = sys_t_ns - p->t0_ns; + + if (t_ns < sys_t_ns) { + printf("late: %10" PRId64 " %10" PRIu64 " %10" PRIu64 " %10" PRId64 "\n", + dt_ns - sys_dt_ns, t_ns, sys_t_ns, t_ns - sys_t_ns); + } + } +#endif + + return (unsigned int)t_ns; +} + +/* Returns absolute system time for a range of stream bytes (in an avb_event array) */ +int ts_parser_timestamp_range(struct file_buffer *b, struct ts_parser *p, struct avb_event *event, unsigned int *event_len, unsigned int data_start, unsigned int data_len, unsigned int presentation_offset) +{ + unsigned int len; + unsigned int event_n = 0; + unsigned int offset = 0; + unsigned int cur = data_start; + int rc; + + while ((offset < data_len) && (event_n < *event_len)) { + len = file_buffer_available_wrap(b, 1); + + ts_parser_update_pcr(p, file_buffer_buf(b, 1), &len, cur); + + file_buffer_read(b, 1, len); + + len = file_buffer_available_wrap(b, 1); + + rc = ts_parser_update_pcr(p, file_buffer_buf(b, 1), &len, cur); + + file_buffer_read(b, 1, len); + + if (rc < 0) { + int timeout; + struct timespec ts; + + /* If some progress was made, use it */ + if (offset) + goto exit; + + if (!file_buffer_free(b, 1)) { + printf("file buffer too small for ts parsing\n"); + offset = -1; + goto exit; + } + + if (p->pcr_count < 2) { +#if TS_PARSER_DEBUG >= 1 + printf("waiting for pcr\n"); +#endif + ts.tv_sec = 0; + ts.tv_nsec = 10000; + + nanosleep(&ts, NULL); + + goto exit; + } + + timeout = ts_parser_time_to_last_pcr(p); + if (timeout > 20000) { +#if TS_PARSER_DEBUG >= 1 + printf("waiting for pcr %d\n", timeout); +#endif + ts.tv_sec = (timeout / 2) / USEC_PER_SEC; + ts.tv_nsec = ((timeout / 2) % USEC_PER_SEC) * NSECS_PER_USEC; + nanosleep(&ts, NULL); + + goto exit; + } +#if TS_PARSER_DEBUG >= 1 + printf("no pcr\n"); +#endif + } + + event[event_n].event_mask = AVTP_SYNC; + event[event_n].ts = ts_parser_get_t_ns(p, cur) + presentation_offset; + event[event_n].index = offset; + +// printf("%u %u %u %u %u %u %u\n", cur, offset, event_n, event[event_n].ts, data_start, data_len, *event_len); + + event_n++; + offset += PES_SIZE; + cur += PES_SIZE; + } + +exit: + *event_len = event_n; + + return offset; +} diff --git a/apps/linux/common/ts_parser.h b/apps/linux/common/ts_parser.h new file mode 100644 index 0000000..daf8316 --- /dev/null +++ b/apps/linux/common/ts_parser.h @@ -0,0 +1,45 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TS_PARSER_H_ +#define _TS_PARSER_H_ + +#include +#include + +#include "file_buffer.h" + +#define PES_SIZE 188 + +#ifdef __cplusplus +extern "C" { +#endif + +struct ts_parser { + uint64_t t0_ns; + unsigned long long pcr0; + + unsigned int pcr_count; + unsigned long long buffer_pts0; + + unsigned int transport_rate; + + unsigned long long byte_count; + + unsigned long long pcr[2]; + unsigned long long count[2]; +}; + +void ts_parser_init(struct ts_parser *p); +int ts_parser_timestamp_range(struct file_buffer *b, struct ts_parser *p, struct avb_event *event, unsigned int *event_len, unsigned int data_start, unsigned int data_len, unsigned int presentation_offset); +int ts_parser_is_pcr(void *buf, unsigned long long *pcr); + +#ifdef __cplusplus +} +#endif + +#endif /* _TS_PARSER_H_ */ diff --git a/apps/linux/genavb-controller-app/CMakeLists.txt b/apps/linux/genavb-controller-app/CMakeLists.txt new file mode 100644 index 0000000..7aea240 --- /dev/null +++ b/apps/linux/genavb-controller-app/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) + +project(genavb-controller-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + ../common/adp.c + ../common/acmp.c + ../common/aecp.c +) + +target_compile_definitions(${PROJECT_NAME} PUBLIC WL_BUILD) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/genavb-controller-app/main.c b/apps/linux/genavb-controller-app/main.c new file mode 100644 index 0000000..251b381 --- /dev/null +++ b/apps/linux/genavb-controller-app/main.c @@ -0,0 +1,602 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief GenAVB simple AVDECC controls handling demo application + @details + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../common/adp.h" +#include "../common/acmp.h" +#include "../common/aecp.h" + +#define MAX_UTF8_STRING_SIZE (AEM_UTF8_MAX_LENGTH + 4) /* Add few characters after the stack limit for unit testing */ + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\nOptions:\n" + "\t-S Set a given control to the given value where control_type \n" + " must be uint8 or utf8 (For utf8: must be string of max %u characters)\n" + "\t-G Get a control value where control_type must be uint8 or utf8 \n" + "\t-l list discovered AVDECC entities\n" + "\t-c connect a stream between a talker and a listener\n" + "\t-d disconnect a stream between a talker and a listener\n" + "\t-r Get information about a listener sink\n" + "\t-t Get information about a talker source\n" + "\t-s Get information from a talker about a given connection/stream\n" + "\t-T Send START_STREAMING or STOP_STREAMING command to a talker\n" + "\t-L Send START_STREAMING or STOP_STREAMING command to a listener\n" + "\t-h print this help text\n", MAX_UTF8_STRING_SIZE - 1); +} + + +#define FORMAT_STR_SIZE 128 +void pretty_print_format(struct avdecc_format *format) +{ + char format_str[FORMAT_STR_SIZE]; + int rc; + + rc = avdecc_fmt_pretty_printf(format, format_str, FORMAT_STR_SIZE); + if ((rc >= FORMAT_STR_SIZE) || rc < 0) + printf("( format decoding error %d )\n", rc); + else + printf("( %s )\n", format_str); +} + +/** Display information about the STREAM_INPUTs or STREAM_OUTPUTs of an entity. + * + * \param ctrl_h AVB control handle to use (must be for a AVB_CTRL_AVDECC_CONTROLLER channel) + * \param entity_id ID of the entity to display information about. + * \param configuration_index Index of the entity configuration to use. + * \param descriptor_type Type of descriptor to use. Must be either AEM_DESC_TYPE_STREAM_OUTPUT or AEM_DESC_TYPE_STREAM_INPUT. + * \param total Number of stream inputs or outputs to display information about. + * + */ +void display_stream_inputs_outputs(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 configuration_index, avb_u16 descriptor_type, avb_u16 total) +{ + struct stream_descriptor stream_desc; + int i, rc; + avb_u8 status; + avb_u16 len = sizeof(struct stream_descriptor); + avb_u64 format; + + for (i = 0; i < total; i++) { + rc = aecp_aem_send_read_descriptor(ctrl_h, entity_id, configuration_index, descriptor_type, i, &stream_desc, &len, &status, 1); + + if ((rc == AVB_SUCCESS) && (status == AECP_AEM_SUCCESS)) { + format = stream_desc.current_format; + printf(" Stream %2d: name = %20s interface index = %u number of formats = %d " + "flags = 0x%x current_format = 0x%016" PRIx64 " ", + i, stream_desc.object_name, ntohs(stream_desc.avb_interface_index), ntohs(stream_desc.number_of_formats), + ntohs(stream_desc.stream_flags), ntohll(format)); + pretty_print_format((struct avdecc_format *)&format); + } + } +} + +/** Display information about the various CONTROLs of an entity. + * + * Note: only LINEAR UNIT8 and UTF8 value types are fully decoded. + * \param ctrl_h AVB control handle to use (must be for a AVB_CTRL_AVDECC_CONTROLLER channel) + * \param entity_id ID of the entity to display information about (in network order). + * \param configuration_index Index of the entity configuration to use. + */ +void display_controls(struct avb_control_handle *ctrl_h, avb_u64 *entity_id, avb_u16 configuration_index) +{ + struct control_descriptor control_desc; + int i, rc = AVB_SUCCESS; + avb_u8 status = AECP_AEM_SUCCESS; + avb_u16 len = sizeof(struct control_descriptor); + avb_u16 value_type, control_value_type; + + printf(" Controls:\n"); + + i = 0; + rc = aecp_aem_send_read_descriptor(ctrl_h, entity_id, configuration_index, AEM_DESC_TYPE_CONTROL, i, &control_desc, &len, &status, 1); + + while ((rc == AVB_SUCCESS) && (status == AECP_AEM_SUCCESS)) { + control_value_type = ntohs(control_desc.control_value_type); + value_type = AEM_CONTROL_GET_VALUE_TYPE(control_value_type); + printf(" Control %2d: name = %20s type = 0x%" PRIx64 " read-only = %3s value_type = %d ", i, control_desc.object_name, ntohll(control_desc.control_type), AEM_CONTROL_GET_R(control_value_type)?"Yes":"No", value_type); + if (value_type == AEM_CONTROL_LINEAR_UINT8) + printf("min = %d current = %d max = %d step = %d\n", control_desc.value_details.linear_int8[0].min, control_desc.value_details.linear_int8[0].current, control_desc.value_details.linear_int8[0].max, control_desc.value_details.linear_int8[0].step); + if (value_type == AEM_CONTROL_UTF8) + printf("current = %s\n", control_desc.value_details.utf8.string); + + i++; + rc = aecp_aem_send_read_descriptor(ctrl_h, entity_id, configuration_index, AEM_DESC_TYPE_CONTROL, i, &control_desc, &len, &status, 1); + } + + if (i == 0) + printf(" None\n"); +} + +/** Display information about a given entity. + * Display the information about an entity retrieved through ADP, and also display details about the STREAM INPUTs, STREAM OUTPUTS and CONTROL available on the entity. + * + * \param ctrl_h AVB control handle to use (must be for a AVB_CTRL_AVDECC_CONTROLLER channel) + * \param entity_info Pointer to an entity info structure received through an ADP message. + */ +void display_entity_info(struct avb_control_handle *ctrl_h, struct entity_info *info) +{ + int total; + + printf("Entity ID = 0x%" PRIx64 " Model ID = 0x%" PRIx64 " Capabilities = 0x%x Association ID = 0x%" PRIx64 "" + " MAC address= %02X:%02X:%02X:%02X:%02X:%02X Local MAC address= %02X:%02X:%02X:%02X:%02X:%02X \n", + ntohll(info->entity_id), ntohll(info->entity_model_id), ntohl(info->entity_capabilities), ntohll(info->association_id), + info->mac_addr[0], info->mac_addr[1], info->mac_addr[2], info->mac_addr[3], info->mac_addr[4], info->mac_addr[5], + info->local_mac_addr[0], info->local_mac_addr[1], info->local_mac_addr[2], info->local_mac_addr[3], info->local_mac_addr[4], info->local_mac_addr[5]); + + if (info->controller_capabilities) + printf(" Controller\n"); + + if (info->talker_stream_sources) { + total = ntohs(info->talker_stream_sources); + printf(" Talker: sources = %d capabilities = 0x%x\n", total, ntohs(info->talker_capabilities)); + display_stream_inputs_outputs(ctrl_h, &info->entity_id, 0, AEM_DESC_TYPE_STREAM_OUTPUT, total); + } + + if (info->listener_stream_sinks) { + total = ntohs(info->listener_stream_sinks); + printf(" Listener: sinks = %d capabilities = 0x%x\n", ntohs(info->listener_stream_sinks), ntohs(info->listener_capabilities)); + display_stream_inputs_outputs(ctrl_h, &info->entity_id, 0, AEM_DESC_TYPE_STREAM_INPUT, total); + } + + display_controls(ctrl_h, &info->entity_id, 0); + + printf("\n\n"); +} + +int main(int argc, char *argv[]) +{ + struct avb_handle *avb_h; + struct avb_control_handle *ctrl_h = NULL; + unsigned long long optval_ull; + unsigned long optval_ul; + int option; + int rc = 0; + unsigned int control_type = AEM_CONTROL_LINEAR_UINT8; + + setlinebuf(stdout); + + printf("NXP's GenAVB AVDECC controller demo application\n"); + + /* + * Get avb handle + */ + rc = avb_init(&avb_h, 0); + if (rc != AVB_SUCCESS) { + printf("avb_init() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + /* + * Open AVB_CTRL_AVDECC_CONTROLLER API control type + */ + rc = avb_control_open(avb_h, &ctrl_h, AVB_CTRL_AVDECC_CONTROLLER); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() failed: %s\n", avb_strerror(rc)); + goto error_control_open; + } + + while ((option = getopt(argc, argv,"S:G:c:d:r:t:s:lhL:T:")) != -1) { + if (optind > 3) { + printf("Cannot parse arguments!\n"); + rc = -1; + usage(); + goto exit; + } + + switch (option) { + case 'S': + { + avb_u64 entity_id; + avb_u16 descriptor_index; + avb_u8 value; + avb_u8 status; + char utf8_val[MAX_UTF8_STRING_SIZE]; + + if (argc != 6) { + printf("Cannot set control value: wrong number of argument(s)!\n"); + rc = -1; + usage(); + goto exit; + } + + if (!strcasecmp(optarg,"uint8")) + control_type = AEM_CONTROL_LINEAR_UINT8; + else if (!strcasecmp(optarg,"utf8")) + control_type = AEM_CONTROL_UTF8; + else { + printf("SET/GET_CONTROL command type should be either uint8 or utf8 \n"); + rc = -1; + goto exit; + } + + if (h_strtoull(&optval_ull, argv[optind], NULL, 0) < 0) { + rc = -1; + goto exit; + } + + entity_id = (avb_u64)optval_ull; + entity_id = htonll(entity_id); + + if (h_strtoul(&optval_ul, argv[optind + 1], NULL, 0) < 0) { + rc = -1; + goto exit; + } + + descriptor_index = (avb_u16)optval_ul; + + if (control_type == AEM_CONTROL_LINEAR_UINT8) { + if (h_strtoul(&optval_ul, argv[optind + 2], NULL, 0) < 0) { + rc = -1; + goto exit; + } + value = (avb_u8)optval_ul; + + rc = aecp_aem_send_set_control_single_u8_command(ctrl_h, &entity_id, descriptor_index, &value, &status, 1); + if ((rc == AVB_SUCCESS) && (status == AECP_AEM_SUCCESS)) { + printf("aecp_aem_send_set_control_single_u8_command successful with the uint8 value (%u) \n", value); + } else if (rc == AVB_SUCCESS) { + printf("aecp_aem_send_set_control_single_u8_command failed with status %u (returned value %u)\n", status, value); + } else { + printf("aecp_aem_send_set_control_single_u8_command send failed rc %d \n", rc); + } + } else { + strncpy(utf8_val, argv[optind + 2], MAX_UTF8_STRING_SIZE - 1); + + rc = aecp_aem_send_set_control_utf8_command(ctrl_h, &entity_id, descriptor_index, utf8_val, &status, 1); + if ((rc == AVB_SUCCESS) && (status == AECP_AEM_SUCCESS)) { + printf("aecp_aem_send_set_control_utf8_command successful with the utf8 string (%s) \n", utf8_val); + } else if (rc == AVB_SUCCESS) { + printf("aecp_aem_send_set_control_single_u8_command failed with status %u (returned value %s)\n", status, utf8_val); + } else { + printf("aecp_aem_send_set_control_single_u8_command send failed rc %d \n", rc); + } + } + + break; + } + case 'G': + { + avb_u64 entity_id; + avb_u16 descriptor_index; + avb_u8 value; + char utf8_val[MAX_UTF8_STRING_SIZE]; + avb_u16 utf8_len = MAX_UTF8_STRING_SIZE; + avb_u16 uint8_len = 1; + avb_u8 status; + + if (argc != 5) { + printf("Cannot get control value: wrong number of argument(s)!\n"); + rc = -1; + usage(); + goto exit; + } + + if (!strcasecmp(optarg,"uint8")) + control_type = AEM_CONTROL_LINEAR_UINT8; + else if (!strcasecmp(optarg,"utf8")) + control_type = AEM_CONTROL_UTF8; + else { + printf("SET/GET_CONTROL command type should be either uint8 or utf8 \n"); + rc = -1; + goto exit; + } + + if (h_strtoull(&optval_ull, argv[optind], NULL, 0) < 0) { + rc = -1; + goto exit; + } + + entity_id = (avb_u64)optval_ull; + entity_id = htonll(entity_id); + + if (h_strtoul(&optval_ul, argv[optind + 1], NULL, 0) < 0) { + rc = -1; + goto exit; + } + + descriptor_index = (avb_u16)optval_ul; + + if (control_type == AEM_CONTROL_LINEAR_UINT8) { + + rc = aecp_aem_send_get_control(ctrl_h, &entity_id, descriptor_index, &value, &uint8_len, &status, 1); + if ((rc == AVB_SUCCESS) && (status == AECP_AEM_SUCCESS)) { + printf("aecp_aem_send_get_control successful with resp_len (%u) and uint8 value (%u) \n", uint8_len, value); + } else if (rc == AVB_SUCCESS) { + printf("aecp_aem_send_get_control failed with status %u \n", status); + } else { + printf("aecp_aem_send_get_control send failed rc %d \n", rc); + } + } else { + + rc = aecp_aem_send_get_control(ctrl_h, &entity_id, descriptor_index, utf8_val, &utf8_len, &status, 1); + if ((rc == AVB_SUCCESS) && (status == AECP_AEM_SUCCESS)) { + printf("aecp_aem_send_get_control successful with resp_len (%u) and utf8 string value (%s) \n", uint8_len, utf8_val); + } else if (rc == AVB_SUCCESS) { + printf("aecp_aem_send_get_control failed with status %u \n", status); + } else { + printf("aecp_aem_send_get_control send failed rc %d \n", rc); + } + } + + break; + } + case 'l': + { + struct entity_info *entities = NULL; + int n_entities, i; + + n_entities = adp_dump_entities(ctrl_h, &entities); + printf("Number of discovered entities: %d\n", n_entities); + for (i = 0; i < n_entities; i++) + display_entity_info(ctrl_h, &entities[i]); + break; + } + case 'c': + case 'd': + { + avb_u64 talker_entity_id, listener_entity_id; + avb_u16 talker_unique_id, listener_unique_id; + avb_u16 flags; + struct avb_acmp_response acmp_rsp; + + if (((option == 'c') && (argc != 7)) || ((option == 'd') && (argc != 6))) { + printf("Cannot connect or disconnect stream: wrong number of argument(s)!\n"); + rc = -1; + usage(); + goto exit; + } + + if (h_strtoull(&optval_ull, optarg, NULL, 0) < 0) { + rc = -1; + goto exit; + } + talker_entity_id = htonll(optval_ull); + + if (h_strtoul(&optval_ul, argv[optind], NULL, 0) < 0) { + rc = -1; + goto exit; + } + talker_unique_id = (avb_u16)optval_ul; + talker_unique_id = htons(talker_unique_id); + + if (h_strtoull(&optval_ull, argv[optind + 1], NULL, 0) < 0) { + rc = -1; + goto exit; + } + listener_entity_id = htonll(optval_ull); + + if (h_strtoul(&optval_ul, argv[optind + 2], NULL, 0) < 0) { + rc = -1; + goto exit; + } + listener_unique_id = (avb_u16)optval_ul; + listener_unique_id = htons(listener_unique_id); + + if (option == 'c') { + if (h_strtoul(&optval_ul, argv[optind + 3], NULL, 0) < 0) { + rc = -1; + goto exit; + } + flags = (avb_u16)optval_ul; + flags = htons(flags); + rc = acmp_connect_stream(ctrl_h, talker_entity_id, talker_unique_id, listener_entity_id, listener_unique_id, flags, &acmp_rsp); + printf("Stream connection"); + } else { + rc = acmp_disconnect_stream(ctrl_h, talker_entity_id, talker_unique_id, listener_entity_id, listener_unique_id, &acmp_rsp); + printf("Stream disconnection"); + } + + if (rc == ACMP_STAT_SUCCESS) { + printf(" successful: stream id = 0x%" PRIx64 " Destination MAC address %02X:%02X:%02X:%02X:%02X:%02X flags = 0x%x connection_count = %d VLAN id = %d\n", + ntohll(acmp_rsp.stream_id), + acmp_rsp.stream_dest_mac[0], acmp_rsp.stream_dest_mac[1], acmp_rsp.stream_dest_mac[2], acmp_rsp.stream_dest_mac[3], acmp_rsp.stream_dest_mac[4], acmp_rsp.stream_dest_mac[5], + ntohs(acmp_rsp.flags), ntohs(acmp_rsp.connection_count), ntohs(acmp_rsp.stream_vlan_id)); + } else + printf(" failed with error %d\n", rc); + + break; + } + case 'r': + case 't': + { + avb_u64 entity_id; + avb_u16 unique_id; + struct avb_acmp_response acmp_rsp; + + if (argc != 4) { + printf("Cannot get state information: wrong number of argument(s)!\n"); + rc = -1; + usage(); + goto exit; + } + + if (h_strtoull(&optval_ull, optarg, NULL, 0) < 0) { + rc = -1; + goto exit; + } + entity_id = htonll(optval_ull); + + if (h_strtoul(&optval_ul, argv[optind], NULL, 0) < 0) { + rc = -1; + goto exit; + } + unique_id = (avb_u16)optval_ul; + unique_id = htons(unique_id); + + if (option == 'r') + rc = acmp_get_rx_state(ctrl_h, entity_id, unique_id, &acmp_rsp); + else + rc = acmp_get_tx_state(ctrl_h, entity_id, unique_id, &acmp_rsp); + + + if (rc == ACMP_STAT_SUCCESS) { + if (option == 'r') + printf("Listener sink information: talker entity id 0x%" PRIx64 " unique id = %d flags = 0x%x", + ntohll(acmp_rsp.talker_entity_id), ntohs(acmp_rsp.talker_unique_id), ntohs(acmp_rsp.flags)); + else + printf("Talker source information"); + + printf(" stream id = 0x%" PRIx64 " Destination MAC address %02X:%02X:%02X:%02X:%02X:%02X connection_count = %d VLAN id = %d\n", + ntohll(acmp_rsp.stream_id), + acmp_rsp.stream_dest_mac[0], acmp_rsp.stream_dest_mac[1], acmp_rsp.stream_dest_mac[2], acmp_rsp.stream_dest_mac[3], acmp_rsp.stream_dest_mac[4], acmp_rsp.stream_dest_mac[5], + ntohs(acmp_rsp.connection_count), ntohs(acmp_rsp.stream_vlan_id)); + } else + printf(" retrieval failed with error %d\n", rc); + + break; + + } + case 'T': + case 'L': + { + avb_u64 entity_id; + avb_u16 unique_id; + avb_u8 status = AECP_AEM_SUCCESS; +#define MAX_CMD_LEN 16 + char command[MAX_CMD_LEN]; + + if (argc != 5) { + printf("Cannot start or stop stream: wrong number of argument(s)!\n"); + rc = -1; + usage(); + goto exit; + } + + if (h_strtoull(&optval_ull, optarg, NULL, 0) < 0) { + rc = -1; + goto exit; + } + entity_id = htonll(optval_ull); + + if (h_strtoul(&optval_ul, argv[optind], NULL, 0) < 0) { + rc = -1; + goto exit; + } + + unique_id = (avb_u16)optval_ul; + + h_strncpy(command, argv[optind + 1], MAX_CMD_LEN); + + if (option == 'T') { + if (!strcmp(command, "start")) + rc = aecp_aem_send_start_streaming(ctrl_h, &entity_id, AEM_DESC_TYPE_STREAM_OUTPUT, unique_id, &status, 1); + else if (!strcmp(command, "stop")) + rc = aecp_aem_send_stop_streaming(ctrl_h, &entity_id, AEM_DESC_TYPE_STREAM_OUTPUT, unique_id, &status, 1); + else { + printf("Wrong AECP command \n"); + rc = -1; + goto exit; + } + } else { + if (!strcmp(command, "start")) + rc = aecp_aem_send_start_streaming(ctrl_h, &entity_id, AEM_DESC_TYPE_STREAM_INPUT, unique_id, &status, 1); + else if (!strcmp(command, "stop")) + rc = aecp_aem_send_stop_streaming(ctrl_h, &entity_id, AEM_DESC_TYPE_STREAM_INPUT, unique_id, &status, 1); + else { + printf("Wrong AECP command \n"); + rc = -1; + goto exit; + } + } + + if (rc == AVB_SUCCESS) { + printf("Streaming command [%s] for %s [entity ID: (0x%" PRIx64 ") - unique ID (%u)] sent successfully : returned with status %d \n", command, (option == 'T') ? "Talker" : "Listener", ntohll(entity_id), unique_id, status); + } else + printf("Streaming command [%s] for %s [entity ID: (0x%" PRIx64 ") - unique ID (%u)] sending failed with error %d\n", command, (option == 'T') ? "Talker" : "Listener", ntohll(entity_id), unique_id, rc); + + break; + } + case 's': + { + avb_u64 talker_entity_id; + avb_u16 talker_unique_id; + avb_u16 connection_count; + struct avb_acmp_response acmp_rsp; + + if (argc != 5) { + printf("Cannot get connection state information: wrong number of argument(s)!\n"); + rc = -1; + usage(); + goto exit; + } + + if (h_strtoull(&optval_ull, optarg, NULL, 0) < 0) { + rc = -1; + goto exit; + } + talker_entity_id = htonll(optval_ull); + + if (h_strtoul(&optval_ul, argv[optind], NULL, 0) < 0) { + rc = -1; + goto exit; + } + talker_unique_id =(avb_u16)optval_ul; + talker_unique_id = htons(talker_unique_id); + + if (h_strtoull(&optval_ull, argv[optind + 1], NULL, 0) < 0) { + rc = -1; + goto exit; + } + connection_count = (avb_u16)optval_ull; + connection_count = htons(connection_count); + + rc = acmp_get_tx_connection(ctrl_h, talker_entity_id, talker_unique_id, connection_count, &acmp_rsp); + + if (rc == ACMP_STAT_SUCCESS) { + printf("Connection information: stream id = 0x%" PRIx64 " listener entity id 0x%" PRIx64 " unique id = %d Destination MAC address %02X:%02X:%02X:%02X:%02X:%02X connection_count = %d VLAN id = %d flags = 0x%x \n", + ntohll(acmp_rsp.stream_id), ntohll(acmp_rsp.listener_entity_id), ntohs(acmp_rsp.listener_unique_id), + acmp_rsp.stream_dest_mac[0], acmp_rsp.stream_dest_mac[1], acmp_rsp.stream_dest_mac[2], acmp_rsp.stream_dest_mac[3], acmp_rsp.stream_dest_mac[4], acmp_rsp.stream_dest_mac[5], + ntohs(acmp_rsp.connection_count), ntohs(acmp_rsp.stream_vlan_id), ntohs(acmp_rsp.flags)); + } else + printf(" retrieval failed with error %d\n", rc); + + break; + + } + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + + +exit: + if (ctrl_h) + avb_control_close(ctrl_h); + +error_control_open: + avb_exit(avb_h); + +error_avb_init: + return rc; + +} diff --git a/apps/linux/genavb-controls-app/CMakeLists.txt b/apps/linux/genavb-controls-app/CMakeLists.txt new file mode 100644 index 0000000..056afd2 --- /dev/null +++ b/apps/linux/genavb-controls-app/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) + +project(genavb-controls-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c +) + +target_compile_definitions(${PROJECT_NAME} PUBLIC WL_BUILD) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) +target_link_libraries(${PROJECT_NAME} asound) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/genavb-controls-app/main.c b/apps/linux/genavb-controls-app/main.c new file mode 100644 index 0000000..ae1434a --- /dev/null +++ b/apps/linux/genavb-controls-app/main.c @@ -0,0 +1,291 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief GenAVB AVDECC controls handling demo application + @details + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\nOptions:\n" + "\t-m specify alsa volume control (default: DAC1)\n " + "\t \"dummy\" Can be used as a fake alsa control to have the controlled socket opened (All avdecc controls will be ignored)\n " + "\t-c specify sound card name (default: default)\n " + "\t-h print this help text\n"); +} + +/* + * The AECP commands map to the entity configuration + * Only one control is implemented for playback + * + * Descriptor type = AEM_DESC_TYPE_CONTROL + * Descriptor index = 0 + * Value type = AEM_CONTROL_LINEAR_UINT8 (One value) + * Control type = AEM_CONTROL_TYPE_GAIN + * Signal type = Audio cluster 0 + * Mapped to the DAC1 channel + * + * The command has been already processed by the + * AVDECC stack and it has been checked that + * the descriptor matches the one described in the + * entity model. + */ + +#define MAX_CONTROL_LEN 128 +char control_mapping[MAX_CONTROL_LEN] = "DAC1"; + +snd_mixer_elem_t* alsa_elem; +snd_mixer_t *alsa_handle; +const char *sound_card_name = "default"; + +int alsa_init(void) +{ + snd_mixer_selem_id_t *sid; + int err = 0; + + /* Initialize Alsa library handles */ + err = snd_mixer_open(&alsa_handle, 0); + if (err < 0) + goto print_error; + + err = snd_mixer_attach(alsa_handle, sound_card_name); + if (err < 0) + goto print_error; + + err = snd_mixer_selem_register(alsa_handle, NULL, NULL); + if (err < 0) + goto print_error; + + err = snd_mixer_load(alsa_handle); + if (err < 0) + goto print_error; + + if (!strcmp(control_mapping, "dummy")) { + printf(" %s Using dummy alsa control ... Skip mixer setup and control\n", __func__); + goto exit; + } + + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, 0); + snd_mixer_selem_id_set_name(sid, control_mapping); + alsa_elem = snd_mixer_find_selem(alsa_handle, sid); + if (!alsa_elem) { + printf("Alsa error: Cannot find mixer control %s \n", control_mapping); + goto error; + } + +exit: + return 0; + +print_error: + printf("Alsa init error: %s\n", snd_strerror(err)); +error: + return -1; +} + +void alsa_close(void) +{ + if(alsa_handle) + snd_mixer_close(alsa_handle); +} + +void set_alsa_master_volume(long volume) +{ + long min, max, val; + + /* Adapt percent to audible dB format, 0-100 => -30dB-0dB */ + min = -30; + max = 0; + val = min + (volume * (max - min)) / 100; + val *= 100; + if (alsa_elem) + snd_mixer_selem_set_playback_dB_all(alsa_elem, val, 0); +} + +int aecp_aem_cmd_rcv(struct aecp_aem_pdu *msg) +{ + avb_u16 cmd_type; + unsigned short status = AECP_AEM_SUCCESS; + + cmd_type = AECP_AEM_GET_CMD_TYPE(msg); + + printf("aecp command type (0x%x) seq_id (%d)\n", cmd_type, ntohs(msg->sequence_id)); + + switch (cmd_type) { + case AECP_AEM_CMD_SET_CONTROL: + { + unsigned char avdecc_value; + struct aecp_aem_set_get_control_pdu *set_control_cmd = (struct aecp_aem_set_get_control_pdu *)(msg + 1); + avb_u16 descriptor_index = ntohs(set_control_cmd->descriptor_index); + + /* Only descriptor index 0 has been mapped here: we only handle volume control in this app */ + if (descriptor_index > 0) { + printf("AECP Command SET_CONTROL with index (0x%x) not handled in this app, skip\n", descriptor_index); + return AECP_AEM_NOT_IMPLEMENTED; + } + + avdecc_value = *(avb_u8 *)(set_control_cmd + 1); + set_alsa_master_volume(avdecc_value); + + break; + } + default: + printf("Command type (0x%x) not handled in this app, skip\n", cmd_type); + status = AECP_AEM_NOT_IMPLEMENTED; + + break; + } + + return status; +} + +static int handle_avdecc_controlled(struct avb_control_handle *ctrl_h, avb_msg_type_t *msg_type) +{ + union avb_controlled_msg msg; + unsigned int msg_len = sizeof(union avb_controlled_msg); + int rc, status; + + rc = avb_control_receive(ctrl_h, msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) { + printf("%s: WARNING: Got error message %d(%s) while trying to receive AECP command.\n", __func__, rc, avb_strerror(rc)); + goto receive_error; + } + + switch (*msg_type) { + case AVB_MSG_AECP: + printf("AVB_MSG_AECP\n"); + + status = aecp_aem_cmd_rcv((struct aecp_aem_pdu *)msg.aecp.buf); + /* Multiple apps can be listening/handling controlled commands, if this app do not handle it, do not answer the stack and let other apps handle that. */ + if (status == AECP_AEM_NOT_IMPLEMENTED) + break; + + msg.aecp.msg_type = AECP_AEM_RESPONSE; + msg.aecp.status = status; + rc = avb_control_send(ctrl_h, *msg_type, &msg, msg_len); + if (rc != AVB_SUCCESS) { + printf("%s: WARNING: Got error message %d(%s) while trying to send response to AECP command.\n", __func__, rc, avb_strerror(rc)); + } + break; + default: + printf("%s: WARNING: Received unsupported AVDECC message type %d.\n", __func__, *msg_type); + break; + } + +receive_error: + return rc; +} + + +int main(int argc, char *argv[]) +{ + struct avb_handle *avb_h; + struct avb_control_handle *ctrl_h = NULL; + int option; + unsigned int event_type; + int ctrl_rx_fd; + struct pollfd ctrl_poll; + int rc = 0; + + setlinebuf(stdout); + + printf("NXP's GenAVB AVDECC control handling demo application\n"); + + while ((option = getopt(argc, argv,"hm:c:")) != -1) { + switch (option) { + case 'm': + h_strncpy(control_mapping, optarg, MAX_CONTROL_LEN); + printf("Using cmd line alsa control name for volume (%s)\n", control_mapping); + break; + + case 'c': + sound_card_name = optarg; + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + + rc = alsa_init(); + if (rc < 0) + goto error_alsa_init; + + /* + * Get avb handle + */ + rc = avb_init(&avb_h, 0); + if (rc != AVB_SUCCESS) { + printf("avb_init() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + /* + * Open AVB_CTRL_AVDECC_CONTROLLED API control channel + */ + rc = avb_control_open(avb_h, &ctrl_h, AVB_CTRL_AVDECC_CONTROLLED); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() failed: %s\n", avb_strerror(rc)); + goto error_control_open; + } + + ctrl_rx_fd = avb_control_rx_fd(ctrl_h); + ctrl_poll.fd = ctrl_rx_fd; + ctrl_poll.events = POLLIN; + ctrl_poll.revents = 0; + + while (1) { + if (poll(&ctrl_poll, 1, -1) == -1) { + printf("poll(%d) failed on waiting for connect\n", ctrl_poll.fd); + rc = -1; + goto error_ctrl_poll; + } + + if (ctrl_poll.revents & POLLIN) { + /* + * read control event from AVDECC + */ + if ((rc = handle_avdecc_controlled(ctrl_h, &event_type)) != AVB_SUCCESS) + printf("handle_avdecc_event error(%d)\n", rc); + } + } + +error_ctrl_poll: + avb_control_close(ctrl_h); + +error_control_open: + avb_exit(avb_h); + +error_avb_init: +error_alsa_init: + alsa_close(); +exit: + return rc; +} diff --git a/apps/linux/genavb-media-app/CMakeLists.txt b/apps/linux/genavb-media-app/CMakeLists.txt new file mode 100644 index 0000000..02e5005 --- /dev/null +++ b/apps/linux/genavb-media-app/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.10) + +project(genavb-media-app) + +option(WAYLAND_BACKEND "Build application with support for wayland backend" ON) + +find_package(PkgConfig) +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0 gstreamer-app-1.0) + +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + thread_config.c + alsa_config.c + avb_stream_config.c + gstreamer_stream.c + multi_frame_sync.c + salsa_camera.c + h264_camera.c + ../common/clock_domain.c + ../common/thread.c + ../common/alsa2.c + ../common/clock.c + ../common/log.c + ../common/stats.c + ../common/msrp.c + ../common/avb_stream.c + ../common/crf_stream.c + ../common/alsa_stream.c + ../common/stream_stats.c + ../common/time.c + ../common/gstreamer.c + ../common/gstreamer_multisink.c + ../common/gstreamer_single.c + ../common/common.c + ../common/ts_parser.c + ../common/file_buffer.c + ../common/avb_stream.c + ../common/gst_pipeline_definitions.c + ../common/aecp.c + ../common/avdecc.c + ../common/gstreamer_custom_rt_pool.c + ../common/timer.c + ../common/audio_mappings.c +) + +if(WAYLAND_BACKEND) + target_compile_definitions(${PROJECT_NAME} PUBLIC WL_BUILD) +endif() + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) +target_link_libraries(${PROJECT_NAME} pthread) +target_link_libraries(${PROJECT_NAME} dl) +target_link_libraries(${PROJECT_NAME} asound) +target_link_libraries(${PROJECT_NAME} ${GSTREAMER_LIBRARIES}) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/genavb-media-app/alsa_config.c b/apps/linux/genavb-media-app/alsa_config.c new file mode 100644 index 0000000..cf2165b --- /dev/null +++ b/apps/linux/genavb-media-app/alsa_config.c @@ -0,0 +1,64 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "../common/alsa_config.h" + +#define ESAI_FIFO_THRESHOLD 64 +#define CFG_ALSA_PCM_START_DELAY 35000 + +#define ALSA_PLAYBACK_DEFAULT_PARAMS \ +{ \ + .period_time_ns = 1000000, /* 1 ms */ \ + .format = SND_PCM_FORMAT_S24_LE, \ + .fifo_threshold = ESAI_FIFO_THRESHOLD, \ + .pcm_start_delay = CFG_ALSA_PCM_START_DELAY, \ +} + +#define ALSA_CAPTURE_DEFAULT_PARAMS \ +{ \ + .period_time_ns = 1000000, /* 1 ms */ \ + .format = SND_PCM_FORMAT_S24_LE, \ + .fifo_threshold = ESAI_FIFO_THRESHOLD, \ + .pcm_start_delay = CFG_ALSA_PCM_START_DELAY, \ +} + +const char alsa_playback_device_names[MAX_ALSA_PLAYBACK][15] = { + [ALSA_PLAYBACK0] = "plughw:0,0", + [ALSA_PLAYBACK1] = "plughw:0,1", + [ALSA_PLAYBACK2] = "plughw:0,2", + [ALSA_PLAYBACK3] = "plughw:0,3", + [ALSA_PLAYBACK4] = "plughw:0,4", + [ALSA_PLAYBACK5] = "plughw:0,5" +}; + +const char alsa_capture_device_names[MAX_ALSA_CAPTURE][15] = { + [ALSA_CAPTURE0] = "plughw:0,0", + [ALSA_CAPTURE1] = "plughw:0,1", + [ALSA_CAPTURE2] = "plughw:0,2", + [ALSA_CAPTURE3] = "plughw:0,3", + [ALSA_CAPTURE4] = "plughw:0,4", + [ALSA_CAPTURE5] = "plughw:0,5", +}; + +aar_alsa_param_t g_alsa_playback_params[MAX_ALSA_PLAYBACK] = { + [ALSA_PLAYBACK0] = ALSA_PLAYBACK_DEFAULT_PARAMS, + [ALSA_PLAYBACK1] = ALSA_PLAYBACK_DEFAULT_PARAMS, + [ALSA_PLAYBACK2] = ALSA_PLAYBACK_DEFAULT_PARAMS, + [ALSA_PLAYBACK3] = ALSA_PLAYBACK_DEFAULT_PARAMS, + [ALSA_PLAYBACK4] = ALSA_PLAYBACK_DEFAULT_PARAMS, + [ALSA_PLAYBACK5] = ALSA_PLAYBACK_DEFAULT_PARAMS, +}; + +aar_alsa_param_t g_alsa_capture_params[MAX_ALSA_CAPTURE] = { + [ALSA_CAPTURE0] = ALSA_CAPTURE_DEFAULT_PARAMS, + [ALSA_CAPTURE1] = ALSA_CAPTURE_DEFAULT_PARAMS, + [ALSA_CAPTURE2] = ALSA_CAPTURE_DEFAULT_PARAMS, + [ALSA_CAPTURE3] = ALSA_CAPTURE_DEFAULT_PARAMS, + [ALSA_CAPTURE4] = ALSA_CAPTURE_DEFAULT_PARAMS, + [ALSA_CAPTURE5] = ALSA_CAPTURE_DEFAULT_PARAMS, +}; diff --git a/apps/linux/genavb-media-app/avb_stream_config.c b/apps/linux/genavb-media-app/avb_stream_config.c new file mode 100644 index 0000000..145757d --- /dev/null +++ b/apps/linux/genavb-media-app/avb_stream_config.c @@ -0,0 +1,312 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "../common/avb_stream_config.h" +#include "../common/crf_stream.h" +#include + + +const unsigned int g_max_avb_talker_streams = 6; +const unsigned int g_max_avb_listener_streams = 6; + +#define DEFAULT_BATCH_SIZE_NS 1000000 + +#define AAF_DEFAULT_FORMAT \ +{ \ + .v = 0, \ + .subtype = AVTP_SUBTYPE_AAF, \ + .subtype_u.aaf = { \ + .nsr = AAF_NSR_48000, \ + .ut = 0, \ + .rsvd = 0, \ + .format = AAF_FORMAT_INT_32BIT, \ + .format_u.pcm = { \ + .bit_depth = 24, \ + AAF_PCM_CHANNELS_PER_FRAME_INIT(2), \ + AAF_PCM_SAMPLES_PER_FRAME_INIT(24), \ + .reserved_msb = 0, \ + .reserved_lsb = 0, \ + } \ + } \ +} + +#define CRF_DEFAULT_FORMAT \ +{ \ + .v = 0, \ + .subtype = AVTP_SUBTYPE_CRF, \ + .subtype_u.crf = { \ + .type = CRF_TYPE_AUDIO_SAMPLE, \ + CRF_TIMESTAMP_INTERVAL_INIT(320), \ + .timestamps_per_pdu = 6, \ + .pull = CRF_PULL_1_1, \ + CRF_BASE_FREQUENCY_INIT(96000), \ + } \ +} + +aar_avb_stream_t g_avb_talker_streams[] = { + // Stream 0 + { + .stream_params = { + .direction = AVTP_DIRECTION_TALKER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + .talker = { + .latency = DEFAULT_BATCH_SIZE_NS, + }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 1 + { + .stream_params = { + .direction = AVTP_DIRECTION_TALKER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + .talker = { + .latency = DEFAULT_BATCH_SIZE_NS, + }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 2 + { + .stream_params = { + .direction = AVTP_DIRECTION_TALKER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + .talker = { + .latency = DEFAULT_BATCH_SIZE_NS, + }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 3 + { + .stream_params = { + .direction = AVTP_DIRECTION_TALKER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + .talker = { + .latency = DEFAULT_BATCH_SIZE_NS, + }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 4 + { + .stream_params = { + .direction = AVTP_DIRECTION_TALKER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + .talker = { + .latency = DEFAULT_BATCH_SIZE_NS, + }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 5 + { + .stream_params = { + .direction = AVTP_DIRECTION_TALKER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + .talker = { + .latency = DEFAULT_BATCH_SIZE_NS, + }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, +}; + +aar_avb_stream_t g_avb_listener_streams[] = { + // Stream 0 + { + .stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 1 + { + .stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 2 + { + .stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 3 + { + .stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 4 + { + .stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, + // Stream 5 + { + .stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_AAF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = AAF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = { 0x00 }, + .dst_mac = { 0x00 }, + }, + + .stream_handle = NULL, + + .batch_size_ns = DEFAULT_BATCH_SIZE_NS, + }, +}; + +aar_crf_stream_t g_crf_streams[] = { + { + .stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_CRF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = AVB_STREAM_FLAGS_MCR, + .format.u.s = CRF_DEFAULT_FORMAT, + .port = AAR_AVB_ETH_PORT, + .stream_id = {0x00, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + .dst_mac = {0x91, 0xe0, 0xf0, 0x00, 0xfe, 0xff}, + .talker = { + .latency = 0, + }, + }, + + .stream_handle = NULL, + .batch_size_ns = 0, + + .is_static_config = false, + }, +}; diff --git a/apps/linux/genavb-media-app/gstreamer_stream.c b/apps/linux/genavb-media-app/gstreamer_stream.c new file mode 100644 index 0000000..80c47b1 --- /dev/null +++ b/apps/linux/genavb-media-app/gstreamer_stream.c @@ -0,0 +1,851 @@ +/* + * Copyright 2017-2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include + +#include + +#include +#include + +#include "gstreamer_stream.h" +#include "../common/avb_stream.h" +#include "../common/thread.h" +#include "../common/gstreamer_single.h" +#include "../common/gst_pipeline_definitions.h" +#include "../common/gstreamer_custom_rt_pool.h" +#include "../common/clock_domain.h" +#include "../common/timer.h" + +#define NSEC_PER_SEC 1000000000 + +#define MAX_CONNECTED_GST_TALKERS 1 +#define MAX_CONNECTED_GST_LISTENERS 1 + +#define DEFAULT_GST_ALSA_DEVICE "plughw:0,0" + +static int connected_gst_talkers = 0; +static int connected_gst_listeners = 0; + +/* Single Stream handler structs*/ +static struct talker_gst_media gstreamer_talker_media[MAX_GSTREAMER_TALKERS] = { [0 ... MAX_GSTREAMER_TALKERS - 1 ].stream_lock = PTHREAD_MUTEX_INITIALIZER }; +static struct talker_gst_multi_app gstreamer_talker_multi_app[MAX_GSTREAMER_TALKERS] = { [0 ... MAX_GSTREAMER_TALKERS - 1].samples_lock = PTHREAD_MUTEX_INITIALIZER}; + +void gstreamer_pipeline_init(struct gstreamer_pipeline *pipeline) +{ + GstTaskPool *pool; + memset(pipeline, 0, sizeof(struct gstreamer_pipeline)); + + /*By default, the skew sync slave method is enabled */ + pipeline->config.sink_slave_method = GST_AUDIO_BASE_SINK_SLAVE_SKEW; + + /*By default we sync to clock on render*/ + pipeline->config.sync_render_to_clock = 1; + pipeline->config.listener.pts_offset = GST_CLOCK_TIME_NONE; + + pipeline->config.alsa_device = DEFAULT_GST_ALSA_DEVICE; + + /* create a custom thread pool */ + pool = avb_rt_pool_new (); + printf("[GSTREAMER] Creating Custom Task pool %p \n",pool); + pipeline->pool = pool; +} + +void dump_gst_config(const struct gstreamer_pipeline *pipeline) +{ + printf("\nPLAYOUT TYPE: \n"); + if (pipeline->config.type & GST_TYPE_LISTENER) + printf("LISTENER: \n"); + if (pipeline->config.type & GST_TYPE_TALKER) + printf("TALKER: \n"); + if (pipeline->config.type & GST_TYPE_VIDEO) + printf("\t -> Video \n"); + if (pipeline->config.type & GST_TYPE_AUDIO) + printf("\t -> Audio \n"); + if (pipeline->config.listener.flags & GST_FLAG_DEBUG) + printf("\t -> Debug Mode : dump to file \n"); + if (pipeline->config.listener.flags & GST_FLAG_CAMERA) + printf("\t -> Camera \n"); + if (pipeline->config.listener.flags & GST_FLAG_BEV) + printf("\t -> BEV Mode \n"); + if (pipeline->config.talker.flags & GST_FLAG_PREVIEW) + printf("\t -> Preview mode : render on server \n"); + if (((pipeline->config.type & GST_TYPE_LISTENER) && (pipeline->config.type & GST_TYPE_VIDEO)) + || ((pipeline->config.type & GST_TYPE_TALKER) && (pipeline->config.talker.flags & GST_FLAG_PREVIEW))) { + printf("\nDISPLAY DEVICE: %s", pipeline->config.device); + printf("\nDISPLAY SCALING: %dx%d\n", pipeline->config.width, pipeline->config.height); + printf("\t -> Presentation offset %llu ns\n", (pipeline->config.type & GST_TYPE_TALKER) ? pipeline->config.talker.preview_ts_offset : pipeline->config.listener.pts_offset); + } + + if ((pipeline->config.type & GST_TYPE_LISTENER) && (pipeline->config.type & GST_TYPE_AUDIO)) { + printf("\nALSA DEVICE: %s", pipeline->config.alsa_device); + } + +} + +int create_stream_timer(unsigned int interval_ms) +{ + int timer_fd; + + timer_fd = create_timerfd_periodic(CLOCK_REALTIME); + if (timer_fd < 0) + goto err; + + if (start_timerfd_periodic(timer_fd, 0, interval_ms * NSECS_PER_MSEC) < 0) + goto err_close; + + return timer_fd; + +err_close: + close(timer_fd); + +err: + return -1; +} + +static void apply_config_post(struct gstreamer_stream *stream) +{ + const struct avdecc_format *format = &stream->params.format; + + if ((format->u.s.subtype == AVTP_SUBTYPE_61883_IIDC) + && (format->u.s.subtype_u.iec61883.fmt == IEC_61883_CIP_FMT_6)) + stream->frame_size = stream->batch_size; +} + +int select_prepare_gst_talker_pipeline(struct gstreamer_pipeline *gst_pipeline, const struct avdecc_format *format) +{ + const struct gstreamer_pipeline_config *config = &gst_pipeline->config; + + /* select adequate gstreamer pipeline */ + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + if (format->u.s.subtype_u.iec61883.sf == 0) { + printf("Unsupported 61883_IIDC format\n"); + return -1; + } else + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_4: + if (config->type & GST_TYPE_MULTI_TALKER) { + if ((config->talker.flags & GST_FLAG_PREVIEW)) { + gst_pipeline->definition = &pipeline_talker_file_61883_4_61883_6_h264_preview; + printf("pipeline_talker_file_61883_4_61883_6_h264_preview selected\n"); + } else { + gst_pipeline->definition = &pipeline_talker_file_61883_4_61883_6_h264; + printf("pipeline_talker_file_61883_4_61883_6_h264 selected\n"); + } + } else { + if (config->type & GST_TYPE_VIDEO) { + if ((config->talker.flags & GST_FLAG_PREVIEW)) { + gst_pipeline->definition = &pipeline_talker_file_61883_4_preview; + printf("pipeline_talker_file_61883_4_preview selected\n"); + } else { + gst_pipeline->definition = &pipeline_talker_file_61883_4; + printf("pipeline_talker_file_61883_4 selected\n"); + } + } else { /*Send audio only over 61883_4*/ + if (strstr(config->talker.input_media_files[config->talker.input_media_file_index], ".m4a") || + strstr(config->talker.input_media_files[config->talker.input_media_file_index], ".mp4") ) { + gst_pipeline->definition = &pipeline_talker_file_61883_4_audio_m4a; + printf("pipeline_talker_file_61883_4_audio_m4a selected\n"); + } else if (strstr(config->talker.input_media_files[config->talker.input_media_file_index], ".mp3")) { + gst_pipeline->definition = &pipeline_talker_file_61883_4_audio_mp3; + printf("pipeline_talker_file_61883_4_audio_mp3 selected\n"); + } else if (strstr(config->talker.input_media_files[config->talker.input_media_file_index], ".wav")) + printf("No gst pipeline selected, wav format not supported over 61883_4\n"); + else + printf("[Talker] No gst pipeline selected, input file format not supported for audio only IEC_61883_CIP_FMT_4\n"); + } + } + break; + case IEC_61883_CIP_FMT_6: + if (config->type & GST_TYPE_MULTI_TALKER) { + if ((config->talker.flags & GST_FLAG_PREVIEW)) { + gst_pipeline->definition = &pipeline_talker_file_61883_4_61883_6_h264_preview; + printf("pipeline_talker_file_61883_4_61883_6_h264_preview selected\n"); + } else { + gst_pipeline->definition = &pipeline_talker_file_61883_4_61883_6_h264; + printf("pipeline_talker_file_61883_4_61883_6_h264 selected\n"); + } + } else { + if (strstr(config->talker.input_media_files[config->talker.input_media_file_index], ".m4a") || + strstr(config->talker.input_media_files[config->talker.input_media_file_index], ".mp4") ) { + gst_pipeline->definition = &pipeline_talker_file_61883_6_audio_m4a; + printf("pipeline_talker_file_61883_6_audio_m4a selected\n"); + } else if (strstr(config->talker.input_media_files[config->talker.input_media_file_index], ".mp3")) { + gst_pipeline->definition = &pipeline_talker_file_61883_6_audio_mp3; + printf("pipeline_talker_file_61883_6_audio_mp3 selected\n"); + } else if (strstr(config->talker.input_media_files[config->talker.input_media_file_index], ".wav")) { + gst_pipeline->definition = &pipeline_talker_file_61883_6_audio_wav; + printf("pipeline_talker_file_61883_6_audio_wav selected\n"); + } else + printf("[Talker] No gst pipeline selected, input file format not supported for IEC_61883_CIP_FMT_6\n"); + } + break; + case IEC_61883_CIP_FMT_8: + default: + printf("[Talker] Unsupported IEC-61883 format: %d\n", format->u.s.subtype); + return -1; + } + + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + if (format->u.s.subtype_u.cvf.format == CVF_FORMAT_RFC) { + switch (format->u.s.subtype_u.cvf.subtype) { + case CVF_FORMAT_SUBTYPE_H264: + if (config->type & GST_TYPE_MULTI_TALKER) { + if ((config->talker.flags & GST_FLAG_PREVIEW)) { + gst_pipeline->definition = &pipeline_talker_file_61883_4_61883_6_h264_preview; + printf("pipeline_talker_file_61883_4_61883_6_h264_preview selected\n"); + } else { + gst_pipeline->definition = &pipeline_talker_file_61883_4_61883_6_h264; + printf("pipeline_talker_file_61883_4_61883_6_h264 selected\n"); + } + } else { + if ((config->talker.flags & GST_FLAG_PREVIEW)) { + gst_pipeline->definition = &pipeline_talker_file_cvf_h264_preview; + printf("pipeline_talker_file_cvf_h264_preview selected : %s \n", gst_pipeline->definition->pipeline_string); + } else { + gst_pipeline->definition = &pipeline_talker_file_cvf_h264; + printf("pipeline_talker_file_cvf_h264 selected %s \n", pipeline_talker_file_cvf_h264.pipeline_string); + } + } + break; + + case CVF_FORMAT_SUBTYPE_JPEG2000: + case CVF_FORMAT_SUBTYPE_MJPEG: + default: + printf("[Talker] Unsupported CVF subtype: %d\n", format->u.s.subtype_u.cvf.subtype); + return -1; + } + } else { + printf("[Talker] Unsupported CVF format: %d\n", format->u.s.subtype_u.cvf.format); + return -1; + } + + break; +#endif + + default: + printf("[Talker] Unsupported AVTP subtype: %d\n", format->u.s.subtype); + return -1; + } + return 0; +} + +int select_gst_listener_pipeline(struct gstreamer_pipeline *gst_pipeline, const struct avdecc_format *format) +{ + const struct gstreamer_pipeline_config *config = &gst_pipeline->config; + + /* select adequate gstreamer pipeline */ + + if ((config->type & GST_TYPE_LISTENER) && (config->listener.flags & GST_FLAG_DEBUG)) { + gst_pipeline->definition = &pipeline_listener_debug; + printf("pipeline_listener_debug selected, file dump at %s \n",config->listener.debug_file_dump_location); + + return 0; + } + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + if (format->u.s.subtype_u.iec61883.sf == 0) { + printf("Unsupported 61883_IIDC format\n"); + return -1; + } else + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_6: + gst_pipeline->definition = &pipeline_listener_61883_6; + printf("pipeline_listener_61883_6 selected\n"); + break; + + case IEC_61883_CIP_FMT_4: + if ((config->type & GST_TYPE_LISTENER) && (config->type & GST_TYPE_VIDEO) && !(config->type & GST_TYPE_AUDIO)) { + gst_pipeline->definition = &pipeline_listener_61883_4_video_only; + printf("pipeline_listener_61883_4_video_only selected\n"); + } + else if ((config->type & GST_TYPE_LISTENER) && (config->type & GST_TYPE_AUDIO) && !(config->type & GST_TYPE_VIDEO) ) { + gst_pipeline->definition = &pipeline_listener_61883_4_audio_only; + printf("pipeline_61883_4_audio_only selected\n"); + } + else { + gst_pipeline->definition = &pipeline_listener_61883_4_audio_video; + printf("pipeline_listener_61883_4_audio_video selected\n"); + } + + break; + + case IEC_61883_CIP_FMT_8: + default: + printf("[Listener] Unsupported IEC-61883 format: %d\n", format->u.s.subtype); + return -1; + } + + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + if (format->u.s.subtype_u.cvf.format == CVF_FORMAT_RFC) { + switch (format->u.s.subtype_u.cvf.subtype) { + case CVF_FORMAT_SUBTYPE_MJPEG: + if (config->nstreams == 1) { + gst_pipeline->definition = &pipeline_listener_cvf_mjpeg; + printf("pipeline_listener_cvf_mjpeg selected\n"); + } else { + gst_pipeline->definition = &pipeline_cvf_mjpeg_decode_only; + printf("pipeline_listener_cvf_mjpeg_decode_only selected\n"); + } + break; + + case CVF_FORMAT_SUBTYPE_H264: + gst_pipeline->definition = &pipeline_listener_cvf_h264; + printf("pipeline_listener_cvf_h264 selected %s \n", pipeline_listener_cvf_h264.pipeline_string); + break; + + case CVF_FORMAT_SUBTYPE_JPEG2000: + default: + printf("[Listener] Unsupported CVF subtype: %d\n", format->u.s.subtype_u.cvf.subtype); + return -1; + } + } else { + printf("[Listener] Unsupported CVF format: %d\n", format->u.s.subtype_u.cvf.format); + return -1; + } + + break; +#endif + + default: + printf("[Listener] Unsupported AVTP subtype: %d\n", format->u.s.subtype); + return -1; + } + + return 0; +} + +void gst_pipeline_configure_pts_offset(struct gstreamer_pipeline *gst_pipeline, const struct avdecc_format *format) +{ + const struct gstreamer_pipeline_config *config = &gst_pipeline->config; + + gst_pipeline->listener.local_pts_offset = 0; + gst_pipeline->listener.pts_offset = config->listener.pts_offset; + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + gst_pipeline->listener.local_pts_offset = LOCAL_PTS_OFFSET; + + if (gst_pipeline->listener.pts_offset == GST_CLOCK_TIME_NONE) + gst_pipeline->listener.pts_offset = DEFAULT_PTS_OFFSET; + + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + if (format->u.s.subtype_u.cvf.format == CVF_FORMAT_RFC) { + switch (format->u.s.subtype_u.cvf.subtype) { + case CVF_FORMAT_SUBTYPE_MJPEG: + gst_pipeline->listener.local_pts_offset = SALSA_LATENCY; + if (gst_pipeline->listener.pts_offset == GST_CLOCK_TIME_NONE) + gst_pipeline->listener.pts_offset = CVF_MJPEG_PTS_OFFSET; + break; + + case CVF_FORMAT_SUBTYPE_H264: + gst_pipeline->listener.local_pts_offset = LOCAL_PTS_OFFSET; + if (gst_pipeline->listener.pts_offset == GST_CLOCK_TIME_NONE) + gst_pipeline->listener.pts_offset = DEFAULT_PTS_OFFSET; + break; + + default: + break; + } + } + + break; +#endif + + default: + break; + } + + if (gst_pipeline->listener.pts_offset == GST_CLOCK_TIME_NONE) + gst_pipeline->listener.pts_offset = 0; + + + if (gst_pipeline->listener.pts_offset < (gst_pipeline->definition->latency + gst_pipeline->listener.local_pts_offset)) { + gst_pipeline->listener.pts_offset = gst_pipeline->definition->latency + gst_pipeline->listener.local_pts_offset; + printf("Warning: PTS offset too small, resetting to %lld ns.\n", gst_pipeline->definition->latency + gst_pipeline->listener.local_pts_offset); + } + + gst_pipeline->listener.pts_offset -= gst_pipeline->listener.local_pts_offset; +} + +static int timeout_handler(struct gstreamer_stream *_stream, unsigned int events) +{ + int rc; + + char tmp[8]; + int ret; + + ret = read(_stream->timer_fd, tmp, 8); + if (ret < 0) + printf("%s: [stream %p] timer_fd read() failed: %s\n", __func__, _stream, strerror(errno)); + + + struct talker_gst_multi_app *stream = _stream->data; + rc = talker_gst_multi_stream_fsm(stream, STREAM_EVENT_TIMER); + if (rc < 0) + goto err; + + rc = talker_gst_multi_fsm(stream->gst, GST_EVENT_TIMER); + if (rc < 0) + goto err; + + return 0; + +err: + return rc; +} + +static int listener_stream_fd_timeout_handler(struct gstreamer_stream *_stream, unsigned int events) +{ + /*Check if we started reading data before*/ + if(_stream->data_received) { + + listener_stream_flush(_stream->stream_h); + + gst_stop_pipeline(_stream->pipe_source.gst_pipeline); + stream_init_stats(_stream); + + _stream->data_received = 0; + + if (gst_start_pipeline(_stream->pipe_source.gst_pipeline, GST_PRIORITY, GST_DIRECTION_LISTENER) < 0) { + printf("gst_start_pipeline() failed inside timeout handler\n"); + goto error_gst_pipeline; + } + _stream->pipe_source.source = _stream->pipe_source.gst_pipeline->source[0].source; + } + return 0; +error_gst_pipeline: + return -1; + +} + +static void media_app_stream_poll_set (void *data, int enable) +{ + struct gstreamer_stream *talker = (struct gstreamer_stream *) data; + + if(talker->thread) + thread_slot_set_events(talker->thread, enable, EPOLLOUT); +} + +static int talker_gst_multi_stream_handler(void *data, unsigned int events) +{ + struct gstreamer_stream *_stream = (struct gstreamer_stream *) data; + struct talker_gst_multi_app *stream = _stream->data; + int rc = 0; + + pthread_mutex_lock(&stream->gst->stream_lock); + rc = talker_gst_multi_stream_fsm(stream, STREAM_EVENT_DATA); + pthread_mutex_unlock(&stream->gst->stream_lock); + + return rc; +} + +int talker_gstreamer_connect(struct gstreamer_stream *talker, struct avb_stream_params *params, unsigned int avdecc_stream_index) +{ + struct avb_handle *avb_h = avbstream_get_avb_handle(); + int rc; + struct talker_gst_multi_app *_stream = NULL; + + if (talker->created) + goto error_stream_create; + + if (connected_gst_talkers >= MAX_CONNECTED_GST_TALKERS) { + printf("%s : Error stream (%d) Maximum supported gstreamer pipeline talkers exceeded (%d) \n", __func__, avdecc_stream_index, MAX_CONNECTED_GST_TALKERS); + goto error_stream_create; + } + + apply_stream_params(talker, params); + + print_stream_id(talker->params.stream_id); + + rc = avb_stream_create(avb_h, &talker->stream_h, &talker->params, &talker->batch_size, talker->flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", talker->batch_size); + apply_config_post(talker); + + /* + * retrieve the file descriptor associated to the stream + */ + talker->stream_fd = avb_stream_fd(talker->stream_h); + if (talker->stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(talker->stream_fd)); + rc = -1; + goto error_stream_fd; + } + + /*Delay to let SRP complete its init phase*/ + sleep(5); + + select_prepare_gst_talker_pipeline(talker->pipe_source.gst_pipeline, &talker->params.format); + + talker->pipe_source.gst_pipeline->talker.gst = &gstreamer_talker_media[talker->source_index]; + + talker->pipe_source.gst_pipeline->talker.gst->gst_pipeline = talker->pipe_source.gst_pipeline; + talker->pipe_source.gst_pipeline->talker.gst->state = GST_STATE_STOPPED; + + talker->data = &gstreamer_talker_multi_app[talker->source_index]; + + /* Init the gst_multi_app struct*/ + _stream = (struct talker_gst_multi_app *) talker->data; + + _stream->gst = talker->pipe_source.gst_pipeline->talker.gst; + _stream->stream_h = talker->stream_h; + _stream->batch_size = talker->batch_size; + _stream->state = STREAM_STATE_DISCONNECTED; + _stream->sink_index = 0; + _stream->index = talker->source_index; + _stream->stream_poll_set = media_app_stream_poll_set; + _stream->stream_poll_data = talker; + memcpy(&_stream->params, &talker->params, sizeof(struct avb_stream_params)); + + /*NOTE 1 AVB Stream <=> one pipeline */ + talker->pipe_source.gst_pipeline->talker.stream[0] = talker->data; + talker->pipe_source.gst_pipeline->sink[0].data = talker->data; + talker->pipe_source.gst_pipeline->talker.nb_streams++; + + dump_gst_config(talker->pipe_source.gst_pipeline); + + /* + * setup and kick-off gstreamer pipeline + * For now, assume 1 AVB stream <=> 1 pipeline + */ + + + talker->timer_fd = create_stream_timer(POLL_TIMEOUT_MS); + if (talker->timer_fd < 0) { + printf("%s timerfd_create() failed %s\n", __func__, strerror(errno)); + goto error_stream_timer_create; + } + + printf("%s Connect the Gstreamer pipeline \n",__func__); + + rc = talker_gst_multi_stream_fsm(_stream, STREAM_EVENT_CONNECT); + if (rc < 0 ) { + printf("talker_gst_multi_stream_fsm CONNECT failed\n"); + goto error_stream_connect; + } + + printf("\n %s Setting the timeout handler \n", __func__); + + if (thread_slot_add(THR_CAP_TIMER, talker->timer_fd, EPOLLIN, talker, (int (*)(void *, unsigned int events))timeout_handler, NULL, 0, (thr_thread_slot_t **)&talker->thread_timer) < 0) + goto error_thread_timer; + + talker->pipe_source.sink = talker->pipe_source.gst_pipeline->sink[0].sink; + + printf("\n %s : Add the talker data slot \n",__func__); + + if (thread_slot_add(THR_CAP_STREAM_TALKER | THR_CAP_CONTROLLED | THR_CAP_TIMER, talker->stream_fd, EPOLLOUT, talker, talker_gst_multi_stream_handler, NULL, 0, (thr_thread_slot_t **)&talker->thread) < 0) + goto error_thread_talker; + + talker->created = 1; + connected_gst_talkers++; + + return 0; + +error_thread_talker: + thread_slot_free(talker->thread_timer); + +error_thread_timer: + talker_gst_multi_stream_fsm(_stream, STREAM_EVENT_DISCONNECT); + +error_stream_connect: +error_stream_timer_create: +error_stream_fd: + avb_stream_destroy(talker->stream_h); + +error_stream_create: + return -1; +} + +int talker_gstreamer_multi_connect(struct avb_stream_params *params, struct gstreamer_talker_multi_handler *talker_multi, unsigned int sink_index, unsigned int stream_index) +{ + struct avb_handle *avb_h = avbstream_get_avb_handle(); + int rc; + struct talker_gst_multi_app *_stream = NULL; + struct gstreamer_stream *talker = talker_multi->talkers[sink_index]; + + if (talker->created) + goto error_stream_create; + + if ((!talker_multi->connected_streams) && (connected_gst_talkers >= MAX_CONNECTED_GST_TALKERS)) { + printf("%s : Error stream (%d) Maximum supported gstreamer talkers exceeded (%d) \n", __func__, stream_index, MAX_CONNECTED_GST_TALKERS); + goto error_stream_create; + } + + apply_stream_params(talker, params); + + print_stream_id(talker->params.stream_id); + + rc = avb_stream_create(avb_h, &talker->stream_h, &talker->params, &talker->batch_size, talker->flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", talker->batch_size); + + apply_config_post(talker); + + /* + * retrieve the file descriptor associated to the stream + */ + talker->stream_fd = avb_stream_fd(talker->stream_h); + if (talker->stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(talker->stream_fd)); + rc = -1; + goto error_stream_fd; + } + + /*Delay to let SRP complete its init phase*/ + sleep(5); + + /* Select pipeline once*/ + if (!talker_multi->connected_streams) + select_prepare_gst_talker_pipeline(&talker_multi->gst_pipeline, &talker->params.format); + + pthread_mutex_lock(&talker_multi->gst_media->stream_lock); + + talker->pipe_source.gst_pipeline = &talker_multi->gst_pipeline; + + talker->data = talker_multi->multi_app[sink_index]; + talker->source_index = stream_index; + + /* Init the gst_multi_app struct*/ + _stream = (struct talker_gst_multi_app *) talker->data; + + _stream->gst = talker->pipe_source.gst_pipeline->talker.gst; + _stream->stream_h = talker->stream_h; + _stream->batch_size = talker->batch_size; + _stream->state = STREAM_STATE_DISCONNECTED; + _stream->sink_index = sink_index; + _stream->index = talker->source_index; + _stream->stream_poll_set = media_app_stream_poll_set; + _stream->stream_poll_data = talker; + + memcpy(&_stream->params, &talker->params, sizeof(struct avb_stream_params)); + + talker->pipe_source.gst_pipeline->talker.stream[sink_index] = talker->data; + talker->pipe_source.gst_pipeline->talker.nb_streams++; + + dump_gst_config(talker->pipe_source.gst_pipeline); + + printf("%s Connect the Gstreamer pipeline \n",__func__); + + rc = talker_gst_multi_stream_fsm(_stream, STREAM_EVENT_CONNECT); + if (rc < 0 ) { + printf("talker_gst_multi_stream_fsm CONNECT failed\n"); + goto error_stream_connect; + } + + pthread_mutex_unlock(&talker_multi->gst_media->stream_lock); + + talker->pipe_source.sink = talker->pipe_source.gst_pipeline->sink[sink_index].sink; + + printf("\n %s : Add the talker data slot \n",__func__); + + if (thread_slot_add(THR_CAP_GST_MULTI | THR_CAP_TIMER, talker->stream_fd, EPOLLOUT, talker, talker_gst_multi_stream_handler, NULL, 0, (thr_thread_slot_t **)&talker->thread) < 0) + goto error_thread_talker; + + talker->created = 1; + + if (!talker_multi->connected_streams) + connected_gst_talkers++; + + talker_multi->connected_streams++; + + return 0; + +error_thread_talker: + pthread_mutex_lock(&talker_multi->gst_media->stream_lock); + talker_gst_multi_stream_fsm(_stream, STREAM_EVENT_DISCONNECT); + pthread_mutex_unlock(&talker_multi->gst_media->stream_lock); + +error_stream_connect: +error_stream_fd: + avb_stream_destroy(talker->stream_h); + +error_stream_create: + return -1; +} + +int listener_gstreamer_connect(struct gstreamer_stream *listener, struct avb_stream_params *params, unsigned int avdecc_stream_index) +{ + struct avb_handle *avb_h = avbstream_get_avb_handle(); + int rc; + unsigned int slot_timeout = 0; + + if (listener->created) + goto error_stream_create; + + if (connected_gst_listeners >= MAX_CONNECTED_GST_LISTENERS) { + printf("%s : Error stream (%d) Maximum supported gstreamer listeners exceeded (%d) \n", __func__, avdecc_stream_index, MAX_CONNECTED_GST_LISTENERS); + goto error_stream_create; + } + + apply_stream_params(listener, params); + + rc = avb_stream_create(avb_h, &listener->stream_h, &listener->params, &listener->batch_size, listener->flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", listener->batch_size); + apply_config_post(listener); + + /* + * retrieve the file descriptor associated to the stream + */ + listener->stream_fd = avb_stream_fd(listener->stream_h); + if (listener->stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(listener->stream_fd)); + rc = -1; + goto error_stream_fd; + } + + select_gst_listener_pipeline(listener->pipe_source.gst_pipeline, &listener->params.format); + gst_pipeline_configure_pts_offset(listener->pipe_source.gst_pipeline, &listener->params.format); + dump_gst_config(listener->pipe_source.gst_pipeline); + + if (get_audio_clk_sync(listener->params.clock_domain)) + listener->pipe_source.gst_pipeline->config.sink_slave_method = GST_AUDIO_BASE_SINK_SLAVE_NONE; + + stream_init_stats(listener); + /* + * setup and kick-off gstreamer pipeline + * For now, assume 1 AVB stream <=> 1 pipeline + */ + if (gst_start_pipeline(listener->pipe_source.gst_pipeline, GST_PRIORITY, GST_DIRECTION_LISTENER) < 0) { + printf("gst_start_pipeline() failed\n"); + rc = -1; + goto error_gst_pipeline; + } + + listener->pipe_source.source = listener->pipe_source.gst_pipeline->source[0].source; + listener->data_received = 0; + slot_timeout = 1 * NSEC_PER_SEC; + + printf("\n %s: Adding listener data slot with timeout %u ns\n", __func__, slot_timeout); + + if (thread_slot_add(THR_CAP_STREAM_LISTENER, listener->stream_fd, EPOLLIN, listener, (int (*)(void *, unsigned int))listener->listener_gst_handler, + (int (*)(void *))listener_stream_fd_timeout_handler, slot_timeout, (thr_thread_slot_t **)&listener->thread) < 0) + goto error_thread; + + listener->created = 1; + connected_gst_listeners++; + + return 0; + + +error_thread: + gst_stop_pipeline(listener->pipe_source.gst_pipeline); +error_gst_pipeline: + +error_stream_fd: + avb_stream_destroy(listener->stream_h); + +error_stream_create: + return -1; +} + +void listener_gstreamer_disconnect(struct gstreamer_stream *listener) +{ + if (listener->created) { + thread_slot_free(listener->thread); + + gst_stop_pipeline(listener->pipe_source.gst_pipeline); + + avb_stream_destroy(listener->stream_h); + + listener->created = 0; + + connected_gst_listeners--; + } +} + +void talker_gstreamer_disconnect(struct gstreamer_stream *talker) +{ + if(talker->created) { + thread_slot_free(talker->thread); + + thread_slot_free(talker->thread_timer); + + close(talker->timer_fd); + + talker_gst_multi_stream_fsm(talker->data, STREAM_EVENT_DISCONNECT); + + avb_stream_destroy(talker->stream_h); + + talker->created = 0; + + connected_gst_talkers--; + } + +} + +void talker_gstreamer_multi_disconnect(struct gstreamer_talker_multi_handler *talker_multi, unsigned int sink_index) +{ + struct gstreamer_stream *talker = talker_multi->talkers[sink_index]; + struct talker_gst_multi_app *stream; + + if(talker->created) { + thread_slot_free(talker->thread); + + stream = (struct talker_gst_multi_app *) talker->data; + pthread_mutex_lock(&stream->gst->stream_lock); + talker_gst_multi_stream_fsm(stream, STREAM_EVENT_DISCONNECT); + pthread_mutex_unlock(&stream->gst->stream_lock); + + avb_stream_destroy(talker->stream_h); + + talker->created = 0; + + talker_multi->connected_streams--; + + if (!talker_multi->connected_streams) + connected_gst_talkers--; + + } + +} + +int gstreamer_split_screen_start(struct gstreamer_pipeline *pipeline) +{ + /* Setup the Gstreamer pipeline for the split screen display */ + pipeline->definition = &pipeline_split_screen; + pipeline->listener.pts_offset = OVERLAY_PIPELINE_LATENCY; + pipeline->listener.local_pts_offset = 0; + + dump_gst_config(pipeline); + + if (gst_start_pipeline(pipeline, GST_PRIORITY, GST_DIRECTION_LISTENER) < 0) { + printf("gst_start_pipeline() failed for pipeline_split_screen\n"); + goto error_gst_pipeline; + } + + return 0; +error_gst_pipeline: + return -1; +} diff --git a/apps/linux/genavb-media-app/gstreamer_stream.h b/apps/linux/genavb-media-app/gstreamer_stream.h new file mode 100644 index 0000000..5cd7190 --- /dev/null +++ b/apps/linux/genavb-media-app/gstreamer_stream.h @@ -0,0 +1,63 @@ +/* + * Copyright 2017-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _GSTREAMER_STREAM_H_ +#define _GSTREAMER_STREAM_H_ + +#include "../common/gstreamer_single.h" +#include "../common/gstreamer_multisink.h" +#include "../common/thread_config.h" + +/* Default input video file name */ +#define DEFAULT_MEDIA_FILE_NAME "sample1.mp4" +/* Default dump file*/ +#define DEFAULT_DUMP_LOCATION "/var/avb_listener_dump" + +#define MAX_ALSA_TALKERS 6 +#define MAX_ALSA_LISTENERS 6 +#define MAX_GSTREAMER_LISTENERS 4 +#define MAX_GSTREAMER_TALKERS 4 + +#define CAMERA_TYPE_SALSA 0 +#define CAMERA_TYPE_H264_1722_2016 1 +#define CAMERA_TYPE_H264_1722_2013 2 + +/*Maximum number of pipeline bus to monitor simultaneously for messages*/ +#define MAX_GST_PIPELINES_MSG_MONITOR 8 +#define GST_BUS_TIMER_TIMEOUT 200000000 /*100 ms*/ +struct gstreamer_bus_messages_monitor { + struct gstreamer_pipeline *gst_pipelines[MAX_GST_PIPELINES_MSG_MONITOR]; + pthread_mutex_t list_lock; + unsigned int nb_pipelines; + thr_thread_slot_t *thread_slot; + int timer_fd; +}; + +extern struct gstreamer_pipeline gstreamer_listener_pipelines[MAX_GSTREAMER_LISTENERS]; +extern struct gstreamer_stream gstreamer_listener_stream[MAX_GSTREAMER_LISTENERS]; +extern struct gstreamer_pipeline gstreamer_talker_pipelines[MAX_GSTREAMER_TALKERS]; +extern struct gstreamer_stream gstreamer_talker_stream[MAX_GSTREAMER_TALKERS]; + +void gstreamer_pipeline_init(struct gstreamer_pipeline *pipeline); + +int select_gst_listener_pipeline(struct gstreamer_pipeline *gst_pipeline, const struct avdecc_format *format); +int select_prepare_gst_talker_pipeline(struct gstreamer_pipeline *gst_pipeline, const struct avdecc_format *format); +void gst_pipeline_configure_pts_offset(struct gstreamer_pipeline *gst_pipeline, const struct avdecc_format *format); +void dump_gst_config(const struct gstreamer_pipeline *pipeline); + +int listener_gstreamer_connect(struct gstreamer_stream *listener, struct avb_stream_params *params, unsigned int avdecc_stream_index); +void listener_gstreamer_disconnect(struct gstreamer_stream *listener); + +int talker_gstreamer_connect(struct gstreamer_stream *talker, struct avb_stream_params *params, unsigned int avdecc_stream_index); +int talker_gstreamer_multi_connect(struct avb_stream_params *params, struct gstreamer_talker_multi_handler *talker_multi, unsigned int sink_index, unsigned int stream_index); +void talker_gstreamer_disconnect(struct gstreamer_stream *talker); +void talker_gstreamer_multi_disconnect(struct gstreamer_talker_multi_handler *talker_multi, unsigned int sink_index); + +int gstreamer_split_screen_start(struct gstreamer_pipeline *pipeline); + +int create_stream_timer(unsigned int interval); + +#endif /* _GSTREAMER_STREAM_H_ */ diff --git a/apps/linux/genavb-media-app/h264_camera.c b/apps/linux/genavb-media-app/h264_camera.c new file mode 100644 index 0000000..89e048f --- /dev/null +++ b/apps/linux/genavb-media-app/h264_camera.c @@ -0,0 +1,164 @@ +/* + * Copyright 2017-2018, 2020-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include + +#include "h264_camera.h" +#include "gstreamer_stream.h" +#include "../common/avb_stream.h" +#include "../common/thread.h" +#include "../common/gst_pipeline_definitions.h" + + +#ifdef CFG_AVTP_1722A + +struct avb_stream_params h264_camera_stream_params_1722_2016 = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_CVF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_MEDIA_CLOCK_DOMAIN_STREAM, + .flags = 0, + .format.u.s = { + .v = 0, + .subtype = AVTP_SUBTYPE_CVF, + .subtype_u.cvf = { + .format = CVF_FORMAT_RFC, + .subtype = CVF_FORMAT_SUBTYPE_H264, + .format_u.h264 = { + .spec_version = CVF_H264_IEEE_1722_2016, + }, + }, + }, + .port = 0, + .stream_id = { 0x0e, 0x0a, 0x35, 0x10, 0x20, 0x30, 0x33, 0x50 }, + .dst_mac = { 0x91, 0xe0, 0xf0, 0x00, 0x06, 0x00 }, +}; + +struct avb_stream_params h264_camera_stream_params_1722_2013 = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_CVF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_MEDIA_CLOCK_DOMAIN_STREAM, + .flags = 0, + .format.u.s = { + .v = 0, + .subtype = AVTP_SUBTYPE_CVF, + .subtype_u.cvf = { + .format = CVF_FORMAT_RFC, + .subtype = CVF_FORMAT_SUBTYPE_H264, + .format_u.h264 = { + .spec_version = CVF_H264_IEEE_1722_2013, + }, + }, + }, + .port = 0, + .stream_id = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }, + .dst_mac = { 0x91, 0xe0, 0xf0, 0x00, 0x06, 0x00 }, +}; + +int h264_camera_connect(struct gstreamer_stream *h264_stream) +{ + struct avb_handle *avb_h = avbstream_get_avb_handle(); + int rc; + struct avb_stream_params *params; + struct gstreamer_pipeline_config *gst_config = &h264_stream->pipe_source.gst_pipeline->config; + + if(h264_stream->created) { + printf("%s : H264 Camera stream already connected \n", __func__); + return -1; + } + + if (gst_config->listener.camera_type == CAMERA_TYPE_H264_1722_2013) + params = &h264_camera_stream_params_1722_2013; + else if (gst_config->listener.camera_type == CAMERA_TYPE_H264_1722_2016) + params = &h264_camera_stream_params_1722_2016; + else { + printf("%s : Unsupported camera type \n", __func__); + return -1; + } + + apply_stream_params(h264_stream, params); + + if (gst_config->listener.stream_ids_mappping[gst_config->nstreams - 1].stream_id) + memcpy(&h264_stream->params.stream_id, &gst_config->listener.stream_ids_mappping[gst_config->nstreams - 1].stream_id, sizeof(gst_config->listener.stream_ids_mappping[gst_config->nstreams - 1].stream_id)); + + select_gst_listener_pipeline(h264_stream->pipe_source.gst_pipeline, &h264_stream->params.format); + gst_pipeline_configure_pts_offset(h264_stream->pipe_source.gst_pipeline, &h264_stream->params.format); + dump_gst_config(h264_stream->pipe_source.gst_pipeline); + + /* + * setup and kick-off gstreamer pipeline + * For now, assume 1 AVB stream <=> 1 pipeline + */ + + if (gst_start_pipeline(h264_stream->pipe_source.gst_pipeline, GST_PRIORITY, GST_DIRECTION_LISTENER) < 0) { + printf("gst_start_pipeline() failed\n"); + rc = -1; + goto error_gst_pipeline; + } + + h264_stream->pipe_source.source = h264_stream->pipe_source.gst_pipeline->source[0].source; + + rc = avb_stream_create(avb_h, &h264_stream->stream_h, &h264_stream->params, &h264_stream->batch_size, h264_stream->flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", h264_stream->batch_size); + + /* + * retrieve the file descriptor associated to the stream + */ + h264_stream->stream_fd = avb_stream_fd(h264_stream->stream_h); + if (h264_stream->stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(h264_stream->stream_fd)); + rc = -1; + goto error_stream_fd; + } + + if (thread_slot_add(THR_CAP_STREAM_LISTENER, h264_stream->stream_fd, EPOLLIN, h264_stream, (int (*)(void *, unsigned int))h264_stream->listener_gst_handler, NULL, 0, (thr_thread_slot_t **)&h264_stream->thread) < 0) + goto error_thread; + + h264_stream->created = 1; + return 0; + + +error_thread: +error_stream_fd: + avb_stream_destroy(h264_stream->stream_h); +error_stream_create: + gst_stop_pipeline(h264_stream->pipe_source.gst_pipeline); + +error_gst_pipeline: + return -1; + +} + + + +void h264_camera_disconnect(struct gstreamer_stream *h264_stream) +{ + if(h264_stream->created) { + thread_slot_free(h264_stream->thread); + + avb_stream_destroy(h264_stream->stream_h); + + gst_stop_pipeline(h264_stream->pipe_source.gst_pipeline); + h264_stream->created = 0; + } +} + +void h264_camera_init(void) +{ + /* Nothing to do for now */ + return; +} + +#endif diff --git a/apps/linux/genavb-media-app/h264_camera.h b/apps/linux/genavb-media-app/h264_camera.h new file mode 100644 index 0000000..b74a535 --- /dev/null +++ b/apps/linux/genavb-media-app/h264_camera.h @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _H264_CAMERA_H_ +#define _H264_CAMERA_H_ + +#ifdef CFG_AVTP_1722A +#include "../common/gstreamer.h" +#include "../common/gst_pipeline_definitions.h" +#include "../common/gstreamer_single.h" + +void h264_camera_init(void); +int h264_camera_connect(struct gstreamer_stream *h264_stream); +void h264_camera_disconnect(struct gstreamer_stream *h264_stream); + + +#endif + + +#endif /* _H264_CAMERA_H_ */ diff --git a/apps/linux/genavb-media-app/main.c b/apps/linux/genavb-media-app/main.c new file mode 100644 index 0000000..c2851d4 --- /dev/null +++ b/apps/linux/genavb-media-app/main.c @@ -0,0 +1,2104 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "../common/log.h" +#include "../common/time.h" +#include "../common/avb_stream.h" +#include "../common/thread.h" +#include "../common/alsa2.h" +#include "../common/alsa_stream.h" +#include "../common/clock.h" +#include "../common/clock_domain.h" +#include "../common/msrp.h" +#include "../common/crf_stream.h" +#include "../common/gstreamer_single.h" +#include "../common/gstreamer_multisink.h" +#include "../common/aecp.h" +#include "../common/avdecc.h" +#include "../common/audio_mappings.h" +#include "gstreamer_stream.h" +#include "salsa_camera.h" +#include "h264_camera.h" + + +#define FB_LVDS_DEVICE_FILE "/dev/fb0" +#define FB_HDMI_DEVICE_FILE "/dev/fb2" + +/*Supported Static Streams: + * - SALSA + * - H264 + * - BEV + */ +#define MAX_STATIC_STREAM_LISTENERS 3 +#define MAX_STATIC_STREAM_TALKERS 3 +#define SALSA_IDX 0 +#define H264_IDX 1 +#define BEV_IDX 2 + +#define DEFAULT_ALSA_CAPTURE_DEVICE "plughw:0,0" +#define DEFAULT_ALSA_PLAYBACK_DEVICE "plughw:0,0" + +static int signal_terminate = 0; + +static struct avb_handle *avb_handle; + +static struct alsa_stream alsa_talker_stream[MAX_ALSA_TALKERS]; +static struct alsa_stream alsa_listener_stream[MAX_ALSA_LISTENERS]; + +struct gstreamer_pipeline gstreamer_listener_pipelines[MAX_GSTREAMER_LISTENERS] = { [0 ... MAX_GSTREAMER_LISTENERS - 1].msg_lock = PTHREAD_MUTEX_INITIALIZER };; // Less than MAX_LISTENERS may be needed, but this is the worst case +struct gstreamer_stream gstreamer_listener_stream[MAX_GSTREAMER_LISTENERS]; + +struct gstreamer_pipeline gstreamer_talker_pipelines[MAX_GSTREAMER_TALKERS] = { [0 ... MAX_GSTREAMER_TALKERS - 1].msg_lock = PTHREAD_MUTEX_INITIALIZER };; // Less than MAX_TALKERS may be needed, but this is the worst case +struct gstreamer_stream gstreamer_talker_stream[MAX_GSTREAMER_TALKERS]; + +static struct gstreamer_pipeline split_screen_pipeline; + +static struct gstreamer_talker_multi_handler gst_talker_multi_handler[MAX_GST_MULTI_HANDLERS] = { [0 ... MAX_GST_MULTI_HANDLERS - 1].gst_pipeline.msg_lock = PTHREAD_MUTEX_INITIALIZER }; +static struct gstreamer_listener_multi_handler gst_listener_multi_handler[MAX_GST_MULTI_HANDLERS] = { [0 ... MAX_GST_MULTI_HANDLERS - 1].gst_pipeline.msg_lock = PTHREAD_MUTEX_INITIALIZER }; + +/*Multi Handler structs*/ +static struct talker_gst_media gstreamer_multi_talker_media[MAX_GST_MULTI_HANDLERS] = { [0 ... MAX_GST_MULTI_HANDLERS - 1].stream_lock = PTHREAD_MUTEX_INITIALIZER }; +static struct talker_gst_multi_app gstreamer_multi_talker_multi_app[MAX_GST_MULTI_HANDLERS_STREAMS] = { [0 ... MAX_GST_MULTI_HANDLERS_STREAMS - 1].samples_lock = PTHREAD_MUTEX_INITIALIZER }; +static struct gstreamer_stream gstreamer_multi_talker_streams[MAX_GST_MULTI_HANDLERS_STREAMS]; + +static struct gstreamer_bus_messages_monitor gstreamer_bus_monitor = { .list_lock = PTHREAD_MUTEX_INITIALIZER, .timer_fd = -1 }; + +#define MAX_AVDECC_LISTENERS 8 +#define MAX_AVDECC_TALKERS 8 + +/*Control FDs : + * 1- AVB_CTRL_AVDECC_MEDIA_STACK + */ +#define NUM_CONTROL_FDS 3 + +enum stream_handler_type { + STREAM_NONE = 0, + STREAM_ALSA, + STREAM_GST, + STREAM_MULTI_GST, + STREAM_CRF, +}; + +struct media_generic_stream { + enum stream_handler_type handler_type; + enum stream_handler_type requested_type; + unsigned long long stream_id; + + struct alsa_config { + int index; + } alsa; + + struct gst_config { + int index; + } gst; + + struct multi_gst_config { + int input_index; + int output_index; + } multi_gst; + + struct crf_config { + int index; + } crf; +}; + +static struct media_generic_stream listener_streams[MAX_AVDECC_LISTENERS]; +static struct media_generic_stream talker_streams[MAX_AVDECC_TALKERS]; +static struct media_generic_stream listener_static_streams[MAX_STATIC_STREAM_LISTENERS]; +static struct media_generic_stream talker_static_streams[MAX_STATIC_STREAM_TALKERS]; + +static void handler_start(int handler_index) +{ + int i; + //FIXME Only one multi handler is supported + struct gstreamer_talker_multi_handler *talker_multi = &gst_talker_multi_handler[handler_index]; + + if (!talker_multi->configured_streams) + return; + + pthread_mutex_lock(&talker_multi->gst_media->stream_lock); + + for (i = 0; i < GST_MAX_SINKS; i++) + talker_gst_multi_stream_fsm(talker_multi->multi_app[i], STREAM_EVENT_PLAY); + + pthread_mutex_unlock(&talker_multi->gst_media->stream_lock); +} + +static void handler_stop(int handler_index) +{ + int i; + struct gstreamer_talker_multi_handler *talker_multi = &gst_talker_multi_handler[handler_index]; + + if (!talker_multi->configured_streams) + return; + + pthread_mutex_lock(&talker_multi->gst_media->stream_lock); + + for (i = 0; i < GST_MAX_SINKS; i++) + talker_gst_multi_stream_fsm(talker_multi->multi_app[i], STREAM_EVENT_STOP); + + pthread_mutex_unlock(&talker_multi->gst_media->stream_lock); +} + +static void aem_send_media_track_name_control_unsolicited_response(struct avdecc_controlled *controlled, const char *track) +{ + aecp_aem_send_set_control_utf8_unsolicited_response(controlled->handle, 3, track); +} + +static int aem_set_control_handler(struct avdecc_controlled *controlled, avb_u16 ctrl_index, void *ctrl_value) +{ + avb_u8 value = *(avb_u8 *)ctrl_value; + int rc = AECP_AEM_SUCCESS; + struct gstreamer_pipeline_config *gst_config = NULL; + + /* Only descriptor indices 1,2 have been mapped here */ + switch(ctrl_index) { + case 1: /* play/stop */ + if (value) { /* PLAY <--> 255 */ + printf("Start playing stream\n"); + //FIXME Only one multi handler is supported + handler_start(0); + } else { /* STOP <--> 0 */ + printf("Stop playing stream\n"); + handler_stop(0); + } + break; + + case 2: /* media track ID */ + printf("Received SET_CONTROL command for media track(%d) from AVDECC\n", value); + //FIXME Apply control to first mutli streams pileine only + gst_config = &gst_talker_multi_handler[0].gst_pipeline.config; + if (!(gst_config->type & GST_TYPE_MULTI_TALKER)) { + printf("[ERROR] Received SET_CONTROL command for a non-configured stream \n"); + rc = AECP_AEM_NO_RESOURCES; + break; + } + + if ((value == 0) || (value > gst_config->talker.n_input_media_files)) { + rc = AECP_AEM_BAD_ARGUMENTS; + value = gst_config->talker.input_media_file_index + 1; + printf("media track index out-of-bounds, clamping to %d\n", value); + } + + *(avb_u8 *)ctrl_value = value; + if (value != (gst_config->talker.input_media_file_index + 1)) { + + handler_stop(0); + + gst_config->talker.input_media_file_index = value - 1; + gst_config->talker.file_src_location = gst_config->talker.input_media_files[gst_config->talker.input_media_file_index]; + aem_send_media_track_name_control_unsolicited_response(controlled, gst_config->talker.file_src_location); + printf("Setting input file to %s\n", gst_config->talker.file_src_location); + sleep(PREVNEXT_STOP_DURATION); + + handler_start(0); + + } + break; + + default: + printf("AECP Command SET_CONTROL with index (0x%x) not handled in this app, skip\n", ctrl_index); + rc = AECP_AEM_NOT_IMPLEMENTED; + break; + } + + return rc; +} + +static void avdecc_streams_init(void) +{ + unsigned int i; + + for (i= 0; i < MAX_AVDECC_LISTENERS; i++) { + listener_streams[i].handler_type = STREAM_NONE; + listener_streams[i].requested_type = STREAM_NONE; + listener_streams[i].alsa.index = i; + listener_streams[i].gst.index = 0; // Only one Gstreamer video sink supported for now + listener_streams[i].crf.index = 0; // Only support clock domain 0 + } + + for (i= 0; i < MAX_AVDECC_TALKERS; i++) { + talker_streams[i].handler_type = STREAM_NONE; + talker_streams[i].requested_type = STREAM_NONE; + talker_streams[i].alsa.index = i; + talker_streams[i].gst.index = i; //FIXME ??? + talker_streams[i].crf.index = 0; + } +} + +static void print_available_multi_handlers() { + + printf("\n The available multi handlers are the following: \n"); + printf("\t AVDECC: \n"); + printf("\t \t Talkers: \n"); + printf("\t \t \t -m 0:0 --> A/V IEC_61883_CIP_FMT_4\n\n"); + printf("\t \t \t -m 0:1 --> A IEC_61883_CIP_FMT_6 \n"); + printf("\t \t \t -m 0:2 --> V CVF H264 \n"); + printf("\t \t Listeners: N/A \n"); + printf("\t STATIC: \n"); + printf("\t \t Talkers: N/A \n"); + printf("\t \t Listeners: \n"); + printf("\t \t \t CVF MJPEG --> -m X:0 (Where 0 <= X <= 3)\n\n"); + printf("\t \t \t CVF MJPEG --> -m X:0 (Where 0 <= X <= 3)\n\n"); + printf("\t \t \t CVF MJPEG --> -m X:0 (Where 0 <= X <= 3)\n\n"); + printf("\t \t \t CVF MJPEG --> -m X:0 (Where 0 <= X <= 3)\n\n"); + printf("\n"); +} + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\ngenavb-media-app can be configured for listener [L] or talker [T]\n"); + printf("\n Configuration options should be passed in the following order: \n"); + printf("\n \t {-L|-T} {-A|-S} {-g|-m|-z} ... \n"); + printf("\n If no Talker [-T] nor Listener [-L] options are passed, genavb-media-app will handle by default avdecc audio streams with alsa handlers\n"); + printf("\nOptions:\n" + "\t-L The following options are for listener config\n" + "\t-T The following options are for Talker config\n" + "\t-A The stream with avdecc stream index\n" + "\t-S The static stream index\n" + "\t-g Index of the gstreamer handler\n" + "\t-m Input and output indexes for the gstreamer multi handler\n" + "\t-z Index of the alsa handler\n" + "\t-c Index of the clock domain (which act as the CRF handler)\n" + "\t-v [L] video rendering of IEC_61883_CIP_FMT_4\n" + "\t-a [T/L] For Listener :audio rendering of IEC_61883_CIP_FMT_4\n" + "\t For Talker: sending audio stream only over IEC_61883_CIP_FMT_4\n" + "\t-d [T/L] display device (lvds (default), hdmi)\n" + "\t-r [T/L] scaling (1080, 720, 768 (default), 480)\n" + "\t-p [T/L] For Listener :media stack presentation time offset (in ns)\n" + "\t For Talker: render delay for local preview (in ns)\n" +#ifdef CFG_AVTP_1722A + "\t-M [L] MJPEG camera mode, static stream configuration\n" + "\t-H [L] H264 camera mode, static stream configuration \n" + "\t H264_1722_2016: H264 camera mode IEEE1722_2016 based, static stream configuration\n" + "\t H264_1722_2013: H264 camera mode IEEE1722_2013 based, static stream configuration\n" + "\t-I [L] Stream ID for the H264 static stream configuration \n" + "\t-F [T/L] Free-run mode : Disable sync to clock on gst pipeline\n" + "\t video sinks\n" +#endif + "\t-f [T] input file name \n" + "\t-l [T] local video preview\n" + "\t-D [L] dump received stream to given location (Default: /var/avb_listener_dump)\n" + "\t-C ALSA capture device to be used for either gst handler or alsa handler\n" + "\t-P ALSA playback device to be used for either gst handler or alsa handler\n" + "\t-b The file used to save Milan bindings parameters\n" + "\t-h print this help text\n"); + printf("\nDefault: audio and video, auto-detect resolution, lvds\n"); + print_available_multi_handlers(); +} + +static int gst_bus_messages_handler(void *data, unsigned int events) +{ + struct gstreamer_bus_messages_monitor *gst_bus_monitor = (struct gstreamer_bus_messages_monitor *) data; + int i, rc; + char buf[10]; + + // FIXME check read value (number of times timer expired) + rc = read(gst_bus_monitor->timer_fd, buf, 10); + if (rc < 0) { + ERR_LOG("read error: %s", strerror(errno)); + return -1; + } + + pthread_mutex_lock(&gst_bus_monitor->list_lock); + + for (i = 0; i < MAX_GST_PIPELINES_MSG_MONITOR; i++) { + if (gst_bus_monitor->gst_pipelines[i] && gst_bus_monitor->gst_pipelines[i]->bus) { + gst_process_bus_messages(gst_bus_monitor->gst_pipelines[i]); + } + } + + pthread_mutex_unlock(&gst_bus_monitor->list_lock); + + return 0; +} + +static int stats_handler(void *data, unsigned int events) +{ + int fd = (int)(intptr_t)data; + int i, rc; + char buf[10]; + + // FIXME check read value (number of times timer expired) + rc = read(fd, buf, 10); + if (rc < 0) { + ERR_LOG("read error: %s", strerror(errno)); + return -1; + } + + // Handle stats dump + for (i = 0; i < MAX_ALSA_TALKERS; ++i) { + struct alsa_stream_stats *stats = &alsa_talker_stream[i].stats; + + if (stream_stats_is_updated(&stats->gen_stats)) { + alsa_stats_dump(stats); + stats->gen_stats.is_updated_flag = 0; + } + } + + for (i = 0; i < MAX_ALSA_LISTENERS; ++i) { + struct alsa_stream_stats *stats = &alsa_listener_stream[i].stats; + + if (stream_stats_is_updated(&stats->gen_stats)) { + alsa_stats_dump(stats); + stats->gen_stats.is_updated_flag = 0; + } + } + + thread_print_stats(); + + return 0; +} + +static int gst_bus_messages_timer_thread_init(struct gstreamer_bus_messages_monitor *gst_bus_monitor) +{ + struct itimerspec timeout; + + gst_bus_monitor->timer_fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (gst_bus_monitor->timer_fd < 0) { + ERR("timerfd_create() failed: %s", strerror(errno)); + goto err_create; + } + + timeout.it_interval.tv_sec = 0; + timeout.it_interval.tv_nsec = GST_BUS_TIMER_TIMEOUT; + timeout.it_value.tv_sec = 0; + timeout.it_value.tv_nsec = GST_BUS_TIMER_TIMEOUT; + if (timerfd_settime(gst_bus_monitor->timer_fd, 0, &timeout, NULL) < 0) { + ERR("timerfd_settime() failed: %s", strerror(errno)); + goto err_settime; + } + + printf("\n %s : Add the gst bus timer thread slot \n",__func__); + + if (thread_slot_add(THR_CAP_GST_BUS_TIMER, gst_bus_monitor->timer_fd, EPOLLIN, (void *)gst_bus_monitor, gst_bus_messages_handler, NULL, 0, &gst_bus_monitor->thread_slot) < 0) { + ERR("thread_slot_add() failed"); + goto err_slot_add; + } + + return 0; + +err_slot_add: +err_settime: + close(gst_bus_monitor->timer_fd); + gst_bus_monitor->timer_fd = -1; + +err_create: + gst_bus_monitor->thread_slot = NULL; + + return -1; +} + +static void gst_bus_messages_timer_thread_exit(struct gstreamer_bus_messages_monitor *gst_bus_monitor) +{ + if (gst_bus_monitor->thread_slot) + thread_slot_free(gst_bus_monitor->thread_slot); + + if (gst_bus_monitor->timer_fd >= 0) { + close(gst_bus_monitor->timer_fd); + gst_bus_monitor->timer_fd = -1; + } +} + +static int stats_thread_init(thr_thread_slot_t **thread_slot) +{ + int i; + int fd; + struct itimerspec timeout; + + for (i = 0; i < g_max_avb_talker_streams; ++i) { + struct alsa_stream_stats *stats = &alsa_talker_stream[i].stats; + + stats->gen_stats.app_ptr = (unsigned long)&alsa_talker_stream[i]; + stats->alsa_device = i; + stats->alsa_direction = AAR_DATA_DIR_INPUT; + stats->alsa_handle_ptr = (unsigned long)&alsa_talker_stream[i].alsa_handle; + stats->gen_stats.avb_stream_ptr = (unsigned long)&g_avb_talker_streams[i]; + stats->gen_stats.is_listener = 0; + stats->gen_stats.period = 10; + } + + for (i = 0; i < g_max_avb_listener_streams; ++i) { + struct alsa_stream_stats *stats = &alsa_listener_stream[i].stats; + + stats->gen_stats.app_ptr = (unsigned long)&alsa_listener_stream[i]; + stats->alsa_device = i; + stats->alsa_direction = AAR_DATA_DIR_OUTPUT; + stats->alsa_handle_ptr = (unsigned long)&alsa_listener_stream[i].alsa_handle; + stats->gen_stats.avb_stream_ptr = (unsigned long)&g_avb_listener_streams[i]; + stats->gen_stats.is_listener = 1; + stats->gen_stats.period = 10; + } + + fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (fd < 0) { + ERR("timerfd_create() failed: %s", strerror(errno)); + goto err_create; + } + + timeout.it_interval.tv_sec = 1; + timeout.it_interval.tv_nsec = 0; + timeout.it_value.tv_sec = 1; + timeout.it_value.tv_nsec = 0; + if (timerfd_settime(fd, 0, &timeout, NULL) < 0) { + ERR("timerfd_settime() failed: %s", strerror(errno)); + goto err_settime; + } + + printf("\n %s : Add the stats thread slot \n",__func__); + + if (thread_slot_add(THR_CAP_STATS, fd, EPOLLIN, (void *)(intptr_t)fd, stats_handler, NULL, 0, thread_slot) < 0) { + ERR("thread_slot_add() failed"); + goto err_slot_add; + } + + return fd; + +err_slot_add: +err_settime: + close(fd); + +err_create: + *thread_slot = NULL; + + return -1; +} + +static void stats_thread_exit(int fd, thr_thread_slot_t *thread_slot) +{ + if (thread_slot) + thread_slot_free(thread_slot); + + if (fd >= 0) + close(fd); +} + +static int controlled_slot_init(thr_thread_slot_t **controlled_thread_slot, struct avdecc_controlled *controlled) +{ + int controllled_rx_fd; + controllled_rx_fd = avb_control_rx_fd(controlled->handle); + controlled->aem_set_control_handler = aem_set_control_handler; + controlled->aem_get_audio_mappings_handler = audio_mappings_get; + controlled->aem_add_audio_mappings_handler = audio_mappings_add; + controlled->aem_remove_audio_mappings_handler = audio_mappings_remove; + + printf("\n %s : Add the controlled data slot \n",__func__); + + if (thread_slot_add(THR_CAP_CONTROLLED, controllled_rx_fd, EPOLLIN, controlled, (int (*)(void *, unsigned int events))avdecc_controlled_handler, NULL, 0, (thr_thread_slot_t **)controlled_thread_slot) < 0) + goto err; + + + return 0; + +err: + *controlled_thread_slot = NULL; + + return -1; +} + +static void controlled_slot_exit(thr_thread_slot_t *controlled_thread_slot) +{ + if (controlled_thread_slot) + thread_slot_free(controlled_thread_slot); +} + +static int init_media_clock_source(unsigned int domain_index, struct avb_stream_params *listener_stream_params) +{ + int rc; + aar_crf_stream_t *crf; + genavb_clock_domain_t clock_domain; + + crf = crf_stream_get(domain_index); + + if (!crf) { + printf("init_media_clock_source: current crf config is null\n"); + goto err; + } + + clock_domain = crf_get_clock_domain(domain_index); + + /* If CRF is already connected, do nothing: the media clock domain source should be properly set. */ + if (crf->stream_handle) + goto exit; + + if (!listener_stream_params) { + /* If connecting an AVTP talker stream without any previous CRF connection: set as media clock master. */ + rc = clock_domain_set_role(MEDIA_CLOCK_MASTER, clock_domain, NULL); + if (rc != AVB_SUCCESS) { + printf("clock_domain_set_role failed: %s\n", avb_strerror(rc)); + goto err; + } + } else { + /* If connecting an AVTP listener stream without any previous CRF connection: set as media clock slave to that stream. */ + rc = clock_domain_set_role(MEDIA_CLOCK_SLAVE, clock_domain, listener_stream_params); + if (rc != AVB_SUCCESS) { + printf("clock_domain_set_role failed: %s\n", avb_strerror(rc)); + goto err; + } + } + +exit: + return 0; + +err: + return -1; +} + +static int media_clock_init(unsigned int domain_index) +{ + int rc; + aar_crf_stream_t *crf; + genavb_clock_domain_t clock_domain; + media_clock_role_t role; + + crf = crf_stream_get(domain_index); + + if (!crf) { + printf("media_clock_init current crf config is null\n"); + goto err_crf; + } + + clock_domain = crf_get_clock_domain(domain_index); + + if (crf->stream_params.direction == AVTP_DIRECTION_TALKER) { + role = MEDIA_CLOCK_MASTER; + } else { + role = MEDIA_CLOCK_SLAVE; + } + + if (crf->stream_handle) { + ERR("CRF stream already initialized/connected for domain_index (%u)", domain_index); + goto exit; + } + + /* + * CRF static setup without AVDECC + */ + if (role == MEDIA_CLOCK_MASTER) { + rc = clock_domain_set_role(role, clock_domain, NULL); + if (rc != AVB_SUCCESS) { + printf("clock_domain_set_role failed: %s\n", avb_strerror(rc)); + goto err_crf; + } + + rc = crf_stream_create(domain_index); + if (rc != AVB_SUCCESS) { + printf("crf_stream_create failed: %s\n", avb_strerror(rc)); + goto err_clock_domain; + } + } + else { + rc = crf_stream_create(domain_index); + if (rc != AVB_SUCCESS) { + printf("crf_stream_create failed: %s\n", avb_strerror(rc)); + goto err_crf; + } + + rc = clock_domain_set_role(role, clock_domain, &crf->stream_params); + if (rc != AVB_SUCCESS) { + printf("clock_domain_set_role failed: %s\n", avb_strerror(rc)); + goto err_clock_domain; + } + } + +exit: + return 0; + +err_clock_domain: + crf_stream_destroy(domain_index); +err_crf: + return -1; +} + +static void media_clock_exit(unsigned int domain_index) +{ + int rc; + + rc = crf_stream_destroy(domain_index); + if (rc != AVB_SUCCESS) + printf("clock_domain_set_role failed: %s\n", avb_strerror(rc)); +} + +static void static_crf_streams_exit(void) +{ + unsigned int i; + + for (i= 0; i < MAX_STATIC_STREAM_LISTENERS; i++) { + if (listener_static_streams[i].requested_type == STREAM_CRF) { + media_clock_exit(listener_static_streams[i].crf.index); + break; + } + } + + for (i= 0; i < MAX_STATIC_STREAM_TALKERS; i++) { + if (talker_static_streams[i].requested_type == STREAM_CRF) { + media_clock_exit(talker_static_streams[i].crf.index); + break; + } + } +} + +static int static_crf_streams_init(void) +{ + unsigned int i; + int rc; + struct media_generic_stream *static_stream; + + for (i = 0; i < MAX_STATIC_STREAM_LISTENERS; i++) { + static_stream = &listener_static_streams[i]; + if (static_stream->requested_type == STREAM_CRF) { + rc = media_clock_init(static_stream->crf.index); + if (rc != AVB_SUCCESS) { + printf("media_clock_init for domain %u as listener failed\n", static_stream->crf.index); + goto err; + } + } else { + continue; + } + } + + for (i = 0; i < MAX_STATIC_STREAM_TALKERS; i++) { + static_stream = &talker_static_streams[i]; + if (static_stream->requested_type == STREAM_CRF) { + rc = media_clock_init(static_stream->crf.index); + if (rc != AVB_SUCCESS) { + printf("media_clock_init for domain %u as talker failed\n", static_stream->crf.index); + goto err; + } + } else { + continue; + } + } + + return 0; + +err: + static_crf_streams_exit(); + return -1; +} + +static void talker_connect(struct avb_stream_params *params, unsigned int avdecc_stream_index) +{ + struct media_generic_stream *avdecc_talker; + + if (avdecc_stream_index >= MAX_AVDECC_TALKERS) + return; + + avdecc_talker = &talker_streams[avdecc_stream_index]; + + if (avdecc_talker->handler_type != STREAM_NONE) + return; + + if (avdecc_format_is_crf(¶ms->format)) { + if (!crf_connect(MEDIA_CLOCK_MASTER, avdecc_talker->crf.index, params)) + avdecc_talker->handler_type = STREAM_CRF; + + } else { + + /* Before connecting any AVTP stream, check if we need to set the clock domain source for AVB_CLOCK_DOMAIN_0 */ + if (init_media_clock_source(0, NULL) < 0) { + printf("%s: init_media_clock_source() failed for AVB_CLOCK_DOMAIN_0, can not connect stream output (%u)\n", __func__, avdecc_stream_index); + return; + } + + if ((avdecc_format_is_aaf_pcm(¶ms->format)) || ((avdecc_format_is_61883_6(¶ms->format)) && (avdecc_talker->requested_type != STREAM_GST) && (avdecc_talker->requested_type != STREAM_MULTI_GST))) { + struct alsa_stream *talker = &alsa_talker_stream[avdecc_talker->alsa.index]; + + talker->index = avdecc_talker->alsa.index; + + if (!talker_alsa_connect(talker, params)) + avdecc_talker->handler_type = STREAM_ALSA; + } else if ( avdecc_format_is_61883_6(¶ms->format) || avdecc_format_is_61883_4(¶ms->format) || (params->subtype == AVTP_SUBTYPE_CVF)) { + if (avdecc_talker->requested_type == STREAM_MULTI_GST) { + struct gstreamer_talker_multi_handler *talker_multi = &gst_talker_multi_handler[avdecc_talker->multi_gst.input_index]; +; + if (!talker_gstreamer_multi_connect(params, talker_multi, avdecc_talker->multi_gst.output_index, avdecc_stream_index)) + avdecc_talker->handler_type = STREAM_MULTI_GST; + } else { + struct gstreamer_stream *talker = &gstreamer_talker_stream[avdecc_talker->gst.index]; + + talker->source_index = avdecc_talker->gst.index; + + if (!talker_gstreamer_connect(talker, params, avdecc_stream_index)) + avdecc_talker->handler_type = STREAM_GST; + } + } + } + +} + +static void talker_disconnect(unsigned int stream_index) +{ + struct media_generic_stream *avdecc_talker; + + if (stream_index >= MAX_AVDECC_TALKERS) + return; + + avdecc_talker = &talker_streams[stream_index]; + + switch (avdecc_talker->handler_type) { + case STREAM_ALSA: + talker_alsa_disconnect(&alsa_talker_stream[avdecc_talker->alsa.index]); + break; + + case STREAM_GST: + talker_gstreamer_disconnect(&gstreamer_talker_stream[avdecc_talker->gst.index]); + break; + + case STREAM_MULTI_GST: + talker_gstreamer_multi_disconnect(&gst_talker_multi_handler[avdecc_talker->multi_gst.input_index], avdecc_talker->multi_gst.output_index); + break; + + case STREAM_CRF: + crf_disconnect(avdecc_talker->crf.index); + break; + + case STREAM_NONE: + default: + break; + } + + avdecc_talker->handler_type = STREAM_NONE; +} + +static void listener_connect(struct avb_stream_params *params, unsigned int avdecc_stream_index) +{ + struct media_generic_stream *avdecc_listener; + + if (avdecc_stream_index >= MAX_AVDECC_LISTENERS) + return; + + avdecc_listener = &listener_streams[avdecc_stream_index]; + + if (avdecc_listener->handler_type != STREAM_NONE) + return; + + if (avdecc_format_is_crf(¶ms->format)) { + if (!crf_connect(MEDIA_CLOCK_SLAVE, avdecc_listener->crf.index, params)) + avdecc_listener->handler_type = STREAM_CRF; + } else { + /* Before connecting any AVTP stream, check if we need to set the clock domain source for AVB_CLOCK_DOMAIN_0 */ + if (init_media_clock_source(0, params) < 0) { + printf("%s: init_media_clock_source() failed for AVB_CLOCK_DOMAIN_0, can not connect stream input (%u)\n", __func__, avdecc_stream_index); + return; + } + + if ((avdecc_format_is_aaf_pcm(¶ms->format)) || ((avdecc_format_is_61883_6(¶ms->format)) && (avdecc_listener->requested_type != STREAM_GST))) { + struct alsa_stream *listener = &alsa_listener_stream[avdecc_listener->alsa.index]; + + listener->index = avdecc_listener->alsa.index; + + if (!listener_alsa_connect(listener, params)) + avdecc_listener->handler_type = STREAM_ALSA; + } else if (avdecc_format_is_61883_6(¶ms->format) || avdecc_format_is_61883_4(¶ms->format) || (params->subtype == AVTP_SUBTYPE_CVF)) { + struct gstreamer_stream *listener = &gstreamer_listener_stream[avdecc_listener->gst.index]; + + listener->source_index = avdecc_listener->gst.index; + + if (!listener_gstreamer_connect(listener, params, avdecc_stream_index)) + avdecc_listener->handler_type = STREAM_GST; + } + } +} + +static void listener_disconnect(int stream_index) +{ + struct media_generic_stream *avdecc_listener; + + if (stream_index >= MAX_AVDECC_LISTENERS) + return; + + avdecc_listener = &listener_streams[stream_index]; + + + switch (avdecc_listener->handler_type) { + case STREAM_ALSA: + listener_alsa_disconnect(&alsa_listener_stream[avdecc_listener->alsa.index]); + break; + + case STREAM_GST: + listener_gstreamer_disconnect(&gstreamer_listener_stream[avdecc_listener->gst.index]); + break; + + case STREAM_CRF: + crf_disconnect(avdecc_listener->crf.index); + break; + + case STREAM_NONE: + default: + break; + } + + avdecc_listener->handler_type = STREAM_NONE; +} + +static void avdecc_streams_disconnect(void) +{ + int i; + + for (i = 0; i < MAX_AVDECC_LISTENERS; i++) + listener_disconnect(i); + + for (i = 0; i < MAX_ALSA_TALKERS; i++) + talker_disconnect(i); + +} + +static int multi_talker_timeout_handler(struct gstreamer_talker_multi_handler *talker_multi, unsigned int events) +{ + int rc; + int i; + char tmp[8]; + int ret; + struct talker_gst_multi_app *stream; + + ret = read(talker_multi->timer_fd, tmp, 8); + if (ret < 0) + printf("%s: [talker_multi %p] timer_fd read() failed: %s\n", __func__, talker_multi, strerror(errno)); + + pthread_mutex_lock(&talker_multi->gst_media->stream_lock); + + for (i = 0; i < GST_MAX_SINKS; i++) { + stream = talker_multi->multi_app[i]; + rc = talker_gst_multi_stream_fsm(stream, STREAM_EVENT_TIMER); + if (rc < 0) + goto err_unlock; + } + + rc = talker_gst_multi_fsm(talker_multi->gst_media, GST_EVENT_TIMER); + if (rc < 0) + goto err_unlock; + + pthread_mutex_unlock(&talker_multi->gst_media->stream_lock); + + return 0; + +err_unlock: + pthread_mutex_unlock(&talker_multi->gst_media->stream_lock); + return rc; +} + +static int media_app_custom_gst_pipeline_setup(struct gstreamer_pipeline *gst) +{ + int i; + int rc = 0; + + printf("%s Add pipeline %p to gst bus monitored list \n", __func__, gst); + /* Add pipeline to monitor list*/ + pthread_mutex_lock(&gstreamer_bus_monitor.list_lock); + + for (i = 0; i < MAX_GST_PIPELINES_MSG_MONITOR; i++) { + if (!gstreamer_bus_monitor.gst_pipelines[i]) { + gstreamer_bus_monitor.gst_pipelines[i] = gst; + /* If first pipeline to monitor, enable the slot fd monitor*/ + if (!gstreamer_bus_monitor.nb_pipelines) { + thread_slot_set_events(gstreamer_bus_monitor.thread_slot, 1, EPOLLIN); + } + gstreamer_bus_monitor.nb_pipelines++; + goto exit; + } + } + + printf("[Error] %s: Can not find a free spot in the gst bus monitor list for pipeline %p \n", __func__, gst); + rc = -1; + +exit: + pthread_mutex_unlock(&gstreamer_bus_monitor.list_lock); + return rc; +} + +static void media_app_custom_gst_pipeline_teardown(struct gstreamer_pipeline *gst) +{ + int i; + printf("%s Remove pipeline %p from gst bus monitored list \n", __func__, gst); + + /* Remove pipeline to monitor list*/ + pthread_mutex_lock(&gstreamer_bus_monitor.list_lock); + + for (i = 0; i < MAX_GST_PIPELINES_MSG_MONITOR; i++) { + if (gstreamer_bus_monitor.gst_pipelines[i] == gst) { + gstreamer_bus_monitor.gst_pipelines[i] = NULL; + gstreamer_bus_monitor.nb_pipelines--; + /* If there is no more pipelines to monitor, disbale the slot fd monitor*/ + if (!gstreamer_bus_monitor.nb_pipelines) { + thread_slot_set_events(gstreamer_bus_monitor.thread_slot, 0, EPOLLIN); + } + pthread_mutex_unlock(&gstreamer_bus_monitor.list_lock); + return; + } + } + + printf("[Error] %s: Can not find gst %p in the gst bus monitor list \n", __func__, gst); + pthread_mutex_unlock(&gstreamer_bus_monitor.list_lock); + +} + + +static int avdecc_multi_talker_streams_init(void) +{ + int i,j; + struct gstreamer_talker_multi_handler *talker_multi; + + for (i = 0; i < MAX_GST_MULTI_HANDLERS; i++) { + talker_multi = &gst_talker_multi_handler[i]; + + if(!talker_multi->configured_streams) + continue; + + talker_multi->gst_media = &gstreamer_multi_talker_media[i]; + talker_multi->gst_media->state = GST_STATE_STOPPED; + talker_multi->gst_media->gst_pipeline = &talker_multi->gst_pipeline; + talker_multi->gst_media->gst_pipeline->custom_gst_pipeline_setup = media_app_custom_gst_pipeline_setup; + talker_multi->gst_media->gst_pipeline->custom_gst_pipeline_teardown = media_app_custom_gst_pipeline_teardown; + talker_multi->gst_pipeline.talker.gst = &gstreamer_multi_talker_media[i]; + + /* Init internal arrays */ + for (j = 0; j < GST_MAX_SINKS; j++ ) { + talker_multi->multi_app[j] = &gstreamer_multi_talker_multi_app[j]; + talker_multi->talkers[j] = &gstreamer_multi_talker_streams[j]; + talker_multi->multi_app[j]->gst = talker_multi->gst_media; + talker_multi->multi_app[j]->state = STREAM_STATE_DISCONNECTED; + talker_multi->multi_app[j]->sink_index = j; + talker_multi->gst_pipeline.sink[j].data = talker_multi->multi_app[j]; + } + + talker_multi->timer_fd = create_stream_timer(POLL_TIMEOUT_MS); + if (talker_multi->timer_fd < 0) { + printf("%s timerfd_create() failed %s\n", __func__, strerror(errno)); + goto error_stream_timer; + } + + if (thread_slot_add(THR_CAP_TIMER | THR_CAP_GST_MULTI, talker_multi->timer_fd, EPOLLIN, talker_multi, (int (*)(void *, unsigned int events))multi_talker_timeout_handler, NULL, 0, (thr_thread_slot_t **)&talker_multi->thread_timer) < 0) + goto error_timer_slot; + } + return 0; + +error_timer_slot: + close(talker_multi->timer_fd); +error_stream_timer: + return -1; + +} + +static void avdecc_multi_talker_streams_exit(void) +{ + int i,j; + struct gstreamer_talker_multi_handler *talker_multi; + + for (i = 0; i < MAX_GST_MULTI_HANDLERS; i++) { + talker_multi = &gst_talker_multi_handler[i]; + + if(!talker_multi->configured_streams) + continue; + + thread_slot_free(talker_multi->thread_timer); + + close(talker_multi->timer_fd); + + pthread_mutex_lock(&talker_multi->gst_media->stream_lock); + + for (j = 0; j < GST_MAX_SINKS; j++ ) + talker_gst_multi_stream_fsm(talker_multi->multi_app[j], STREAM_EVENT_DISCONNECT); + + pthread_mutex_unlock(&talker_multi->gst_media->stream_lock); + } + +} + +static int handle_avdecc_event(struct avb_control_handle *ctrl_h, const char *binding_file_name) +{ + struct avb_stream_params *params; + union avb_media_stack_msg msg; + unsigned int msg_type, msg_len; + int rc; + + msg_len = sizeof(union avb_media_stack_msg); + rc = avb_control_receive(ctrl_h, &msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) + goto error_control_receive; + + switch (msg_type) { + case AVB_MSG_MEDIA_STACK_CONNECT: + + params = &msg.media_stack_connect.stream_params; + + printf("AVB_MSG_MEDIA_STACK_CONNECT %u with flags 0x%x \n", msg.media_stack_connect.stream_index, msg.media_stack_connect.flags); + + if (params->direction == AVTP_DIRECTION_TALKER) { + talker_connect(params, msg.media_stack_connect.stream_index); + } else { + listener_connect(params, msg.media_stack_connect.stream_index); + } + + break; + + case AVB_MSG_MEDIA_STACK_DISCONNECT: + + printf("AVB_MSG_MEDIA_STACK_DISCONNECT %u\n", msg.media_stack_disconnect.stream_index); + + if (msg.media_stack_disconnect.direction == AVTP_DIRECTION_TALKER) { + talker_disconnect(msg.media_stack_disconnect.stream_index); + } else { + listener_disconnect(msg.media_stack_disconnect.stream_index); + } + + break; + + case GENAVB_MSG_MEDIA_STACK_BIND: + printf("GENAVB_MSG_MEDIA_STACK_BIND: Controller (%016"PRIx64") bound listener stream (%016"PRIx64", %u, %s) to talker stream (%016"PRIx64", %u) \n", + msg.media_stack_bind.controller_entity_id, + msg.media_stack_bind.entity_id, msg.media_stack_bind.listener_stream_index, (msg.media_stack_bind.started == ACMP_LISTENER_STREAM_STARTED) ? "STARTED" : "STOPPED", + msg.media_stack_bind.talker_entity_id, msg.media_stack_bind.talker_stream_index); + + if (binding_file_name) + avdecc_nvm_bindings_update(binding_file_name, &msg.media_stack_bind); + break; + + case GENAVB_MSG_MEDIA_STACK_UNBIND: + printf("GENAVB_MSG_MEDIA_STACK_UNBIND: listener stream (%016"PRIx64", %u) has been unbound\n", + msg.media_stack_bind.entity_id, msg.media_stack_bind.listener_stream_index); + + if (binding_file_name) + avdecc_nvm_bindings_remove(binding_file_name, &msg.media_stack_unbind); + break; + + default: + break; + } + +error_control_receive: + return rc; +} + +void signal_terminate_handler (int signal_num) +{ + signal_terminate = 1; +} + +static void alsa_talkers_device_config_init(char *alsa_device) +{ + struct alsa_stream *alsa_stream; + unsigned int i; + + for (i = 0; i < MAX_ALSA_TALKERS; i++) { + alsa_stream = &alsa_talker_stream[i]; + + // All alsa handlers point to the same default device + alsa_stream->alsa_device = alsa_device; + } +} + +static void alsa_listeners_device_config_init(char *alsa_device) +{ + struct alsa_stream *alsa_stream; + unsigned int i; + + for (i = 0; i < MAX_ALSA_LISTENERS; i++) { + alsa_stream = &alsa_listener_stream[i]; + + // All alsa handlers point to the same default device + alsa_stream->alsa_device = alsa_device; + } +} + +void gstreamer_config_init(void) +{ + struct gstreamer_pipeline *pipeline; + unsigned int i; + + gstreamer_init(); + + for (i = 0; i < MAX_GSTREAMER_LISTENERS; i++) { + pipeline = &gstreamer_listener_pipelines[i]; + + // Map each gstreamer stream to its pipeline + gstreamer_listener_stream[i].pipe_source.gst_pipeline = pipeline; + gstreamer_pipeline_init(pipeline); + pipeline->custom_gst_pipeline_setup = media_app_custom_gst_pipeline_setup; + pipeline->custom_gst_pipeline_teardown = media_app_custom_gst_pipeline_teardown; + } + gstreamer_pipeline_init(&split_screen_pipeline); + + for (i = 0; i < MAX_GSTREAMER_TALKERS; i++) { + pipeline = &gstreamer_talker_pipelines[i]; + + // Map each gstreamer stream to its pipeline + gstreamer_talker_stream[i].pipe_source.gst_pipeline = pipeline; + + gstreamer_pipeline_init(pipeline); + pipeline->custom_gst_pipeline_setup = media_app_custom_gst_pipeline_setup; + pipeline->custom_gst_pipeline_teardown = media_app_custom_gst_pipeline_teardown; + } + + for (i = 0; i < MAX_GST_MULTI_HANDLERS; i++) { + pipeline = &gst_talker_multi_handler[i].gst_pipeline; + + gstreamer_pipeline_init(pipeline); + pipeline->custom_gst_pipeline_setup = media_app_custom_gst_pipeline_setup; + pipeline->custom_gst_pipeline_teardown = media_app_custom_gst_pipeline_teardown; + } + + for (i = 0; i < MAX_GST_MULTI_HANDLERS; i++) { + pipeline = &gst_listener_multi_handler[i].gst_pipeline; + + gstreamer_pipeline_init(pipeline); + pipeline->custom_gst_pipeline_setup = media_app_custom_gst_pipeline_setup; + pipeline->custom_gst_pipeline_teardown = media_app_custom_gst_pipeline_teardown; + } + +#ifdef CFG_AVTP_1722A + salsa_camera_init(); + h264_camera_init(); +#endif +} + +void gstreamer_config_exit(void) +{ + gstreamer_reset(); +} + +static int static_streams_init(void) +{ + int rc = 0; +#ifdef CFG_AVTP_1722A + int i; + struct gstreamer_pipeline_config *gst_config = NULL; + struct media_generic_stream *static_stream; + + for (i = 0; i < MAX_STATIC_STREAM_LISTENERS; i++) { + static_stream = &listener_static_streams[i]; + + if (static_stream->requested_type == STREAM_GST) + gst_config = &gstreamer_listener_pipelines[static_stream->gst.index].config; + else if (static_stream->requested_type == STREAM_MULTI_GST) + gst_config = &gst_listener_multi_handler[static_stream->multi_gst.output_index].gst_pipeline.config; + else + continue; + + /* Check if a previous stream has passed for the same config*/ + if(gst_config->configured) + continue; + + if (gst_config->listener.flags & GST_FLAG_CAMERA) { + if(gst_config->listener.camera_type == CAMERA_TYPE_SALSA) { + switch (gst_config->nstreams) { + case 1: + if (static_stream->requested_type == STREAM_GST) { + if (salsa_camera_connect(&gstreamer_listener_stream[static_stream->gst.index]) < 0) { + printf("Couldn't start Salsa camera listener.\n"); + rc = -1; + goto exit; + } + } else if (static_stream->requested_type == STREAM_MULTI_GST) { + if (multi_salsa_split_screen_start(1, &gst_listener_multi_handler[static_stream->multi_gst.output_index].gst_pipeline) < 0) { + printf("Couldn't start split screen\n"); + rc = -1; + goto exit; + } + + } + break; + case 2: + case 3: + case 4: + default: + if (multi_salsa_split_screen_start(gst_config->nstreams, &gst_listener_multi_handler[static_stream->multi_gst.output_index].gst_pipeline) < 0) { + printf("Couldn't start split screen\n"); + rc = -1; + goto exit; + } + break; + } + } + else if (gst_config->listener.camera_type == CAMERA_TYPE_H264_1722_2013 || gst_config->listener.camera_type == CAMERA_TYPE_H264_1722_2016) { + + if(gst_config->nstreams != 1) { + printf("[ERROR] H264 Multi stream is not supported\n"); + rc = -1; + goto exit; + } + + if (h264_camera_connect(&gstreamer_listener_stream[static_stream->gst.index]) < 0) { + printf("Couldn't start H264 camera listener.\n"); + rc = -1; + goto exit; + } + } + } else if (gst_config->listener.flags & GST_FLAG_BEV) { + if (multi_salsa_surround_start(gst_config) < 0) { + printf("Couldn't start Surround view\n"); + rc = -1; + goto exit; + } + } + gst_config->configured = 1; + } + +exit: +#endif + return rc; +} + +static void static_streams_exit(void) +{ +#ifdef CFG_AVTP_1722A + int i; + struct gstreamer_pipeline_config *gst_config = NULL; + struct media_generic_stream *static_stream; + + for (i = 0; i < MAX_STATIC_STREAM_LISTENERS; i++) { + + static_stream = &listener_static_streams[i]; + + if (static_stream->requested_type == STREAM_GST) + gst_config = &gstreamer_listener_pipelines[static_stream->gst.index].config; + else if (static_stream->requested_type == STREAM_MULTI_GST) + gst_config = &gst_listener_multi_handler[static_stream->multi_gst.output_index].gst_pipeline.config; + else + continue; + + /* Check if a previous stream has passed for the same config*/ + if(!gst_config->configured) + continue; + + if (gst_config->listener.flags & GST_FLAG_CAMERA) { + if(gst_config->listener.camera_type == CAMERA_TYPE_SALSA) { + switch (gst_config->nstreams) { + case 2: + case 3: + case 4: + multi_salsa_camera_disconnect(gst_config->nstreams); + gst_stop_pipeline(&gst_listener_multi_handler[static_stream->multi_gst.output_index].gst_pipeline); + break; + case 1: + salsa_camera_disconnect(&gstreamer_listener_stream[static_stream->gst.index]); + break; + default: + printf("ERROR: invalid number of camera streams (%d)\n", gst_config->nstreams); + break; + } + } + else if (gst_config->listener.camera_type == CAMERA_TYPE_H264_1722_2013 || gst_config->listener.camera_type == CAMERA_TYPE_H264_1722_2016) + { + h264_camera_disconnect(&gstreamer_listener_stream[static_stream->gst.index]); + } + } else if (gst_config->listener.flags & GST_FLAG_BEV) { + multi_salsa_camera_disconnect(MAX_CAMERA); + surround_exit(); + } + + gst_config->configured = 0; + } +#endif +} + +static void set_default_device_width ( const char *device, unsigned int *width ) +{ + if (!strcmp(device,V4L2_HDMI_DEVICE_FILE) || !strcmp(device,FB_HDMI_DEVICE_FILE)) + *width = DEFAULT_HDMI_WIDTH; + else + *width = DEFAULT_LVDS_WIDTH; +} +static void set_default_device_height ( const char *device, unsigned int *height ) +{ + if (!strcmp(device,V4L2_HDMI_DEVICE_FILE) || !strcmp(device,FB_HDMI_DEVICE_FILE)) + *height = DEFAULT_HDMI_HEIGHT; + else + *height = DEFAULT_LVDS_HEIGHT; +} + +int main(int argc, char *argv[]) +{ + struct avb_control_handle *ctrl_h = NULL; + struct avdecc_controlled controlled = { 0 }; + int ctrl_rx_fd, clock_domain_rx_fd; + struct pollfd ctrl_poll[NUM_CONTROL_FDS]; + int option; + thr_thread_slot_t *stats_thread_slot = NULL; + thr_thread_slot_t *controlled_thread_slot = NULL; + int stats_fd; + struct sched_param param = { + .sched_priority = 1, + }; + struct gstreamer_pipeline_config *current_gst_config = NULL; + struct alsa_stream *current_alsa_config = NULL; + aar_crf_stream_t *current_crf_config = NULL; + int rc = 0; + avb_u64 crf_stream_id = 0; + struct sigaction action; + int i; + unsigned long long pts_offset; + unsigned int current_gst_handler_index = 0; + unsigned int current_alsa_handler_index = 0; + unsigned int current_crf_handler_index = 0; + unsigned int multi_handler_input_index = 0; + unsigned int multi_handler_output_index = 0; + int current_avdecc_stream_index = -1; + int current_static_stream_index = -1; + int current_config_direction = -1; + char *input_index, *output_index; + unsigned long long optval_ull; + unsigned long optval_ul; + char *binding_file_name = NULL; + + setlinebuf(stdout); + + printf("NXP's GenAVB reference media application\n"); + + rc = sched_setscheduler(0, SCHED_FIFO, ¶m); + if (rc < 0) { + printf("sched_setscheduler() failed: %s\n", strerror(errno)); + goto err_sched; + } + + avdecc_streams_init(); + audio_mappings_init(); + gstreamer_config_init(); + alsa_talkers_device_config_init(DEFAULT_ALSA_CAPTURE_DEVICE); + alsa_listeners_device_config_init(DEFAULT_ALSA_PLAYBACK_DEVICE); + +#ifdef CFG_AVTP_1722A + while ((option = getopt(argc, argv,"c:vaBS:d:r:hp:t:f:lLTH:I:FD:A:Mg:m:z:C:P:b:")) != -1) { +#else + while ((option = getopt(argc, argv,"c:vad:r:hp:t:f:lLTFD:A:S:g:m:z:C:P:b:")) != -1) { +#endif + switch (option) { + case 'L': + current_avdecc_stream_index = -1; + current_static_stream_index = -1; + current_gst_config = NULL; + current_alsa_config = NULL; + current_crf_config = NULL; + multi_handler_input_index = 0; + multi_handler_output_index = 0; + current_config_direction = AVTP_DIRECTION_LISTENER; + break; + case 'T': + current_config_direction = AVTP_DIRECTION_TALKER; + current_avdecc_stream_index = -1; + current_static_stream_index = -1; + current_gst_config = NULL; + current_alsa_config = NULL; + current_crf_config = NULL; + multi_handler_input_index = 0; + multi_handler_output_index = 0; + break; + case 'A': + if (h_strtoul(&optval_ul, optarg, NULL, 0) < 0) + goto err_sched; + + current_avdecc_stream_index = (int)optval_ul; + current_gst_config = NULL; + current_alsa_config = NULL; + current_crf_config = NULL; + break; + case 'S': + if (h_strtoul(&optval_ul, optarg, NULL, 0) < 0) + goto err_sched; + + current_static_stream_index = (int)optval_ul; + current_gst_config = NULL; + current_alsa_config = NULL; + current_crf_config = NULL; + break; + case 'z': + if (h_strtoul(&optval_ul, optarg, NULL, 0) < 0) + goto err_sched; + + current_alsa_handler_index = (unsigned int)optval_ul; + + if (current_avdecc_stream_index >= 0) { + if(current_config_direction == AVTP_DIRECTION_TALKER) { + if (current_alsa_handler_index >= MAX_ALSA_TALKERS){ + printf("[ERROR] Alsa handler index above max (MAX_ALSA_TALKERS=%d)\n", MAX_ALSA_TALKERS); + goto err_sched; + } + talker_streams[current_avdecc_stream_index].requested_type = STREAM_ALSA; + talker_streams[current_avdecc_stream_index].alsa.index = current_alsa_handler_index; + current_alsa_config = &alsa_talker_stream[current_alsa_handler_index]; + } else if (current_config_direction == AVTP_DIRECTION_LISTENER) { + if (current_alsa_handler_index >= MAX_ALSA_LISTENERS){ + printf("[ERROR] Alsa handler index above max (MAX_ALSA_LISTENERS=%d)\n", MAX_ALSA_LISTENERS); + goto err_sched; + } + listener_streams[current_avdecc_stream_index].requested_type = STREAM_ALSA; + listener_streams[current_avdecc_stream_index].alsa.index = current_alsa_handler_index; + current_alsa_config = &alsa_listener_stream[current_alsa_handler_index]; + } else { + printf("[ERROR] No Stream direction specified for the avdecc stream. \n"); + goto err_sched; + } + } else { + printf("[ERROR] No stream index specified. \n"); + goto err_sched; + } + break; + case 'g': + if (h_strtoul(&optval_ul, optarg, NULL, 0) < 0) + goto err_sched; + + current_gst_handler_index = (unsigned int)optval_ul; + + if (current_avdecc_stream_index >= 0) { + if(current_config_direction == AVTP_DIRECTION_TALKER) { + talker_streams[current_avdecc_stream_index].requested_type = STREAM_GST; + talker_streams[current_avdecc_stream_index].gst.index = current_gst_handler_index; + current_gst_config = &gstreamer_talker_pipelines[current_gst_handler_index].config; + current_gst_config->type = GST_TYPE_TALKER; + } else if (current_config_direction == AVTP_DIRECTION_LISTENER) { + listener_streams[current_avdecc_stream_index].requested_type = STREAM_GST; + listener_streams[current_avdecc_stream_index].gst.index = current_gst_handler_index; + current_gst_config = &gstreamer_listener_pipelines[current_gst_handler_index].config; + current_gst_config->type = GST_TYPE_LISTENER; + } else { + printf("[ERROR] No Stream direction specified for the avdecc stream. \n"); + goto err_sched; + } + } else if (current_static_stream_index >= 0) { + /* Only support static listeners*/ + listener_static_streams[current_static_stream_index].requested_type = STREAM_GST; + listener_static_streams[current_static_stream_index].gst.index = current_gst_handler_index; + current_gst_config = &gstreamer_listener_pipelines[current_gst_handler_index].config; + } else { + printf("[ERROR] No stream index specified. \n"); + goto err_sched; + } + current_gst_config->nstreams = 1; /* Single stream handler*/ + break; + case 'm': + input_index = strtok(optarg, ":"); + if (!input_index) { + printf("[ERROR] Bad multi handler specification.\n"); + goto err_sched; + } + + output_index = strtok(NULL, ":"); + if (!output_index) { + printf("[ERROR] Bad multi handler specification.\n"); + goto err_sched; + } + + if (h_strtoul(&optval_ul, input_index, NULL, 0) < 0) + goto err_sched; + + multi_handler_input_index = (unsigned int) optval_ul; + + if (h_strtoul(&optval_ul, output_index, NULL, 0) < 0) + goto err_sched; + + multi_handler_output_index = (unsigned int)optval_ul; + + if (current_avdecc_stream_index >= 0) { + if(current_config_direction == AVTP_DIRECTION_TALKER) { + talker_streams[current_avdecc_stream_index].requested_type = STREAM_MULTI_GST; + talker_streams[current_avdecc_stream_index].multi_gst.input_index = multi_handler_input_index; + talker_streams[current_avdecc_stream_index].multi_gst.output_index = multi_handler_output_index; + current_gst_config = &gst_talker_multi_handler[multi_handler_input_index].gst_pipeline.config; + current_gst_config->type |= GST_TYPE_MULTI_TALKER; + gst_talker_multi_handler[multi_handler_input_index].configured_streams++; + } else if (current_config_direction == GST_TYPE_LISTENER) { + printf("[ERROR] Avdecc listener gstreamer multi handler is not supported\n"); + goto err_sched; + } + } else if (current_static_stream_index >= 0) { + if(current_config_direction == AVTP_DIRECTION_LISTENER) { + listener_static_streams[current_static_stream_index].requested_type = STREAM_MULTI_GST; + listener_static_streams[current_static_stream_index].multi_gst.input_index = multi_handler_input_index; + listener_static_streams[current_static_stream_index].multi_gst.output_index = multi_handler_output_index; + current_gst_config = &gst_listener_multi_handler[multi_handler_output_index].gst_pipeline.config; + current_gst_config->listener.stream_ids_mappping[current_gst_config->nstreams].source_index = multi_handler_input_index; + current_gst_config->listener.stream_ids_mappping[current_gst_config->nstreams].sink_index = multi_handler_output_index; + } else if (current_config_direction == AVTP_DIRECTION_TALKER) { + printf("[ERROR] STATIC talker gstreamer multi handler is not supported\n"); + goto err_sched; + } + } else { + printf("[ERROR] No stream index specified. \n"); + goto err_sched; + } + current_gst_config->nstreams++; + break; + case 'c': + if (h_strtoul(&optval_ul, optarg, NULL, 0) < 0) + goto err_sched; + + current_crf_handler_index = (unsigned int)optval_ul; + + if (current_crf_handler_index >= MAX_CLOCK_DOMAIN) { + printf("[ERROR] CRF handler index above max (MAX_CLOCK_DOMAIN=%d)\n", MAX_CLOCK_DOMAIN); + goto err_sched; + } + + current_crf_config = crf_stream_get(current_crf_handler_index); + + if (!current_crf_config) { + printf("CRF stream not found for domain_index (%u)\n", current_crf_handler_index); + goto err_sched; + } + + if (current_avdecc_stream_index >= 0) { + if(current_config_direction == AVTP_DIRECTION_TALKER) { + talker_streams[current_avdecc_stream_index].requested_type = STREAM_CRF; + talker_streams[current_avdecc_stream_index].crf.index = current_crf_handler_index; + if (crf_configure(current_crf_handler_index, AVTP_DIRECTION_TALKER, 0) < 0) { + printf("[ERROR] Cannot configure CRF current config\n"); + goto err_sched; + } + + } else if (current_config_direction == AVTP_DIRECTION_LISTENER) { + listener_streams[current_avdecc_stream_index].requested_type = STREAM_CRF; + listener_streams[current_avdecc_stream_index].crf.index = current_crf_handler_index; + if (crf_configure(current_crf_handler_index, AVTP_DIRECTION_LISTENER, AVB_STREAM_FLAGS_MCR) < 0) { + printf("[ERROR] Cannot configure CRF current config\n"); + goto err_sched; + } + + } else { + printf("[ERROR] No Stream direction specified for the avdecc stream. \n"); + goto err_sched; + } + + } else if (current_static_stream_index >= 0) { + if (!current_crf_config->is_static_config){ + if(current_config_direction == AVTP_DIRECTION_TALKER) { + talker_static_streams[current_static_stream_index].requested_type = STREAM_CRF; + talker_static_streams[current_static_stream_index].crf.index = current_crf_handler_index; + if (crf_configure(current_crf_handler_index, AVTP_DIRECTION_TALKER, 0) < 0) { + printf("[ERROR] Cannot configure CRF current config\n"); + goto err_sched; + } + current_crf_config->is_static_config = true; + + } else if (current_config_direction == AVTP_DIRECTION_LISTENER) { + listener_static_streams[current_static_stream_index].requested_type = STREAM_CRF; + listener_static_streams[current_static_stream_index].crf.index = current_crf_handler_index; + if (crf_configure(current_crf_handler_index, AVTP_DIRECTION_LISTENER, AVB_STREAM_FLAGS_MCR) < 0) { + printf("[ERROR] Cannot configure CRF current config\n"); + goto err_sched; + } + current_crf_config->is_static_config = true; + + } else { + printf("[ERROR] No Stream direction specified for the static stream. \n"); + goto err_sched; + } + } else { + printf("[ERROR] Support for single static configuration per clock domain: crf handler %u previously configured with static stream\n", current_crf_handler_index); + goto err_sched; + } + } else { + printf("[ERROR] No stream index specified. \n"); + goto err_sched; + } + break; + case 'p': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + if (h_strtoull(&pts_offset, optarg, NULL, 0) < 0) + goto err_sched; + + if (pts_offset > MAX_PTS_OFFSET) + pts_offset = MAX_PTS_OFFSET; + + if (current_config_direction == AVTP_DIRECTION_TALKER) + current_gst_config->talker.preview_ts_offset = pts_offset; + else + current_gst_config->listener.pts_offset = pts_offset; + + break; + + case 'v': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->type |= GST_TYPE_VIDEO; + break; + + case 'a': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->type |= GST_TYPE_AUDIO; + break; + + case 'd': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + if (!strcasecmp(optarg, "lvds")) { + if ((current_gst_config->type & GST_TYPE_LISTENER) && (current_gst_config->listener.flags & GST_FLAG_BEV)) + current_gst_config->device = FB_LVDS_DEVICE_FILE; + else + current_gst_config->device = V4L2_LVDS_DEVICE_FILE; + } + else if (!strcasecmp(optarg, "hdmi")) { + if ((current_gst_config->type & GST_TYPE_LISTENER) && (current_gst_config->listener.flags & GST_FLAG_BEV)) + current_gst_config->device = FB_HDMI_DEVICE_FILE; + else + current_gst_config->device = V4L2_HDMI_DEVICE_FILE; + } + else { + usage(); + goto err_sched; + } + + break; + + case 'r': + if (!current_gst_config) { + printf("[Error]: No stream index is specified\n"); + goto err_sched; + } + if (!strcasecmp(optarg, "1080")) { + current_gst_config->width = 1920; + current_gst_config->height = 1080; + } else if (!strcasecmp(optarg, "720")) { + current_gst_config->width = 1280; + current_gst_config->height = 720; + } else if (!strcasecmp(optarg, "768")) { + current_gst_config->width = 1024; + current_gst_config->height = 768; + } else if (!strcasecmp(optarg, "480")) { + current_gst_config->width = 640; + current_gst_config->height = 480; + } else { + usage(); + goto err_sched; + } + + break; +#ifdef CFG_AVTP_1722A + case 'M': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->type = GST_TYPE_LISTENER; + current_gst_config->listener.flags |= GST_FLAG_CAMERA; + current_gst_config->type |= GST_TYPE_VIDEO; + current_gst_config->listener.camera_type = CAMERA_TYPE_SALSA; + break; + case 'B': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->type = GST_TYPE_LISTENER; + current_gst_config->listener.flags |= GST_FLAG_BEV; + current_gst_config->type |= GST_TYPE_VIDEO; + break; + case 'H': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->type = GST_TYPE_LISTENER; + current_gst_config->listener.flags |= GST_FLAG_CAMERA; + current_gst_config->type |= GST_TYPE_VIDEO; + if (!strcasecmp(optarg, "H264_1722_2016")) { + current_gst_config->listener.camera_type = CAMERA_TYPE_H264_1722_2016; + printf("H264 camera based on IEEE1722_2016 selected\n"); + } else if (!strcasecmp(optarg, "H264_1722_2013")) { + current_gst_config->listener.camera_type = CAMERA_TYPE_H264_1722_2013; + printf("H264 camera based on IEEE1722_2013 selected\n"); + } + break; + case 'I': + if (!current_gst_config && !current_crf_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + + if (h_strtoull(&optval_ull, optarg, NULL, 0) < 0) + goto err_sched; + + if (current_gst_config) { + current_gst_config->listener.stream_ids_mappping[current_gst_config->nstreams - 1].stream_id = htonll(optval_ull); + current_gst_config->listener.stream_ids_mappping[current_gst_config->nstreams - 1].source_index = multi_handler_input_index; + current_gst_config->listener.stream_ids_mappping[current_gst_config->nstreams - 1].sink_index = multi_handler_output_index; + + } else if (current_crf_config) { + crf_stream_id = htonll(optval_ull); + memcpy(¤t_crf_config->stream_params.stream_id, &crf_stream_id, sizeof(crf_stream_id)); + } + break; + case 'F': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->sync_render_to_clock = 0; + break; +#endif + + case 'f': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->talker.input_media_file_name = optarg; + rc = gst_build_media_file_list(current_gst_config); + + if (rc < 0) + goto err_sched; + printf("Talker mode : argument file name %s \n",optarg); + if (current_gst_config->talker.n_input_media_files > 1) { + current_gst_config->talker.input_media_file_index = current_gst_config->talker.n_input_media_files - 1; + printf("Found %d media files in %s, using %s as first file.\n", current_gst_config->talker.n_input_media_files, current_gst_config->talker.input_media_file_name, current_gst_config->talker.input_media_files[current_gst_config->talker.input_media_file_index]); + } + + break; + case 'D': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->listener.flags |= GST_FLAG_DEBUG; + current_gst_config->listener.debug_file_dump_location = optarg; + break; + case 'l': + if (!current_gst_config) { + printf("[Error]: No stream is specified\n"); + goto err_sched; + } + current_gst_config->talker.flags |= GST_FLAG_PREVIEW; + break; + case 'C': + case 'P': + if (current_gst_config || current_alsa_config) { + if (current_config_direction >= 0 && + ((current_config_direction == AVTP_DIRECTION_TALKER && option == 'P') + || (current_config_direction == AVTP_DIRECTION_LISTENER && option == 'C'))) { + printf("[Error]: Wrong ALSA device config\n"); + goto err_sched; + } + + if (current_gst_config) + current_gst_config->alsa_device = optarg; + else + current_alsa_config->alsa_device = optarg; + + } else { + /* If no specific media handler is specified, set for all avdecc talker or listener alsa handlers */ + if (option == 'C') + alsa_talkers_device_config_init(optarg); + else + alsa_listeners_device_config_init(optarg); + } + break; + case 'b': + binding_file_name = optarg; + break; + case 'h': + default: + usage(); + rc = -1; + goto err_sched; + } + } + + /*Apply default configurations for enabled listener streams*/ + for (i = 0; i < MAX_GSTREAMER_LISTENERS; i++) { + current_gst_config = &gstreamer_listener_pipelines[i].config; + + if (!(current_gst_config->type & GST_TYPE_LISTENER)) + continue; + + if (!current_gst_config->device) + current_gst_config->device = V4L2_LVDS_DEVICE_FILE; + + if (!current_gst_config->width) + set_default_device_width(current_gst_config->device, ¤t_gst_config->width); + + if(!current_gst_config->height) + set_default_device_height(current_gst_config->device, ¤t_gst_config->height); + + if (!(current_gst_config->type & GST_TYPE_VIDEO) && !(current_gst_config->type & GST_TYPE_AUDIO)) + current_gst_config->type |= GST_TYPE_VIDEO | GST_TYPE_AUDIO; + + if((current_gst_config->listener.flags & GST_FLAG_DEBUG) && (!current_gst_config->listener.debug_file_dump_location)) + current_gst_config->listener.debug_file_dump_location = DEFAULT_DUMP_LOCATION; + } + + /*Apply default configurations for enabled talker streams*/ + for (i = 0; i < MAX_GSTREAMER_TALKERS; i++) { + current_gst_config = &gstreamer_talker_pipelines[i].config; + + if (!(current_gst_config->type & GST_TYPE_TALKER)) + continue; + + if (!current_gst_config->device) + current_gst_config->device = V4L2_LVDS_DEVICE_FILE; + + if (!current_gst_config->width) + set_default_device_width(current_gst_config->device, ¤t_gst_config->width); + + if(!current_gst_config->height) + set_default_device_height(current_gst_config->device, ¤t_gst_config->height); + + if (!(current_gst_config->type & GST_TYPE_VIDEO) && !(current_gst_config->type & GST_TYPE_AUDIO)) + current_gst_config->type |= GST_TYPE_VIDEO | GST_TYPE_AUDIO; + + if(!current_gst_config->talker.input_media_file_name) + current_gst_config->talker.input_media_file_name = DEFAULT_MEDIA_FILE_NAME; + else /* set the file src location to first file */ + current_gst_config->talker.file_src_location = current_gst_config->talker.input_media_files[current_gst_config->talker.input_media_file_index]; + } + + /*Apply default configurations for enabled multi talker streams*/ + for (i = 0; i < MAX_GST_MULTI_HANDLERS; i++) { + current_gst_config = &gst_talker_multi_handler[i].gst_pipeline.config; + + if (!(current_gst_config->type & GST_TYPE_MULTI_TALKER)) + continue; + + if (!current_gst_config->device) + current_gst_config->device = V4L2_LVDS_DEVICE_FILE; + + if (!current_gst_config->width) + set_default_device_width(current_gst_config->device, ¤t_gst_config->width); + + if(!current_gst_config->height) + set_default_device_height(current_gst_config->device, ¤t_gst_config->height); + + if (!(current_gst_config->type & GST_TYPE_VIDEO) && !(current_gst_config->type & GST_TYPE_AUDIO)) + current_gst_config->type |= GST_TYPE_VIDEO | GST_TYPE_AUDIO; + + if(!current_gst_config->talker.input_media_file_name) + current_gst_config->talker.input_media_file_name = DEFAULT_MEDIA_FILE_NAME; + else /* set the file src location to first file */ + current_gst_config->talker.file_src_location = current_gst_config->talker.input_media_files[current_gst_config->talker.input_media_file_index]; + } + + /*Apply default configurations for enabled multi listener streams*/ + for (i = 0; i < MAX_GST_MULTI_HANDLERS; i++) { + current_gst_config = &gst_listener_multi_handler[i].gst_pipeline.config; + + if (!(current_gst_config->type & GST_TYPE_LISTENER)) + continue; + + if (!current_gst_config->device) { + if (current_gst_config->listener.flags & GST_FLAG_BEV) + current_gst_config->device = FB_LVDS_DEVICE_FILE; + else + current_gst_config->device = V4L2_LVDS_DEVICE_FILE; + } + + if (!current_gst_config->width) + set_default_device_width(current_gst_config->device, ¤t_gst_config->width); + + if(!current_gst_config->height) + set_default_device_height(current_gst_config->device, ¤t_gst_config->height); + + if((current_gst_config->listener.flags & GST_FLAG_DEBUG) && (!current_gst_config->listener.debug_file_dump_location)) + current_gst_config->listener.debug_file_dump_location = DEFAULT_DUMP_LOCATION; + } + + /* + * set signals handler + */ + action.sa_handler = signal_terminate_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGTERM, &action, NULL) < 0) /* Termination signal */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGQUIT, &action, NULL) < 0) /* Quit from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGINT, &action, NULL) < 0) /* Interrupt from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + + rc = clock_init(0); + if (rc < 0) + goto err_clock; + + + rc = aar_log_init(NULL); + if (rc < 0) + goto err_log; + + rc = avbstream_init(); + if (rc < 0) + goto err_stream; + + avb_handle = avbstream_get_avb_handle(); + + rc = msrp_init(avb_handle); + if (rc < 0) + goto err_msrp; + + rc = clock_domain_init(avb_handle, &clock_domain_rx_fd); + if (rc < 0) + goto err_clock_domain_init; + + ctrl_poll[2].fd = clock_domain_rx_fd; + ctrl_poll[2].events = POLLIN; + + clock_domain_get_status(AVB_CLOCK_DOMAIN_0); + + /* + * Setup static CRF and clock domain + */ + rc = static_crf_streams_init(); + if (rc < 0) + goto err_static_crf_streams_init; + + rc = thread_init(); + if (rc < 0) + goto err_thread; + + stats_fd = stats_thread_init(&stats_thread_slot); + if (stats_fd < 0) { + rc = -1; + goto err_stats_thread; + } + + rc = gst_bus_messages_timer_thread_init(&gstreamer_bus_monitor); + if (rc < 0) + goto err_gst_bus_timer_thread; + + /* Do not start monitoring until at least one pipeline is launched*/ + thread_slot_set_events(gstreamer_bus_monitor.thread_slot, 0, EPOLLIN); + + if (static_streams_init() < 0) + goto err_static; + + if (avdecc_multi_talker_streams_init() < 0) + goto err_avdecc_multi; + + /* + * listen to avdecc events to get stream parameters + */ + + rc = avb_control_open(avb_handle, &ctrl_h, AVB_CTRL_AVDECC_MEDIA_STACK); + if (rc != AVB_SUCCESS) { + printf("avb_control_open(AVB_CTRL_AVDECC_MEDIA_STACK) failed: %s\n", avb_strerror(rc)); + printf("WARNING: will not listen for AVDECC notifications messages\n"); + rc = 0; + + ctrl_poll[0].fd = -1; + ctrl_poll[0].events = POLLIN; + } else { + + ctrl_rx_fd = avb_control_rx_fd(ctrl_h); + ctrl_poll[0].fd = ctrl_rx_fd; + ctrl_poll[0].events = POLLIN; + } + + rc = avb_control_open(avb_handle, &controlled.handle, AVB_CTRL_AVDECC_CONTROLLED); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() for AVB_CTRL_AVDECC_CONTROLLED channel failed: %s\n", avb_strerror(rc)); + printf("WARNING: will not listen for AVDECC CONTROLL messages\n"); + rc = 0; + + ctrl_poll[1].fd = -1; + ctrl_poll[1].events = POLLIN; + } else { + printf("avb_control_open() for AVB_CTRL_AVDECC_CONTROLLED channel success : %p\n", controlled.handle); + + ctrl_poll[1].events = POLLIN; + ctrl_poll[1].fd = -1; + + if (controlled_slot_init(&controlled_thread_slot, &controlled) < 0) + goto exit; + } + + /* If we have a filename for non-volatile binding params parse it and init the avdecc stack with its parameters. */ + if (binding_file_name) { + if (avdecc_nvm_bindings_init(ctrl_h, binding_file_name) < 0) { + printf("failed to parse binding file %s\n", binding_file_name); + rc = -1; + goto exit; + } + } + + while (1) { + rc = poll(ctrl_poll, NUM_CONTROL_FDS, -1); + if ((errno == EINTR) && signal_terminate) { + printf("processing terminate signal\n"); + rc = -1; + goto exit; + } + if (rc == -1) { + printf("poll() failed errno(%d,%s)\n", errno, strerror(errno)); + break; + } + + if (ctrl_poll[0].revents & POLLIN) + handle_avdecc_event(ctrl_h, binding_file_name); + + if (ctrl_poll[2].revents & POLLIN) + handle_clock_domain_event(); + } + +exit: + if (ctrl_h) + avb_control_close(ctrl_h); + + controlled_slot_exit(controlled_thread_slot); + + if (controlled.handle) + avb_control_close(controlled.handle); + +err_avdecc_multi: +err_static: + gst_bus_messages_timer_thread_exit(&gstreamer_bus_monitor); +err_gst_bus_timer_thread: + stats_thread_exit(stats_fd, stats_thread_slot); + +err_stats_thread: + thread_exit(); + + avdecc_streams_disconnect(); + + static_streams_exit(); + avdecc_multi_talker_streams_exit(); + +err_thread: + static_crf_streams_exit(); + +err_static_crf_streams_init: + clock_domain_exit(); + +err_clock_domain_init: + msrp_exit(); + +err_msrp: + avbstream_exit(); + +err_stream: + aar_log_exit(); + +err_log: + clock_exit(0); + +err_clock: + gstreamer_config_exit(); + +err_sched: + return rc; +} diff --git a/apps/linux/genavb-media-app/multi_frame_sync.c b/apps/linux/genavb-media-app/multi_frame_sync.c new file mode 100644 index 0000000..ba05862 --- /dev/null +++ b/apps/linux/genavb-media-app/multi_frame_sync.c @@ -0,0 +1,263 @@ +/* + * Copyright 2017-2018, 2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "multi_frame_sync.h" +#include "../common/thread.h" +#include "../common/time.h" + +struct gstreamer_sync { + GstAppSink *appsink; + GstClockTime basetime; + int event_fd; + int index; + thr_thread_slot_t *thread_slot; +}; + +struct multi_frame_sync_handle { + unsigned int nstreams; + struct gstreamer_sync sync[MFS_MAX_STREAMS]; + GstBuffer *last_buffer[MFS_MAX_STREAMS]; + GstClockTime last_ts[MFS_MAX_STREAMS]; + GstClockTime last_cluster_time; + unsigned long long first_wakeup; + unsigned int ready; + int (*push_buffers)(void *priv, GstBuffer **buffers); + void *priv; + +}; + +static struct multi_frame_sync_handle mfs; + + + +#define MFS_FRAME_PERIOD 35000000 // 30 fps + wake-up latency margin +#define MFS_VALID_TIME 400000000 // How long to keep displaying a stale frame before blanking it +#define CVF_TS_VALID_WINDOW 500000000 // 500ms + +static void multi_frame_sync(unsigned long long current_wall_time) +{ + unsigned long long current_ts, ts_diff_current, ts_diff; + int ts_index_sorted[MFS_MAX_STREAMS]; + unsigned int i, j, dummy, first_active, first_valid; + const unsigned int nstreams = mfs.nstreams; + + /* bubble sort */ + for (i = 0; i < nstreams; i++) + ts_index_sorted[i] = i; + + for (i = 0; i < nstreams - 1; i++) { + for (j = i + 1; j < nstreams; j++) { + if (mfs.last_ts[ts_index_sorted[i]] > mfs.last_ts[ts_index_sorted[j]]) { + dummy = ts_index_sorted[i]; + ts_index_sorted[i] = ts_index_sorted[j]; + ts_index_sorted[j] = dummy; + } + } + } + + current_ts = mfs.last_ts[ts_index_sorted[nstreams - 1]]; + /* filter out inactive streams (last_frame too old) */ + i = 0; + while ((i < nstreams - 1) && ((mfs.last_ts[ts_index_sorted[i]] + MFS_VALID_TIME) < current_ts)) { + i++; + } + first_valid = i; + while ((i < nstreams - 1) && ((mfs.last_ts[ts_index_sorted[i]] + MFS_FRAME_PERIOD) < current_ts)) { + i++; + } + first_active = i; + + ts_diff_current = current_ts - mfs.last_ts[ts_index_sorted[first_active]]; + ts_diff = ts_diff_current; + + while (i < nstreams - 1) { + ts_diff = mfs.last_ts[ts_index_sorted[i]] + MFS_FRAME_PERIOD - mfs.last_ts[ts_index_sorted[i + 1]]; + /* If mfs.last_ts[ts_index_sorted[i]] is not updated, the above could become negative, so ts_diff(unsigned) will wrap + * and the test below will still work. + */ + if (ts_diff < ts_diff_current) + break; + i++; + } + if ((ts_diff >= ts_diff_current) && ((current_ts - mfs.last_cluster_time) > MFS_FRAME_PERIOD / 2)) { + GstBuffer *buffers[MFS_MAX_STREAMS] = {NULL}; + long long latency = current_ts - current_wall_time; + if ((latency < -CVF_TS_VALID_WINDOW) || (latency > CVF_TS_VALID_WINDOW)) + current_ts = current_wall_time; + mfs.last_cluster_time = current_ts; + + // Pass the frames + for (i = 0; i < nstreams; i++) { + unsigned int idx = ts_index_sorted[i]; + + if ((i >= first_valid) && (mfs.last_buffer[idx] != NULL)) { + GST_BUFFER_PTS(mfs.last_buffer[idx]) = current_ts; + buffers[idx] = mfs.last_buffer[idx]; + } else + buffers[idx] = NULL; + } + + mfs.push_buffers(mfs.priv, buffers); + } +} + +static int multi_frame_sync_handler(void *data, unsigned int events) +{ + GstSample *sample; + GstBuffer *buffer; + GstClockTime ts; + int read_samples; + unsigned long long n_samples; + uint64_t current_wall_time; + const unsigned int nstreams = mfs.nstreams; + unsigned int index; + + if (gettime_ns(¤t_wall_time) != 0) + goto err; + + for (index = 0; index < nstreams; index++) { + n_samples = 0; + + read_samples = read(mfs.sync[index].event_fd, &n_samples, 8); + if (read_samples != 8) { + if (read_samples < 0) + printf("%s: read error, index=%d, error:%s\n", __func__, index, strerror(errno)); + else + printf("%s: read incomplete, index=%d, read_samples=%d expected 8\n", __func__, index, read_samples); + } + + if (n_samples > 1) { + printf("stream(%d) Dropping %llu frames\n", index, n_samples - 1); //FIXME Add some stats instead + } + + if (n_samples > 0) { + // VPUDEC seems very sensitive to buffer starvation, so make sure to free up buffers as quickly as possible to avoid a pipeline error + sample = gst_base_sink_get_last_sample(GST_BASE_SINK(mfs.sync[index].appsink)); + buffer = gst_sample_get_buffer(sample); + if (buffer == mfs.last_buffer[index]) { + gst_sample_unref(sample); + continue; + } + gst_buffer_ref(buffer); + gst_sample_unref(sample); + + if (mfs.first_wakeup == 0) + mfs.first_wakeup = current_wall_time; + + if (!GST_CLOCK_TIME_IS_VALID(mfs.sync[index].basetime)) { + mfs.sync[index].basetime = gst_element_get_base_time(GST_ELEMENT(mfs.sync[index].appsink)); + } + ts = GST_BUFFER_PTS(buffer); + ts += mfs.sync[index].basetime; + + + if (mfs.last_ts[index] != 0) { + gst_buffer_unref(mfs.last_buffer[index]); + mfs.ready = 1; + } + + mfs.last_buffer[index] = buffer; + mfs.last_ts[index] = ts; + } + } + + if ((current_wall_time - mfs.first_wakeup) > MFS_FRAME_PERIOD) + mfs.ready = 1; + + if (mfs.ready) + multi_frame_sync(current_wall_time); + + return 0; + +err: + return -1; +} + +static GstFlowReturn multi_frame_sync_gst_new_sample_handler(GstAppSink *sink, gpointer data) +{ + struct gstreamer_sync *sync = data; + unsigned long long val = 1; + int rc = 0; + + rc = write(sync->event_fd, &val, 8); + if (rc != 8) { + if (rc < 0) + printf("write() failed: %s\n", strerror(errno)); + else + printf("write() incomplete, returned %d\n", rc); + + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; +} + + +void mfs_init(unsigned int nstreams, int (*push_buffers)(void *priv, GstBuffer **buffers), void *priv) +{ + int i; + + memset(mfs.last_buffer, 0, sizeof(GstBuffer *) * MFS_MAX_STREAMS); + memset(mfs.last_ts, 0, sizeof(GstClockTime) * MFS_MAX_STREAMS); + mfs.last_cluster_time = GST_CLOCK_TIME_NONE; + mfs.first_wakeup = 0; + mfs.ready = 0; + + for (i = 0; i < MFS_MAX_STREAMS; i++) { + mfs.sync[i].basetime = GST_CLOCK_TIME_NONE; + } + mfs.push_buffers = push_buffers; + mfs.priv = priv; + mfs.nstreams = nstreams; +} + +int mfs_add_sync(unsigned int i, GstAppSink *appsink) +{ + GstAppSinkCallbacks callbacks = { NULL }; + + mfs.sync[i].index = i; + + mfs.sync[i].appsink = appsink; + + mfs.sync[i].event_fd = eventfd(0, EFD_NONBLOCK); + if (mfs.sync[i].event_fd < 0) { + printf("eventfd failed for mfs.sync %d\n", i); + goto error_event; + } + + if (thread_slot_add(THR_CAP_GSTREAMER_SYNC, mfs.sync[i].event_fd, EPOLLIN, &mfs.sync[i], multi_frame_sync_handler, NULL, 0, &mfs.sync[i].thread_slot) < 0) { + printf("thread_slot_add failed for mfs.sync %d\n", i); + goto error_thread_sync; + } + + callbacks.new_sample = multi_frame_sync_gst_new_sample_handler; + gst_app_sink_set_callbacks(mfs.sync[i].appsink, &callbacks, &mfs.sync[i], NULL); + + return 0; + +error_thread_sync: + close(mfs.sync[i].event_fd); + +error_event: + return -1; + +} + + +void mfs_remove_sync(unsigned int i) +{ + thread_slot_free(mfs.sync[i].thread_slot); + close(mfs.sync[i].event_fd); +} diff --git a/apps/linux/genavb-media-app/multi_frame_sync.h b/apps/linux/genavb-media-app/multi_frame_sync.h new file mode 100644 index 0000000..3382fda --- /dev/null +++ b/apps/linux/genavb-media-app/multi_frame_sync.h @@ -0,0 +1,19 @@ +/* + * Copyright 2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _MULTI_FRAME_SYNC_H_ +#define _MULTI_FRAME_SYNC_H_ + +#include +#include + +#define MFS_MAX_STREAMS 4 + +void mfs_init(unsigned int nstreams, int (*push_buffers)(void *priv, GstBuffer **buffers), void *priv); +int mfs_add_sync(unsigned int i, GstAppSink *appsink); +void mfs_remove_sync(unsigned int i); + +#endif /* _MULTI_FRAME_SYNC_H_ */ diff --git a/apps/linux/genavb-media-app/salsa_camera.c b/apps/linux/genavb-media-app/salsa_camera.c new file mode 100644 index 0000000..b60ec8e --- /dev/null +++ b/apps/linux/genavb-media-app/salsa_camera.c @@ -0,0 +1,622 @@ +/* + * Copyright 2017-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include + +#include "salsa_camera.h" +#include "gstreamer_stream.h" +#include "multi_frame_sync.h" +#include "../common/avb_stream.h" +#include "../common/thread.h" +#include "../common/gst_pipeline_definitions.h" + + +#ifdef CFG_AVTP_1722A + +static struct gstreamer_pipeline salsa_decode_pipeline[MAX_CAMERA]; +static struct gstreamer_stream salsa_camera_stream[MAX_CAMERA]; + + + +struct avb_stream_params salsa_camera_stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_CVF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_CLOCK_DOMAIN_0, + .flags = 0, + .format.u.s = { + .v = 0, + .subtype = AVTP_SUBTYPE_CVF, + .subtype_u.cvf = { + .format = CVF_FORMAT_RFC, + .subtype = CVF_FORMAT_SUBTYPE_MJPEG, + .format_u.mjpeg = { + .p = CVF_MJPEG_P_PROGRESSIVE, + .type = CVF_MJPEG_TYPE_YUV420, + .width = 160, + .height = 100, + }, + }, + }, + .port = 0, + .stream_id = { 0x00, 0x00, 0x00, 0x04, 0x9f, 0x00, 0x4a, 0x50 }, + .dst_mac = { 0x91, 0xe0, 0xf0, 0x00, 0x06, 0x00 }, +}; + + + +int salsa_camera_connect(struct gstreamer_stream *salsa_stream) +{ + struct avb_handle *avb_h = avbstream_get_avb_handle(); + int rc; + + select_gst_listener_pipeline(salsa_stream->pipe_source.gst_pipeline, &salsa_camera_stream_params.format); + gst_pipeline_configure_pts_offset(salsa_stream->pipe_source.gst_pipeline, &salsa_camera_stream_params.format); + dump_gst_config(salsa_stream->pipe_source.gst_pipeline); + + + if (gst_start_pipeline(salsa_stream->pipe_source.gst_pipeline, GST_PRIORITY, GST_DIRECTION_LISTENER) < 0) { + printf("gst_start_pipeline() failed\n"); + rc = -1; + goto error_gst_pipeline; + } + + rc = gst_cvf_mjpeg_warm_up_pipeline(salsa_stream->pipe_source.gst_pipeline, 1); + + salsa_stream->pipe_source.source = salsa_stream->pipe_source.gst_pipeline->source[0].source; + salsa_stream->source_index = 0; + + apply_stream_params(salsa_stream, &salsa_camera_stream_params); + + rc = avb_stream_create(avb_h, &salsa_stream->stream_h, &salsa_stream->params, &salsa_stream->batch_size, salsa_stream->flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", salsa_stream->batch_size); + + /* + * retrieve the file descriptor associated to the stream + */ + salsa_stream->stream_fd = avb_stream_fd(salsa_stream->stream_h); + if (salsa_stream->stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(salsa_stream->stream_fd)); + rc = -1; + goto error_stream_fd; + } + + stream_init_stats(salsa_stream); + + if (thread_slot_add(THR_CAP_STREAM_LISTENER, salsa_stream->stream_fd, EPOLLIN, salsa_stream, (int (*)(void *, unsigned int))salsa_stream->listener_gst_handler, NULL, 0, (thr_thread_slot_t **)&salsa_stream->thread) < 0) + goto error_thread; + + salsa_stream->created = 1; + + + return 0; + + +error_thread: +error_stream_fd: + avb_stream_destroy(salsa_stream->stream_h); + +error_stream_create: + gst_stop_pipeline(salsa_stream->pipe_source.gst_pipeline); + +error_gst_pipeline: + return -1; +} + +void salsa_camera_disconnect(struct gstreamer_stream *salsa_stream) +{ + if (salsa_stream->created) { + thread_slot_free(salsa_stream->thread); + + avb_stream_destroy(salsa_stream->stream_h); + + gst_stop_pipeline(salsa_stream->pipe_source.gst_pipeline); + + salsa_stream->created = 0; + } +} + +void salsa_camera_init(void) +{ + int i; + + for (i = 0; i < MAX_CAMERA; i++) + gstreamer_pipeline_init(&salsa_decode_pipeline[i]); +} + +static int mfs_split_screen_push_buffers(void *priv, GstBuffer **buffers) +{ + int index, rc = 0; + struct gstreamer_pipeline *split_screen_pipeline = (struct gstreamer_pipeline *)priv; + long long offset; + + if (!GST_CLOCK_TIME_IS_VALID(split_screen_pipeline->basetime)) + split_screen_pipeline->basetime = gst_element_get_base_time(GST_ELEMENT(split_screen_pipeline->pipeline)); + + offset = split_screen_pipeline->listener.pts_offset + split_screen_pipeline->listener.local_pts_offset - split_screen_pipeline->basetime; + + for (index = 0; index < split_screen_pipeline->config.nstreams; index++) { + GstBuffer *buffer = buffers[index]; + if (buffer) { + gst_buffer_ref(buffer); + GST_BUFFER_PTS(buffer) += offset; + rc = gst_app_src_push_buffer(split_screen_pipeline->source[index].source, buffer); + if (rc != GST_FLOW_OK) { + if (rc == GST_FLOW_FLUSHING) + printf("Pipeline not in PAUSED or PLAYING state\n"); + else + printf("End-of-Stream occurred\n"); + } + } + } + + return 0; +} + + +static void *svm_library; + +static int (*svm_render)(unsigned char * buffer_cam, int index_cam); +static void (*svm_eye_position)(float irx, float iry, float ipx, float ipy, float ipz); +static void (*svm_render_start)(void); +static void (*svm_render_end)(void); +static int (*svm_init)(const char *settings_filename); +static int (*svm_close)(void); +static int (*svm_open)(unsigned int fb_index); + +GstMapInfo surround_prev_info[MAX_CAMERA]; +GstBuffer *surround_prev_buffer[MAX_CAMERA]; +unsigned int surround_ready = 0; +float surround_rx = 0.0f, surround_ry = -0.3f; +float surround_dry = -0.001; + +static int mfs_surround_push_buffers(void *priv, GstBuffer **buffers) +{ + int index, rc = GST_FLOW_OK; + unsigned char * current_data = NULL; + + if (!surround_ready) { + svm_init("/home/media/surround_view/settings.xml"); + surround_ready = 1; + } + + surround_rx += 0.005; + if (surround_rx > 3.1416) + surround_rx -= 2*3.1416; + + if (surround_ry < -1.3) + surround_dry = 0.001; + if (surround_ry > -0.3) + surround_dry = -0.001; + surround_ry += surround_dry; + + svm_eye_position(surround_rx, surround_ry, 0.0, 0.0, -3.0); + svm_render_start(); + for (index = 0; index < MAX_CAMERA; index++) { + GstBuffer *buffer = buffers[index]; + + if (buffer) { + if (buffer != surround_prev_buffer[index]) { + if (surround_prev_buffer[index]) { + if (surround_prev_info[index].data) { + gst_buffer_unmap(surround_prev_buffer[index], &surround_prev_info[index]); + surround_prev_info[index].data = NULL; + } + gst_buffer_unref(surround_prev_buffer[index]); + } + gst_buffer_ref(buffer); + surround_prev_buffer[index] = buffer; + + if (gst_buffer_map(buffer, &surround_prev_info[index], GST_MAP_READ)) + current_data = surround_prev_info[index].data; + } else + current_data = surround_prev_info[index].data; + + if (!current_data) { + printf("Couldn't get data buffer%d\n", index); + rc = -1; + goto exit; + } + + svm_render(current_data, index); //stitching function + } + } + + svm_render_end(); + +exit: + return rc; +} + + +int surround_init(struct gstreamer_pipeline_config *gst_config) +{ + int rc, fb_index; + + svm_library = dlopen("/home/media/surround_view/libsvm.so", RTLD_NOW); + if (!svm_library) { + printf("Could not open svm library: %s\n", dlerror()); + return -1; + } + + svm_render = dlsym(svm_library, "svm_render"); + if (!svm_render) + goto symbol_err; + + svm_eye_position = dlsym(svm_library, "svm_eye_position"); + if (!svm_eye_position) + goto symbol_err; + + svm_render_start = dlsym(svm_library, "svm_render_start"); + if (!svm_render_start) + goto symbol_err; + + svm_render_end = dlsym(svm_library, "svm_render_end"); + if (!svm_render_end) + goto symbol_err; + + svm_init = dlsym(svm_library, "svm_init"); + if (!svm_init) + goto symbol_err; + + svm_close = dlsym(svm_library, "svm_close"); + if (!svm_close) + goto symbol_err; + + svm_open = dlsym(svm_library, "svm_open"); + if (!svm_open) + goto symbol_err; + + rc = sscanf(gst_config->device, "/dev/fb%d", &fb_index); + if (rc != 1) { + printf("Invalid fb display device %s\n", gst_config->device); + goto err; + } + + svm_open(fb_index); + + memset(surround_prev_info, 0, sizeof(GstMapInfo) * MAX_CAMERA); + memset(surround_prev_buffer, 0, sizeof(GstBuffer *) * MAX_CAMERA); + + return 0; + +symbol_err: + printf("Missing symbol in avbsvm library: %s\n", dlerror()); +err: + dlclose(svm_library); + return -1; + +} + +int surround_exit(void) +{ + int index; + + svm_close(); + + for (index = 0; index < MAX_CAMERA; index++) { + if (surround_prev_buffer[index]) { + if (surround_prev_info[index].data) { + gst_buffer_unmap(surround_prev_buffer[index], &surround_prev_info[index]); + surround_prev_info[index].data = NULL; + } + + gst_buffer_unref(surround_prev_buffer[index]); + } + } + + dlclose(svm_library); + + return 0; +} + + + + + + + + + + +/* Warm up the overlay pipeline */ +static void multi_salsa_camera_warmup(unsigned int nstreams, struct gstreamer_pipeline *split_screen_pipeline) +{ + GstSample *sample; + unsigned int i; + + for (i = 0; i < nstreams; i++) { + gst_cvf_mjpeg_warm_up_pipeline(&salsa_decode_pipeline[i], 1); + + sample = gst_app_sink_pull_sample(GST_APP_SINK(salsa_decode_pipeline[i].sink[0].sink)); + if (split_screen_pipeline) + gst_app_src_push_sample(split_screen_pipeline->source[i].source, sample); + gst_sample_unref(sample); + } +} + + +static int multi_salsa_decoding_start(unsigned int nstreams) +{ + unsigned int i; + int rc = 0; + + /* Setup the decoding pipelines */ + for (i = 0; i < nstreams; i++) { + select_gst_listener_pipeline(&salsa_decode_pipeline[i], &salsa_camera_stream_params.format); + salsa_decode_pipeline[i].config.device = malloc(48); + memcpy(salsa_decode_pipeline[i].config.device, "/tmp/cam1-%d.jpg", sizeof("/tmp/cam1-%d.jpg")); + salsa_decode_pipeline[i].config.device[8] = '0' + i + 1; + gst_pipeline_configure_pts_offset(&salsa_decode_pipeline[i], &salsa_camera_stream_params.format); + + if (gst_start_pipeline(&salsa_decode_pipeline[i], GST_PRIORITY, GST_DIRECTION_LISTENER) < 0) { + printf("gst_play_pipeline() failed for salsa_decode_pipeline %d \n", i); + rc = -1; + goto err_start; + } + } + + return rc; + +err_start: + while (i > 0) { + i--; + gst_stop_pipeline(&salsa_decode_pipeline[i]); + } + + return rc; +} + +static void multi_salsa_decoding_stop(unsigned int nstreams) +{ + unsigned int i; + + /* Stop the decoding pipelines */ + for (i = 0; i < nstreams; i++) { + gst_stop_pipeline(&salsa_decode_pipeline[i]); + } +} + +static int multi_salsa_decoding_connect_avb(struct gstreamer_pipeline_config *gst_config) +{ + struct avb_handle *avb_h = avbstream_get_avb_handle(); + unsigned int i; + int rc; + struct gstreamer_stream *salsa_stream; + unsigned int nstreams = gst_config->nstreams; + + /* Prepare the streams */ + for (i = 0; i < nstreams; i++) { + salsa_stream = &salsa_camera_stream[i]; + salsa_stream->pipe_source.gst_pipeline = &salsa_decode_pipeline[i]; + salsa_stream->pipe_source.source = salsa_decode_pipeline[i].source[0].source; + salsa_stream->source_index = i; + + stream_init_stats(salsa_stream); + + if(!(gst_config->listener.stream_ids_mappping[i].stream_id)) { + + switch(gst_config->listener.stream_ids_mappping[i].source_index) { + case 0: + salsa_camera_stream_params.stream_id[7] = 0x50; + break; + case 1: + salsa_camera_stream_params.stream_id[7] = 0x60; + break; + case 2: + salsa_camera_stream_params.stream_id[7] = 0x70; + break; + case 3: + salsa_camera_stream_params.stream_id[7] = 0x80; + break; + default: + break; + } + } + apply_stream_params(salsa_stream, &salsa_camera_stream_params); + + if(gst_config->listener.stream_ids_mappping[i].stream_id) + memcpy(&salsa_stream->params.stream_id, &gst_config->listener.stream_ids_mappping[i].stream_id, sizeof(gst_config->listener.stream_ids_mappping[i].stream_id)); + + print_stream_id(salsa_stream->params.stream_id); + } + + /* Create the AVB streams */ + for (i = 0; i < nstreams; i++) { + salsa_stream = &salsa_camera_stream[i]; + + rc = avb_stream_create(avb_h, &salsa_stream->stream_h, &salsa_stream->params, &salsa_stream->batch_size, salsa_stream->flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", salsa_stream->batch_size); + + /* + * retrieve the file descriptor associated to the stream + */ + salsa_stream->stream_fd = avb_stream_fd(salsa_stream->stream_h); + if (salsa_stream->stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(salsa_stream->stream_fd)); + rc = -1; + goto error_stream_fd; + } + } + + /* Add the data handlers for the AVB streams */ + for (i = 0; i < nstreams; i++) { + salsa_stream = &salsa_camera_stream[i]; + + if (thread_slot_add(THR_CAP_STREAM_LISTENER, salsa_stream->stream_fd, EPOLLIN, salsa_stream, (int (*)(void *, unsigned int))salsa_stream->listener_gst_handler, NULL, 0, (thr_thread_slot_t **)&salsa_stream->thread) < 0) { + printf("thread_slot_add() failed\n"); + goto error_thread; + } + salsa_stream->created = 1; + } + + return 0; + +error_thread: + while (i > 0) { + i--; + salsa_stream = &salsa_camera_stream[i]; + thread_slot_free(salsa_stream->thread); + salsa_stream->created = 0; + } + + i = nstreams; + +error_stream_create: + while (i > 0) { + i--; +error_stream_fd: + salsa_stream = &salsa_camera_stream[i]; + avb_stream_destroy(salsa_stream->stream_h); + } + + return -1; +} + +static int multi_salsa_decoding_connect(struct gstreamer_pipeline_config *gst_config) +{ + unsigned int i; + unsigned int src_index; + unsigned int nstreams = gst_config->nstreams; + + /* Connect the output of the decoding pipelines to our handling functions */ + for (i = 0; i < nstreams; i++) { + src_index = gst_config->listener.stream_ids_mappping[i].source_index; + if (mfs_add_sync(i, salsa_decode_pipeline[src_index].sink[0].sink) < 0) { + printf("mfs_add_sync failed for mfs.sync %d\n", i); + goto error_mfs_add_sync; + } + } + + if (multi_salsa_decoding_connect_avb(gst_config) < 0) + goto error_mfs_add_sync; + + + return 0; + +error_mfs_add_sync: + while (i > 0) { + i--; + mfs_remove_sync(i); + } + + return -1; +} + +static void multi_salsa_decoding_disconnect(unsigned int nstreams) +{ + unsigned int i; + + for (i = 0; i < nstreams; i++) { + struct gstreamer_stream *salsa_stream = &salsa_camera_stream[i]; + + if (salsa_stream->created) { + thread_slot_free(salsa_stream->thread); + mfs_remove_sync(i); + + avb_stream_destroy(salsa_stream->stream_h); + + salsa_stream->created = 0; + } + } + +} + + +void multi_salsa_camera_disconnect(unsigned int nstreams) +{ + multi_salsa_decoding_disconnect(nstreams); + + multi_salsa_decoding_stop(nstreams); +} + +int multi_salsa_split_screen_start(unsigned int nstreams, struct gstreamer_pipeline *pipeline) +{ + if (gstreamer_split_screen_start(pipeline) < 0) { + printf("gstreamer_split_screen_start() failed\n"); + goto error_gst_split_screen; + } + + mfs_init(pipeline->config.nstreams, mfs_split_screen_push_buffers, pipeline); + + + if (multi_salsa_decoding_start(nstreams) < 0) + goto error_decoding_start; + + multi_salsa_camera_warmup(nstreams, pipeline); + + if (multi_salsa_decoding_connect(&pipeline->config) < 0) + goto error_decoding_connect; + + return 0; + +error_decoding_connect: + multi_salsa_decoding_stop(nstreams); + +error_decoding_start: +error_gst_split_screen: + return -1; +} + +int multi_salsa_surround_start(struct gstreamer_pipeline_config *gst_config) +{ + if (surround_init(gst_config) < 0) { + printf("Couldn't initialize Surround view\n"); + goto error_surround; + } + + mfs_init(MAX_CAMERA, mfs_surround_push_buffers, NULL); //FIXME create surround struct to hold all surround vars + + if (multi_salsa_decoding_start(gst_config->nstreams) < 0) + goto error_decoding_start; + + multi_salsa_camera_warmup(gst_config->nstreams, NULL); + + if (multi_salsa_decoding_connect(gst_config) < 0) + goto error_decoding_connect; + + return 0; + +error_decoding_connect: + multi_salsa_decoding_stop(gst_config->nstreams); + +error_decoding_start: +error_surround: + return -1; +} + + + + + + + + + + + + + + + + + + +#endif diff --git a/apps/linux/genavb-media-app/salsa_camera.h b/apps/linux/genavb-media-app/salsa_camera.h new file mode 100644 index 0000000..012f396 --- /dev/null +++ b/apps/linux/genavb-media-app/salsa_camera.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _SALSA_CAMERA_H_ +#define _SALSA_CAMERA_H_ + +#ifdef CFG_AVTP_1722A +#include "multi_frame_sync.h" +#include "../common/gstreamer.h" +#include "../common/gst_pipeline_definitions.h" +#include "../common/gstreamer_single.h" + +#define MAX_CAMERA (MFS_MAX_STREAMS) + +void salsa_camera_init(void); +int salsa_camera_connect(struct gstreamer_stream *salsa_stream); +void salsa_camera_disconnect(struct gstreamer_stream *salsa_stream); + +int multi_salsa_split_screen_start(unsigned int nstreams, struct gstreamer_pipeline *pipeline); +int multi_salsa_surround_start(struct gstreamer_pipeline_config *gst_config); +void multi_salsa_camera_disconnect(unsigned int nstreams); + +int surround_exit(void); + +#endif + + +#endif /* _SALSA_CAMERA_H_ */ diff --git a/apps/linux/genavb-media-app/thread_config.c b/apps/linux/genavb-media-app/thread_config.c new file mode 100644 index 0000000..a60b8c5 --- /dev/null +++ b/apps/linux/genavb-media-app/thread_config.c @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "../common/thread_config.h" +#include "../common/gstreamer.h" + +/* Slots are allocated by thread array index order */ +thr_thread_t g_thread_array[MAX_THREADS] = { + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 60, + .max_slots = 1, + .thread_capabilities = THR_CAP_STREAM_TALKER | THR_CAP_STREAM_LISTENER | THR_CAP_ALSA | THR_CAP_STREAM_AUDIO, + .slots = {{0}}, + }, + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 60, + .max_slots = 1, + .thread_capabilities = THR_CAP_STREAM_TALKER | THR_CAP_STREAM_LISTENER | THR_CAP_ALSA | THR_CAP_STREAM_AUDIO, + .slots = {{0}}, + }, + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 60, + .max_slots = 1, + .thread_capabilities = THR_CAP_STREAM_TALKER | THR_CAP_STREAM_LISTENER | THR_CAP_ALSA | THR_CAP_STREAM_AUDIO, + .slots = {{0}}, + }, + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 60, + .max_slots = 1, + .thread_capabilities = THR_CAP_STREAM_TALKER | THR_CAP_STREAM_LISTENER | THR_CAP_ALSA | THR_CAP_STREAM_AUDIO, + .slots = {{0}}, + }, + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 60, + .max_slots = 4, + .thread_capabilities = THR_CAP_GSTREAMER_SYNC | THR_CAP_STREAM_TALKER | THR_CAP_CONTROLLED | THR_CAP_TIMER, + .slots = {{0}}, + }, + + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 2, + .max_slots = 1, + .thread_capabilities = THR_CAP_STATS, + .slots = {{0}}, + }, + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 60, + .max_slots = 4, + .thread_capabilities = THR_CAP_GST_MULTI | THR_CAP_STREAM_TALKER | THR_CAP_TIMER, + .slots = {{0}}, + }, + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = GST_THREADS_PRIORITY, + .max_slots = 1, + .thread_capabilities = THR_CAP_GST_BUS_TIMER, + .slots = {{0}}, + }, +}; diff --git a/apps/linux/genavb-multi-stream-app/CMakeLists.txt b/apps/linux/genavb-multi-stream-app/CMakeLists.txt new file mode 100644 index 0000000..f51ee06 --- /dev/null +++ b/apps/linux/genavb-multi-stream-app/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.10) + +project(genavb-multi-stream-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + ../common/common.c + ../common/stats.c + ../common/time.c + ../common/alsa.c +) + +target_compile_definitions(${PROJECT_NAME} PUBLIC WL_BUILD) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) +target_link_libraries(${PROJECT_NAME} asound) +target_link_libraries(${PROJECT_NAME} pthread) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/genavb-multi-stream-app/main.c b/apps/linux/genavb-multi-stream-app/main.c new file mode 100644 index 0000000..2aa5b41 --- /dev/null +++ b/apps/linux/genavb-multi-stream-app/main.c @@ -0,0 +1,727 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../common/common.h" +#include "../common/alsa.h" + + +#define BATCH_SIZE 1024 +#define CFG_CAPTURE_LATENCY_NS 1500000 // Additional fixed playback latency in ns + +#define PROCESS_PRIORITY 51 /* RT priority to be used for the process */ + +#define MAX_THREAD 16 +#define MAX_STREAMS_PER_THREAD 1 + +#define STREAM_CONNECTED (1 << 0) + +#define STREAM_ADD 1 +#define STREAM_REMOVE 2 + +#define FILENAME_SIZE SCHAR_MAX + +#define DEFAULT_ALSA_DEVICE "plughw:0,0" + +struct media_app_stream { + int index; + void *thread; + unsigned int state; + struct avb_stream_params params; + struct avb_stream_handle *stream_h; + int stream_fd; + unsigned int batch_size; + char media_file_name[FILENAME_SIZE]; + int media_fd; + unsigned int media_flags; + void *alsa_h; +}; + +struct mailbox { + pthread_mutex_t mutex; + pthread_cond_t cond; + int type; + int fd; + int index; + short events; +}; + +struct thread { + int index; + pthread_t id; + struct mailbox msg; + struct media_app_stream stream[MAX_STREAMS_PER_THREAD]; + int num_streams; + struct pollfd poll_fd[MAX_STREAMS_PER_THREAD]; +}; + + +/* application main context */ +struct media_app { + struct avb_handle *avb_h; + struct avb_control_handle *ctrl_h; + struct thread thread[MAX_THREAD]; + int stream_flags; + unsigned int flags; + char *alsa_device; +}; + +#define APP_FLAG_ALSA (1 << 0) +#define APP_FLAG_CLOCK_SLAVE (1 << 1) + + +struct media_app app; +pthread_barrier_t init_barrier; + + +static void set_avb_config(unsigned int *avb_flags) +{ + *avb_flags = 0; +} + + +static void dump_stream_infos(struct media_app_stream *stream) +{ + print_stream_id(stream->params.stream_id); + + printf("media file name: %s\n", stream->media_file_name); + printf("mode: %s\n", (stream->params.direction == AVTP_DIRECTION_LISTENER)? "LISTENER":"TALKER"); +} + +static struct media_app_stream *find_free_stream(struct thread *thread) +{ + struct media_app_stream *stream = NULL; + int i; + + for (i = 0; i < MAX_STREAMS_PER_THREAD; i++) { + if (!(thread->stream[i].state & STREAM_CONNECTED)) { + stream = &thread->stream[i]; + printf("%s: stream(%p) found\n", __func__, stream); + break; + } + } + + return stream; +} + + +static struct thread *find_free_thread(void) +{ + struct thread *thread = NULL; + int i; + + for (i = 0; i < MAX_THREAD; i++) { + if (app.thread[i].num_streams < MAX_STREAMS_PER_THREAD) { + thread = &app.thread[i]; + printf("%s: thread(%p) found\n", __func__, thread); + break; + } + } + + return thread; +} + +static struct media_app_stream *find_stream_by_id(void *id) +{ + struct media_app_stream *stream = NULL; + int i, j; + + print_stream_id(id); + + for (i = 0; i < MAX_THREAD; i++) { + for (j = 0; j < MAX_STREAMS_PER_THREAD; j++) { + if (app.thread[i].stream[j].state & STREAM_CONNECTED) { + + if (!memcmp(app.thread[i].stream[j].params.stream_id, id, 8)) { + stream = &app.thread[i].stream[j]; + + printf("%s: stream(%p) found\n", __func__, stream); + + break; + } + } + } + } + + return stream; +} + +static void msg_send(struct thread *thread, int type, int index, int fd, short events) +{ + pthread_mutex_lock(&thread->msg.mutex); + + thread->msg.index = index; + thread->msg.type = type; + thread->msg.fd = fd; + thread->msg.events = events; + + printf("msg_send(%p, %d)\n", thread, type); + + if (!pthread_kill(thread->id, SIGUSR1)) { + + while (thread->msg.type) + pthread_cond_wait(&thread->msg.cond, &thread->msg.mutex); + } + + pthread_mutex_unlock(&thread->msg.mutex); +} + + +static void msg_receive(struct thread *thread) +{ + int index; + + pthread_mutex_lock(&thread->msg.mutex); + + if (thread->msg.type) { + + printf("msg_receive(%p, %d)\n", thread, thread->msg.type); + + index = thread->msg.index; + + switch (thread->msg.type) { + case STREAM_ADD: + thread->poll_fd[index].fd = thread->msg.fd; + thread->poll_fd[index].events = thread->msg.events; + thread->num_streams++; + break; + + case STREAM_REMOVE: + thread->poll_fd[index].fd = -1; + thread->num_streams--; + break; + + default: + break; + } + + thread->msg.type = 0; + pthread_cond_signal(&thread->msg.cond); + } + + pthread_mutex_unlock(&thread->msg.mutex); +} + +static int thread_remove_stream(struct media_app_stream *stream) +{ + struct thread *thread = stream->thread; + int rc = 0; + + msg_send(thread, STREAM_REMOVE, stream->index, 0, 0); + + stream->state &= ~STREAM_CONNECTED; + + if (stream->media_flags & MEDIA_FLAGS_ALSA) + alsa_tx_exit(stream->alsa_h); + + close(stream->media_fd); + + avb_stream_destroy(stream->stream_h); + + printf("%s: thread(%p) removed stream(%p)\n", __func__, thread, stream); + + return rc; +} + + +static int thread_add_stream(struct thread *thread, struct avb_stream_params *stream_params, unsigned int avdecc_stream_index) +{ + struct media_app_stream *stream; + struct avb_stream_handle *stream_h; + int stream_fd; + int rc = 0; + + stream = find_free_stream(thread); + if (!stream) { + printf("%s: could not find free stream\n", __func__); + rc = -1; + goto err_stream_get; + } + + stream->media_flags = 0; + + /* + * create media file + */ + + if (stream_params->direction == AVTP_DIRECTION_LISTENER) { + + /* + * FIXME, provide choice via config file + */ + if ((app.flags & APP_FLAG_ALSA) && !avdecc_stream_index) + stream->media_flags |= MEDIA_FLAGS_ALSA; + else + stream->media_flags |= MEDIA_FLAGS_FILE; + + if (stream->media_flags & MEDIA_FLAGS_FILE) { + rc = snprintf(stream->media_file_name, FILENAME_SIZE, "/home/media/listener_media%d.raw", avdecc_stream_index); + if ((rc < 0) || (rc >= FILENAME_SIZE)) { + printf("Error %d while generating listener media file name \n", rc); + rc = -1; + goto err_filename; + } + + stream->media_fd = open(stream->media_file_name, O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + } + else if (stream->media_flags & MEDIA_FLAGS_ALSA) { + stream->alsa_h = alsa_tx_init(stream_params->stream_id, &stream_params->format, BATCH_SIZE, app.alsa_device); + if (!stream->alsa_h) { + printf("alsa_tx_init error\n"); + rc = -1; + goto err_alsa; + } + + } + } else { + rc = snprintf(stream->media_file_name, FILENAME_SIZE, "/home/media/talker_media%d.raw", avdecc_stream_index); + if ((rc < 0) || (rc >= FILENAME_SIZE)) { + printf("Error %d while generating talker media file name \n", rc); + rc = -1; + goto err_filename; + } + + stream->media_fd = open(stream->media_file_name, O_RDONLY | O_NONBLOCK); + + if (avdecc_format_is_61883_6(&stream_params->format) + && (AVDECC_FMT_61883_6_FDF_EVT(&stream_params->format) == IEC_61883_6_FDF_EVT_AM824)) + { + stream->media_flags |= MEDIA_FLAGS_SET_AM824_LABEL_RAW; + printf("IEC-61883-6 AM824 data format\n"); + } + } + + if (stream->media_fd < 0) { + printf("%s: open(%s) failed: %s\n", __func__, stream->media_file_name, strerror(errno)); + goto err_open; + } + + /* + * setup the stream + */ + + stream->batch_size = BATCH_SIZE; + rc = avb_stream_create(app.avb_h, &stream_h, stream_params, &stream->batch_size, app.stream_flags); + if (rc != AVB_SUCCESS) { + printf("%s: avb_stream_create() failed: %s\n", __func__, avb_strerror(rc)); + rc = -1; + goto err_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", stream->batch_size); + + + /* + * retrieve the file descriptor associated to the stream + */ + + stream_fd = avb_stream_fd(stream_h); + if (stream_fd < 0) { + printf("%s: avb_stream_fd() failed: %s\n", __func__, avb_strerror(stream_fd)); + rc = -1; + goto err_stream_fd; + } + + /* + * link stream to the group and add it to polling list + */ + + memcpy(&stream->params, stream_params, sizeof(struct avb_stream_params)); + stream->stream_h = stream_h; + stream->stream_fd = stream_fd; + stream->state |= STREAM_CONNECTED; + stream->thread = thread; + + dump_stream_infos(stream); + + /* + * add stream to thread polling list + */ + + if (stream_params->direction == AVTP_DIRECTION_LISTENER) + msg_send(thread, STREAM_ADD, stream->index, stream->stream_fd, POLLIN); + else + msg_send(thread, STREAM_ADD, stream->index, stream->stream_fd, POLLOUT); + + printf("%s: thread(%p) added stream(%p) fd(%d)\n", __func__, thread, stream, stream->stream_fd); + + return rc; + +err_stream_fd: + avb_stream_destroy(stream->stream_h); + +err_stream_create: + close(stream->media_fd); + +err_open: +err_alsa: +err_filename: +err_stream_get: + return rc; +} + +static void stream_thread_cleanup(void *arg) +{ + struct thread *thread = (struct thread *)arg; + int i; + + for (i = 0; i < MAX_STREAMS_PER_THREAD; i++) + thread_remove_stream(&thread->stream[i]); +} + + +void signal_usr_handler(int signal_num) +{ +} + +static void *stream_thread(void *arg) +{ + struct thread *thread = (struct thread *)arg; + struct media_app_stream *stream; + struct sigaction action; + sigset_t mask, omask; + int ready, i, n; + int err; + + printf("%s: thread(%p) started\n", __func__, thread); + + action.sa_handler = signal_usr_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGUSR1, &action, NULL) < 0) /* User signal to pause talker streaming */ + printf("sigaction(): %s\n", strerror(errno)); + + /* + * intialize fd/stream table + */ + for (i = 0; i < MAX_STREAMS_PER_THREAD; i++) { + thread->poll_fd[i].fd = -1; + thread->stream[i].index = i; + } + + pthread_cleanup_push(stream_thread_cleanup, thread); + + /* Block the SIGUSR1 signal to avoid missing it before the ppoll */ + + err = sigemptyset(&omask); + if (err < 0) { + printf("thread(%p): sigemptyset failed: %s\n", thread, strerror(errno)); + goto exit; + } + + err = sigemptyset(&mask); + if (err < 0) { + printf("thread(%p): sigemptyset failed: %s\n", thread, strerror(errno)); + goto exit; + } + + err = sigaddset(&mask, SIGUSR1); + if (err < 0) { + printf("thread(%p): sigaddset failed: %s\n", thread, strerror(errno)); + goto exit; + } + + err = pthread_sigmask(SIG_BLOCK, &mask, &omask); + if (err != 0) { + printf("thread(%p): pthread_sigmask failed: %s\n", thread, strerror(err)); + goto exit; + } + + /* + * Wait for barrier to make sure we wake up the main + * thread when all threads complete init + */ + pthread_barrier_wait(&init_barrier); + + /* + * listen to read event from the stack + */ + + while (1) { + ready = ppoll(thread->poll_fd, MAX_STREAMS_PER_THREAD, NULL, &omask); + if (ready < 0) { + if (errno == EINTR) { + msg_receive(thread); + continue; + } + + printf("thread(%p): poll() failed: %s\n", thread, strerror(errno)); + + goto exit; + } + + for (n = 0, i = 0; (i < MAX_STREAMS_PER_THREAD) && (n < ready); i++) { + if ((thread->poll_fd[i].fd > 0) && (thread->poll_fd[i].revents & (POLLIN | POLLOUT))) { + stream = &thread->stream[i]; + + if (stream->params.direction == AVTP_DIRECTION_TALKER) + talker_file_handler(stream->stream_h, stream->media_fd, stream->batch_size, stream->media_flags); + else { + if (stream->media_flags & MEDIA_FLAGS_FILE) + listener_file_handler(stream->stream_h, stream->media_fd, stream->batch_size, NULL); + else if (stream->media_flags & MEDIA_FLAGS_ALSA) + alsa_tx(stream->alsa_h, stream->stream_h, &stream->params); + } + n++; + } + } + + msg_receive(thread); + } + +exit: + pthread_cleanup_pop(1); + + return (void*)0; +} + + +static int handle_avdecc_event(struct avb_control_handle *ctrl_h) +{ + struct avb_stream_params *params; + struct media_app_stream *stream; + struct thread *thread; + union avb_media_stack_msg msg; + unsigned int msg_type, msg_len; + int rc; + + msg_len = sizeof(union avb_media_stack_msg); + rc = avb_control_receive(ctrl_h, &msg_type, &msg, &msg_len); + if (rc != AVB_SUCCESS) + goto error_control_receive; + + switch (msg_type) { + case AVB_MSG_MEDIA_STACK_CONNECT: + printf("AVB_MSG_MEDIA_STACK_CONNECT\n"); + + params = &msg.media_stack_connect.stream_params; + + stream = find_stream_by_id(¶ms->stream_id); + if (stream) { + printf("%s: stream already created\n", __func__); + break; + } + + if (params->direction == AVTP_DIRECTION_TALKER) { + if (app.flags & APP_FLAG_CLOCK_SLAVE) + params->clock_domain = AVB_MEDIA_CLOCK_DOMAIN_STREAM; + else + params->clock_domain = AVB_MEDIA_CLOCK_DOMAIN_PTP; + + params->talker.latency = max(CFG_CAPTURE_LATENCY_NS, sr_class_interval_p(params->stream_class) / sr_class_interval_q(params->stream_class)); + } else { + params->clock_domain = AVB_MEDIA_CLOCK_DOMAIN_STREAM; + } + + + + thread = find_free_thread(); + if (thread) { + if (thread_add_stream(thread, params, msg.media_stack_connect.stream_index) < 0) + printf("%s: stream creation failed\n", __func__); + } else + printf("%s: stream creation failed: could not find free thread\n", __func__); + + break; + + case AVB_MSG_MEDIA_STACK_DISCONNECT: + printf("AVB_MSG_MEDIA_STACK_DISCONNECT\n"); + + stream = find_stream_by_id(&msg.media_stack_disconnect.stream_id); + + if (stream) + thread_remove_stream(stream); + + break; + + default: + break; + } + +error_control_receive: + return rc; +} + + +static void thread_destroy(struct thread *thread) +{ + pthread_cancel(thread->id); + pthread_join(thread->id, NULL); + pthread_cond_destroy(&thread->msg.cond); + pthread_mutex_destroy(&thread->msg.mutex); +} + + +static int thread_create(struct thread *thread) +{ + int rc; + + pthread_mutex_init(&thread->msg.mutex, NULL); + pthread_cond_init(&thread->msg.cond, NULL); + + rc = pthread_create(&thread->id, NULL, &stream_thread, thread); + if (rc != 0) { + pthread_cond_destroy(&thread->msg.cond); + pthread_mutex_destroy(&thread->msg.mutex); + } + + return rc; +} + + +int main(int argc, char *argv[]) +{ + unsigned int avb_flags; + int ctrl_rx_fd; + struct pollfd ctrl_poll; + int option; + struct sched_param param = { + .sched_priority = PROCESS_PRIORITY, + }; + int i,j; + int rc = 0; + + setlinebuf(stdout); + + memset(&app, 0, sizeof(app)); + + printf("NXP's GenAVB reference multiple audio stream application\n"); + + if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) { + printf("sched_setscheduler() failed: %s\n", strerror(errno)); + rc = -1; + goto error_sched; + } + + app.alsa_device = DEFAULT_ALSA_DEVICE; + + while ((option = getopt(argc, argv, "asd:")) != -1) { + switch (option) { + case 'a': + app.flags |= APP_FLAG_ALSA; + break; + + case 'd': + app.alsa_device = optarg; + break; + + case 's': + app.flags |= APP_FLAG_CLOCK_SLAVE; + break; + + default: + break; + } + } + + /* + * setup the avb stack + */ + + set_avb_config(&avb_flags); + + rc = avb_init(&app.avb_h, avb_flags); + if (rc != AVB_SUCCESS) { + printf("avb_init() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + /* + * common setting applied to any stream + */ + app.stream_flags = AVTP_NONBLOCK; + + /* + * Init the barrier with a count of all created thread plus the main thread + */ + pthread_barrier_init(&init_barrier, NULL, MAX_THREAD + 1); + + /* + * create all streams threads + */ + for (i = 0; i < MAX_THREAD; i++) { + rc = thread_create(&app.thread[i]); + if (rc != 0) { + printf("pthread_create for thread number (%d) failed: %s\n", i, strerror(rc)); + /* Remove all previously created threads*/ + for (j = 0; j < i; j++) + thread_destroy(&app.thread[i]); + goto error_thread_create; + } + + app.thread[i].index= i; + } + + + /* + * Wait for all threads to complete init + */ + pthread_barrier_wait(&init_barrier); + + /* + * listen to avdecc events to get stream parameters + */ + + rc = avb_control_open(app.avb_h, &app.ctrl_h, AVB_CTRL_AVDECC_MEDIA_STACK); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() failed: %s\n", avb_strerror(rc)); + goto error_control_open; + } + + ctrl_rx_fd = avb_control_rx_fd(app.ctrl_h); + ctrl_poll.fd = ctrl_rx_fd; + ctrl_poll.events = POLLIN; + ctrl_poll.revents = 0; + + while (1) { + if (poll(&ctrl_poll, 1, -1) == -1) { + printf("poll(%d) failed on waiting for connect\n", ctrl_poll.fd); + rc = -1; + goto error_ctrl_poll; + } + + if (ctrl_poll.revents & POLLIN) + handle_avdecc_event(app.ctrl_h); + } + +error_ctrl_poll: + avb_control_close(app.ctrl_h); + +error_control_open: + /* + * destroy all stream threads + */ + for (i = 0; i < MAX_THREAD; i++) + thread_destroy(&app.thread[i]); + + pthread_barrier_destroy(&init_barrier); + +error_thread_create: + avb_exit(app.avb_h); + +error_avb_init: +error_sched: + return rc; +} diff --git a/apps/linux/genavb-video-player-app/CMakeLists.txt b/apps/linux/genavb-video-player-app/CMakeLists.txt new file mode 100644 index 0000000..6dbe0cb --- /dev/null +++ b/apps/linux/genavb-video-player-app/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.10) + +project(genavb-video-player-app) + +option(WAYLAND_BACKEND "Build application with support for wayland backend" ON) + +find_package(PkgConfig) +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0 gstreamer-app-1.0) + +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + gstreamer.c + gst_pipelines.c + gstreamer_single.c + ../common/common.c + ../common/stats.c + ../common/time.c + ../common/ts_parser.c + ../common/file_buffer.c +) + +if(WAYLAND_BACKEND) + target_compile_definitions(${PROJECT_NAME} PUBLIC WL_BUILD) +endif() + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) +target_link_libraries(${PROJECT_NAME} ${GSTREAMER_LIBRARIES}) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) +install(PROGRAMS salsa-camera.sh DESTINATION usr/bin) diff --git a/apps/linux/genavb-video-player-app/gst_pipelines.c b/apps/linux/genavb-video-player-app/gst_pipelines.c new file mode 100644 index 0000000..39f5197 --- /dev/null +++ b/apps/linux/genavb-video-player-app/gst_pipelines.c @@ -0,0 +1,119 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "gst_pipelines.h" + + +/* Talker pipelines */ + +char const * const pipeline_talker_file_61883_4 = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue ! video/x-h264 ! h264parse config-interval=1 ! mux." + " demux. ! queue ! audio/mpeg ! mux." + " mpegtsmux name=mux ! appsink name=sink0" +; + +char const * const pipeline_talker_file_61883_4_61883_6 = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue ! video/x-h264 ! h264parse config-interval=1 ! mux." /* video */ + " demux. ! queue ! audio/mpeg ! aacparse ! tee name=t" /* audio */ + " t. ! queue ! avdec_aac ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! appsink name=sink1" + " t. ! queue ! mux." + " mpegtsmux name=mux ! appsink name=sink0" +; + +char const * const pipeline_talker_file_61883_4_preview = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! audio/mpeg ! mux." + " demux. ! tee name=t" +#ifdef WL_BUILD + " t. ! queue ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=false sync=true" /* local display tee */ +#else + " t. ! queue ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true sync=true" /* local display tee */ +#endif + " t. ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! video/x-h264 ! h264parse config-interval=1 ! mux." /* avb streaming tee */ + " mpegtsmux name=mux ! appsink name=sink0" +; + +char const * const pipeline_talker_file_61883_4_61883_6_preview = + "filesrc name=filesrc typefind=true ! qtdemux name=demux" + " demux. ! tee name=tvideo" +#ifdef WL_BUILD + " tvideo. ! queue ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=false sync=true" /* local display tee */ +#else + " tvideo. ! queue ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true sync=true" /* local display tee */ +#endif + " tvideo. ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! video/x-h264 ! h264parse config-interval=1 ! mux." /* video */ + " demux. ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! audio/mpeg ! aacparse ! tee name=taudio" /* audio */ + " taudio. ! queue ! avdec_aac ! audioconvert ! audio/x-raw,channels=2,format=S24_32BE,rate=48000 ! appsink name=sink1" + " taudio. ! queue ! mux." + " mpegtsmux name=mux alignment=1 ! appsink name=sink0" +; + + +/* Listener pipelines */ + +char const * const pipeline_listener_61883_4_audio_video = + "appsrc name=source0 is-live=true ! tsdemux name=demux" +#ifdef WL_BUILD + " demux. ! video/x-h264 ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=false max-lateness=1000000 sync=true" +#else + " demux. ! video/x-h264 ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true max-lateness=1000000 sync=true" +#endif + " demux. ! audio/mpeg ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! alsasink name=audiosink device=default max-lateness=1000000 alignment-threshold=1000000 latency-time=5000 buffer-time=50000 sync=true" +; + +char const * const pipeline_listener_61883_4_audio_only = + "appsrc name=source0 is-live=true ! tsdemux" + " ! audio/mpeg ! queue ! aacparse ! avdec_aac ! audioconvert" + " ! alsasink name=audiosink device=default max-lateness=1000000 alignment-threshold=1000000 latency-time=5000 buffer-time=50000 sync=true" +; + +char const * const pipeline_listener_61883_4_video_only = + "appsrc name=source0 is-live=true ! tsdemux" +#ifdef WL_BUILD + " ! video/x-h264 ! queue ! vpudec frame-drop=false ! glimagesink name=videosink force-aspect-ratio=false max-lateness=1000000 sync=true" +#else + " ! video/x-h264 ! queue ! vpudec frame-drop=false ! imxv4l2sink name=videosink force-aspect-ratio=true max-lateness=1000000 sync=true" +#endif +; + + +char const * const pipeline_listener_61883_6 = + "appsrc name=source0 is-live=true" + " ! audio/x-raw,format=S24_32BE,rate=48000,channels=2 ! queue max-size-bytes=200000000 max-size-buffers=0 max-size-time=0 ! audioconvert" + " ! alsasink name=audiosink device=default max-lateness=1000000 alignment-threshold=1000000 latency-time=5000 buffer-time=50000 sync=true" +; + +char const * const pipeline_listener_cvf_mjpeg = + "appsrc name=source0 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! imxv4l2sink name=videosink0 force-aspect-ratio=true sync=true max-lateness=0" +; + +char const * const pipeline_cvf_mjpeg_four_cameras = + "appsrc name=source0 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! overlaysink name=videosink0 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source1 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! overlaysink name=videosink1 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source2 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! overlaysink name=videosink2 force-aspect-ratio=true sync=true max-lateness=0" + " appsrc name=source3 is-live=true" + " ! image/jpeg, width=1280, height=800, framerate=30/1 ! vpudec frame-drop=false ! overlaysink name=videosink3 force-aspect-ratio=true sync=true max-lateness=0" +; + +char const * const pipeline_listener_debug = + "appsrc name=source0 is-live=true" + " ! filesink location=/storage/dump.ts" +; + + +unsigned long long latency_61883_4_audio_video = MPEGTS_LATENCY + ALSA_LATENCY; +unsigned long long latency_61883_4_audio_only = MPEGTS_LATENCY + ALSA_LATENCY; +unsigned long long latency_61883_4_video_only = MPEGTS_LATENCY; +unsigned long long latency_61883_6 = ALSA_LATENCY; +unsigned long long latency_cvf_mjpeg = MJPEG_PIPELINE_LATENCY; diff --git a/apps/linux/genavb-video-player-app/gst_pipelines.h b/apps/linux/genavb-video-player-app/gst_pipelines.h new file mode 100644 index 0000000..ae0b736 --- /dev/null +++ b/apps/linux/genavb-video-player-app/gst_pipelines.h @@ -0,0 +1,41 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _GST_PIPELINES_H_ +#define _GST_PIPELINES_H_ + +extern char const * const pipeline_talker_file_61883_4; +extern char const * const pipeline_talker_file_61883_4_61883_6; +extern char const * const pipeline_talker_file_61883_4_preview; +extern char const * const pipeline_talker_file_61883_4_61883_6_preview; + +extern char const * const pipeline_listener_61883_4_audio_video; +extern char const * const pipeline_listener_61883_4_audio_only; +extern char const * const pipeline_listener_61883_4_video_only; +extern char const * const pipeline_listener_61883_6; +extern char const * const pipeline_listener_cvf_mjpeg; +extern char const * const pipeline_cvf_mjpeg_four_cameras; +extern char const * const pipeline_listener_debug; + +#define MPEGTS_LATENCY 100000000 /* Must match latency compiled in gstreamer plugin */ +#define ALSA_LATENCY 55000000 /* Must match latency set bellow */ + +#define LOCAL_PTS_OFFSET 100000000 /* 100ms (in ns) */ +#define DEFAULT_PTS_OFFSET (LOCAL_PTS_OFFSET + MPEGTS_LATENCY + ALSA_LATENCY) +#define MAX_PTS_OFFSET (1 * GST_SECOND) + +#define MJPEG_PIPELINE_LATENCY 10000000 +#define SALSA_LATENCY 33000000 +#define CVF_PTS_OFFSET (SALSA_LATENCY + MJPEG_PIPELINE_LATENCY) + +extern unsigned long long latency_61883_4_audio_video; +extern unsigned long long latency_61883_4_audio_only; +extern unsigned long long latency_61883_4_video_only; +extern unsigned long long latency_61883_6; +extern unsigned long long latency_cvf_mjpeg; + +#endif /* _GST_PIPELINES_H_ */ diff --git a/apps/linux/genavb-video-player-app/gstreamer.c b/apps/linux/genavb-video-player-app/gstreamer.c new file mode 100644 index 0000000..61a2352 --- /dev/null +++ b/apps/linux/genavb-video-player-app/gstreamer.c @@ -0,0 +1,473 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gstreamer.h" +#include "../common/time.h" + + +static void gst_get_latency(struct gstreamer_pipeline *gst) +{ + GstQuery *q; + + q = gst_query_new_latency(); + if (gst_element_query(gst->pipeline, q)) { + gboolean live; + GstClockTime minlat, maxlat; + + gst_query_parse_latency(q, &live, &minlat, &maxlat); + + printf("Pipeline latency: %ju-%ju ns\n", minlat, maxlat); + } + + gst_query_unref(q); +} + +static void gst_set_latency(struct gstreamer_pipeline *gst) +{ + if (gst->video_sink) { + g_object_set(G_OBJECT(gst->video_sink), + "render-delay", gst->pts_offset - gst->pipeline_latency, + NULL); + + printf("Set video sink render-delay: %ju\n", gst->pts_offset - gst->pipeline_latency); + + if (gst->audio_sink) { + g_object_set(G_OBJECT(gst->audio_sink), + "render-delay", gst->pts_offset - gst->pipeline_latency, + NULL); + + printf("Set audio sink render-delay: %ju\n", gst->pts_offset - gst->pipeline_latency); + } + } + + if (gst->audio_sink) { + /* Audio sink render delay affects the overall pipeline latency but is not having any effect on the actual audio playback + * set also ts-offset as a workaround */ + g_object_set(G_OBJECT(gst->audio_sink), + "ts-offset", gst->pts_offset - gst->pipeline_latency, + NULL); + + printf("Set audio sink ts-offset: %ju\n", gst->pts_offset - gst->pipeline_latency); + } +} + + +static void gst_blank_screen(char *device_str) +{ + int rc; + uint64_t now, then; + GstElement *video_sink; + GstElement *pipeline; + GstBus *bus; + GstMessage *msg; + + gettime_us(&now); + + pipeline = gst_parse_launch("videotestsrc pattern=black num-buffers=4 !video/x-raw,format=YV12 !imxv4l2sink name=videosink overlay-width=2000 overlay-height=2000", NULL); + if (!pipeline) + goto err_pipeline; + + video_sink = gst_bin_get_by_name(GST_BIN(pipeline), "videosink"); + if (!video_sink) + goto err_video; + + g_object_set(G_OBJECT(video_sink), "device", device_str, NULL); + + rc = gst_element_set_state(pipeline, GST_STATE_PLAYING); + if (rc == GST_STATE_CHANGE_FAILURE) + goto err_state; + + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + if (!bus) + goto err_bus; + + msg = gst_bus_timed_pop_filtered(bus, GST_SECOND, GST_MESSAGE_EOS); + if (msg) + gst_message_unref(msg); + + gst_element_set_state(pipeline, GST_STATE_NULL); + + gettime_us(&then); + + printf("Blanking screen took %" PRId64 " us\n", then - now); + + gst_object_unref(bus); + +err_bus: +err_state: + gst_object_unref(video_sink); + +err_video: + gst_object_unref(pipeline); + +err_pipeline: + return; +} + +static int gst_config_pipeline(struct gstreamer_pipeline *gst) +{ + int i; + + gst_blank_screen(gst->device); + + gst->clock = g_object_new (GST_TYPE_SYSTEM_CLOCK, "name", "GstSystemClock", NULL); + if (!gst->clock) { + printf("Error: could not obtain system clock.\n"); + goto err_clock; + } + g_object_set(G_OBJECT(gst->clock), "clock-type", GST_CLOCK_TYPE_REALTIME, NULL); + + gst->time = gst_clock_get_time(gst->clock); + gst_pipeline_use_clock(GST_PIPELINE(gst->pipeline), gst->clock); + +#if 0 + if (gst->direction == GST_DIRECTION_LISTENER) { + /* Enable to use absolute gPTP time as the running-time of the pipeline, useful for synchronization debugging */ + gst_element_set_start_time(gst->pipeline, GST_CLOCK_TIME_NONE); + gst_element_set_base_time(gst->pipeline, 0); + } +#endif + + gst->video_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "videosink"); + if (!gst->video_sink) { + printf("Warning: Video sink not found in pipeline\n"); + } else { + g_object_set(G_OBJECT(gst->video_sink), "device", gst->device, + "crop-width", gst->crop_width, + "crop-height", gst->crop_height, + "overlay-width", gst->overlay_width, + "overlay-height", gst->overlay_height, + NULL); + + /* Direction specific settings */ + if (gst->direction == GST_DIRECTION_LISTENER) { + + } else { + g_object_set(G_OBJECT(gst->video_sink), + "ts-offset", gst->u.talker.preview_ts_offset, + NULL); + } + } + + gst->audio_sink = gst_bin_get_by_name(GST_BIN(gst->pipeline), "audiosink"); + if (!gst->audio_sink) + printf("Warning: Audio sink not found in pipeline\n"); + + if (gst->direction == GST_DIRECTION_LISTENER) { + gst_set_latency(gst); + + for (i = 0; i < gst->u.listener.num_sources; i++) { + GstAppSrc *source = gst->u.listener.source[i].source; + + g_object_set(G_OBJECT(source), "format", GST_FORMAT_TIME, NULL); + } + + } else { + GstElement *element; + + element = gst_bin_get_by_name(GST_BIN(gst->pipeline), "filesrc"); + if (!element) { + printf("File source element not found in pipeline\n"); + goto err_file_src; + } + + g_object_set(G_OBJECT(element), "location", gst->u.talker.file_src_location, NULL); + gst_object_unref(element); + } + + return 0; + +err_file_src: + if (gst->video_sink) { + g_object_unref(gst->video_sink); + gst->video_sink = NULL; + } + + if (gst->audio_sink) { + g_object_unref(gst->audio_sink); + gst->audio_sink = NULL; + } + + gst_object_unref(gst->clock); + +err_clock: + return -1; +} + +static int gst_get_sources_sinks(struct gstreamer_pipeline *gst) +{ + int i, j, rc; + + if (gst->direction == GST_DIRECTION_LISTENER) { + for (i = 0; i < gst->u.listener.num_sources; i++) { + GstAppSrc *source; + char source_name[16]; + + rc = snprintf(source_name, 16, "source%d", i); + if ((rc < 0) || (rc >= 16)) { + printf("Error while generating source name, rc: %d\n", rc); + goto err; + } + source = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(gst->pipeline), source_name)); + if (!source) { + printf("gst_bin_get_by_name(%s) failed\n", source_name); + goto err; + } + + gst->u.listener.source[i].source = source; + } + } else { + for (i = 0; i < gst->u.talker.num_sinks; i++) { + GstAppSink *sink; + char sink_name[16]; + + rc = snprintf(sink_name, 16, "sink%d", i); + if ((rc < 0) || (rc >= 16)) { + printf("Error while generating sink name, rc: %d\n", rc); + goto err; + } + sink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(gst->pipeline), sink_name)); + if (!sink) { + printf("gst_bin_get_by_name(%s) failed\n", sink_name); + goto err; + } + + gst->u.talker.sink[i].sink = sink; + } + } + + return 0; + +err: + if (gst->direction == GST_DIRECTION_LISTENER) { + for (j = 0; j < i; j++) + gst_object_unref(gst->u.listener.source[j].source); + } else { + for (j = 0; j < i; j++) + gst_object_unref(gst->u.talker.sink[j].sink); + } + + return -1; +} + +static void gst_release_sources_sinks(struct gstreamer_pipeline *gst) +{ + int i; + + if (gst->direction == GST_DIRECTION_LISTENER) { + for (i = 0; i < gst->u.listener.num_sources; i++) + gst_object_unref(gst->u.listener.source[i].source); + } else { + for (i = 0; i < gst->u.talker.num_sinks; i++) + gst_object_unref(gst->u.talker.sink[i].sink); + } +} + +static void gst_teardown_pipeline(struct gstreamer_pipeline *gst) +{ + if (gst->video_sink) { + g_object_unref(gst->video_sink); + gst->video_sink = NULL; + } + + if (gst->audio_sink) { + g_object_unref(gst->audio_sink); + gst->audio_sink = NULL; + } + + gst_object_unref(gst->clock); +} + +int gst_stop_pipeline(struct gstreamer_pipeline *gst) +{ + int rc; + + rc = gst_element_set_state(gst->pipeline, GST_STATE_NULL); + if (rc == GST_STATE_CHANGE_FAILURE) + printf("Unable to set the pipeline to the NULL state.\n"); + + gst_release_sources_sinks(gst); + + gst_teardown_pipeline(gst); + + gst_object_unref(gst->bus); + + gst_object_unref(gst->pipeline); + + return rc; +} + +int gst_setup_pipeline(struct gstreamer_pipeline *gst, int priority, unsigned int direction) +{ + gst->direction = direction; + + gst->pipeline = gst_parse_launch((const gchar *)gst->pipeline_string, NULL); + if (!gst->pipeline) { + printf("gst_parse_launch() failed\n"); + goto err_pipeline; + } + + if (gst_get_sources_sinks(gst) < 0) + goto err_src_sink; + + if (gst_config_pipeline(gst) < 0) + goto err_setup; + + gst->bus = gst_pipeline_get_bus(GST_PIPELINE(gst->pipeline)); + if (!gst->bus) { + printf("gst_pipeline_get_bus() failed\n"); + goto err_bus; + } + + return 0; + +err_bus: + gst_teardown_pipeline(gst); + +err_setup: + gst_release_sources_sinks(gst); + +err_src_sink: + gst_object_unref(gst->pipeline); + +err_pipeline: + return -1; +} + +int gst_play_pipeline(struct gstreamer_pipeline *gst) +{ + int rc; + + gst_bus_set_flushing(gst->bus, TRUE); + gst_bus_set_flushing(gst->bus, FALSE); + + rc = gst_element_set_state(GST_ELEMENT(gst->pipeline), GST_STATE_PLAYING); + if (rc == GST_STATE_CHANGE_FAILURE) { + printf("Unable to set the pipeline to the playing state.\n"); + goto err_state; + } + + gst->basetime = GST_CLOCK_TIME_NONE; + + return 0; + +err_state: + gst_stop_pipeline(gst); + + return -1; +} + +int gst_start_pipeline(struct gstreamer_pipeline *gst, int priority, unsigned int direction) +{ + int rc; + + rc = gst_setup_pipeline(gst, priority, direction); + if (rc < 0) + goto err_setup; + + rc = gst_play_pipeline(gst); + + return rc; + +err_setup: + return -1; +} + +void gst_process_bus_messages(struct gstreamer_pipeline *gst) +{ + GstMessage *msg; + GError *err; + gchar *debug_info; + + while (gst_bus_have_pending(gst->bus)) { + + msg = gst_bus_pop_filtered(gst->bus, GST_MESSAGE_ERROR | GST_MESSAGE_WARNING | GST_MESSAGE_EOS | GST_MESSAGE_ASYNC_DONE); + if (!msg) + break; + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ASYNC_DONE: + gst_get_latency(gst); + + break; + + case GST_MESSAGE_STATE_CHANGED: { + GstState old_state, new_state, pending_state; + + gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); + + g_print ("Element %s changed state from %s to %s (target %s)\n", GST_OBJECT_NAME(msg->src), + gst_element_state_get_name(old_state), gst_element_state_get_name(new_state), gst_element_state_get_name(pending_state)); + + break; + } + + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &err, &debug_info); + + printf("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message); + printf("Debugging information: %s\n", debug_info ? debug_info : "none"); + + g_clear_error(&err); + g_free(debug_info); + + break; + + case GST_MESSAGE_WARNING: + gst_message_parse_warning(msg, &err, &debug_info); + + printf("Warning received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message); + printf("Debugging information: %s\n", debug_info ? debug_info : "none"); + + g_clear_error(&err); + g_free(debug_info); + + break; + + case GST_MESSAGE_INFO: + gst_message_parse_info(msg, &err, &debug_info); + printf("Info received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); + printf("Debugging information: %s\n", debug_info ? debug_info : "none"); + g_clear_error(&err); + g_free(debug_info); + break; + + case GST_MESSAGE_EOS: + printf("End-Of-Stream reached.\n"); + break; + + default: + /* We should not reach here because we only asked for ERRORs and EOS */ + break; + } + + gst_message_unref (msg); + } +} + +void gstreamer_init(void) +{ + gst_init(NULL, NULL); +} + +void gstreamer_reset(void) +{ + gst_deinit(); +} diff --git a/apps/linux/genavb-video-player-app/gstreamer.h b/apps/linux/genavb-video-player-app/gstreamer.h new file mode 100644 index 0000000..13d27a3 --- /dev/null +++ b/apps/linux/genavb-video-player-app/gstreamer.h @@ -0,0 +1,116 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _GSTREAMER_H_ +#define _GSTREAMER_H_ + +#include + +#include +#include +#include + +#define LVDS_DEVICE_FILE "/dev/video16" +#define HDMI_DEVICE_FILE "/dev/video18" + +#define GST_DIRECTION_LISTENER 0 +#define GST_DIRECTION_TALKER 1 + + +#ifdef __cplusplus +extern "C" { +#endif + +#define GST_MAX_SOURCES 4 +#define GST_MAX_SINKS 2 + +struct gstreamer_sink { + void *data; + + GstAppSink *sink; +}; + +struct gstreamer_source { + void *data; + + GstAppSrc *source; +}; + + +#define GST_TYPE_VIDEO (1 << 0) +#define GST_TYPE_AUDIO (1 << 1) +#define GST_TYPE_CAMERA (1 << 2) + + +struct gstreamer_pipeline_config { + unsigned int type; + char *device; + unsigned int width; + unsigned int height; + unsigned int nstreams; // Number of streams that this pipeline will handle + GstClockTime pts_offset; + +}; + +struct gstreamer_pipeline { + struct gstreamer_pipeline_config config; + char const * pipeline_string; + unsigned int crop_width; + unsigned int crop_height; + unsigned int overlay_width; + unsigned int overlay_height; + char *device; + + unsigned int direction; + + union { + struct { + int num_sources; + + struct gstreamer_source source[GST_MAX_SOURCES]; + } listener; + + struct { + int num_sinks; + + struct gstreamer_sink sink[GST_MAX_SINKS]; + + char *file_src_location; + unsigned int preview_ts_offset; + unsigned int sync; + } talker; + } u; + + GstElement *pipeline; + GstBus *bus; + GstClock *clock; + GstElement *video_sink; + GstElement *audio_sink; + GstClockTime time; + GstClockTime pipeline_latency; + GstClockTime pts_offset; + unsigned long long local_pts_offset; + unsigned long long basetime; +}; + +int gst_setup_pipeline(struct gstreamer_pipeline *gst, int priority, unsigned int direction); +int gst_play_pipeline(struct gstreamer_pipeline *gst); +int gst_start_pipeline(struct gstreamer_pipeline *gst, int priority, unsigned int direction); + +void gst_process_bus_messages(struct gstreamer_pipeline *gst); + +int gst_stop_pipeline(struct gstreamer_pipeline *gst); + +void gstreamer_init(void); +void gstreamer_reset(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* _GSTREAMER_H_ */ diff --git a/apps/linux/genavb-video-player-app/gstreamer_single.c b/apps/linux/genavb-video-player-app/gstreamer_single.c new file mode 100644 index 0000000..e37bd4b --- /dev/null +++ b/apps/linux/genavb-video-player-app/gstreamer_single.c @@ -0,0 +1,793 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include "gst_pipelines.h" +#include "gstreamer_single.h" + + +void stream_init_stats(struct gstreamer_stream *stream) +{ + stats_init(&stream->stream_stats.write_delay, 31, stream, NULL); + stats_init(&stream->stream_stats.delay, 31, stream, NULL); + stats_init(&stream->stream_stats.period, 31, stream, NULL); + stats_init(&stream->stream_stats.rate, 31, stream, NULL); + stats_init(&stream->stream_stats.mpeg_ts.pcr_delay, 31, stream, NULL); + stats_init(&stream->stream_stats.mpeg_ts.pcr_period, 31, stream, NULL); + + stream->stream_stats.pkt_lost = 0; + stream->stream_stats.ts_err = 0; + stream->stream_stats.mr = 0; + stream->started = 0; + stream->pipe_source.byte_count = 0; + stream->stream_stats.byte_count_prev = 0; + stream->pipe_source.tlast = time(NULL); + stream->ts_parser.pcr_count = 0; + stream->pipe_source.count = 0; + stream->pipe_source.late_count = 0; + stream->pipe_source.ontime_count = 0; +} + +void stream_update_stats(struct gstreamer_stream *stream, unsigned long long byte_count, unsigned long long now, unsigned long long buffer_pts) +{ + int period = 0, rate = 0; + + stats_update(&stream->stream_stats.delay, (int)(buffer_pts - now)); + + if (byte_count) { + period = buffer_pts - stream->stream_stats.buffer_pts_prev; + + if (period) { + rate = ((byte_count - stream->stream_stats.byte_count_prev) * NSECS_PER_SEC) / period; + + stats_update(&stream->stream_stats.period, period); + + stats_update(&stream->stream_stats.rate, rate); + + stream->stream_stats.buffer_pts_prev = buffer_pts; + stream->stream_stats.byte_count_prev = byte_count; + } + } else { + stream->stream_stats.buffer_pts_prev = buffer_pts; + stream->stream_stats.byte_count_prev = byte_count; + } + + if ((stream->stream_stats.delay.current_count < 10) && (stream->pipe_source.byte_count < 1000000)) { + printf("now: %llu(%u) count: %llu ts: %llu(%u) delay: %lld period: %d rate: %d\n", + now, (unsigned int)now, + byte_count, + buffer_pts, (unsigned int)buffer_pts, + buffer_pts - now, + period, + rate); + } +} + +void stream_dump_stats(struct gstreamer_stream *stream) +{ + stats_compute(&stream->stream_stats.write_delay); + stats_compute(&stream->stream_stats.delay); + stats_compute(&stream->stream_stats.period); + stats_compute(&stream->stream_stats.rate); + + printf("stream(%u): write delay(ns): %7d/%7d/%7d delay(ns): %9d/%9d/%9d period(ns): %9d/%9d/%9d rate(bytes/s): %7d/%7d/%7d pkt_lost: %7llu ts_err: %7llu mr: %7llu\n", stream->source_index, + stream->stream_stats.write_delay.min, stream->stream_stats.write_delay.mean, stream->stream_stats.write_delay.max, + stream->stream_stats.delay.min, stream->stream_stats.delay.mean, stream->stream_stats.delay.max, + stream->stream_stats.period.min, stream->stream_stats.period.mean, stream->stream_stats.period.max, + stream->stream_stats.rate.min, stream->stream_stats.rate.mean, stream->stream_stats.rate.max, + stream->stream_stats.pkt_lost, stream->stream_stats.ts_err, stream->stream_stats.mr + ); + + stats_reset(&stream->stream_stats.write_delay); + stats_reset(&stream->stream_stats.delay); + stats_reset(&stream->stream_stats.period); + stats_reset(&stream->stream_stats.rate); + + if (avdecc_format_is_61883_4(&stream->params.format)) { + stats_compute(&stream->stream_stats.mpeg_ts.pcr_delay); + stats_compute(&stream->stream_stats.mpeg_ts.pcr_period); + + printf("stream(%u): pcr delay(ns): (%lld) %7d/%7d/%7d period(ns): %7d/%7d/%7d\n", stream->source_index, + stream->ts_parser.buffer_pts0 - stream->ts_parser.pcr0, + stream->stream_stats.mpeg_ts.pcr_delay.min, stream->stream_stats.mpeg_ts.pcr_delay.mean, stream->stream_stats.mpeg_ts.pcr_delay.max, + stream->stream_stats.mpeg_ts.pcr_period.min, stream->stream_stats.mpeg_ts.pcr_period.mean, stream->stream_stats.mpeg_ts.pcr_period.max + ); + + stats_reset(&stream->stream_stats.mpeg_ts.pcr_delay); + stats_reset(&stream->stream_stats.mpeg_ts.pcr_period); + } +} + +void dump_stream_infos(struct gstreamer_stream *stream) +{ + print_stream_id(stream->params.stream_id); + + if(stream->params.format.u.s.subtype != AVTP_SUBTYPE_CVF) + printf("media file name: %s\n", stream->media_file_name); + + printf("mode: %s\n", (stream->params.direction == AVTP_DIRECTION_LISTENER)? "LISTENER":"TALKER"); +} + +void stream_61883_4_update_stats(struct gstreamer_stream *stream, unsigned char *buf, unsigned long long buffer_pts) +{ + unsigned long long pcr; + int period; + + if (ts_parser_is_pcr(buf, &pcr)) { + + if (!stream->ts_parser.pcr_count) { + stream->ts_parser.pcr0 = pcr; + stream->ts_parser.buffer_pts0 = buffer_pts; + } + + stats_update(&stream->stream_stats.mpeg_ts.pcr_delay, (pcr - stream->ts_parser.pcr0) - (buffer_pts - stream->ts_parser.buffer_pts0)); + + if (stream->ts_parser.pcr_count > 1) { + period = pcr - stream->stream_stats.mpeg_ts.pcr_prev; + stats_update(&stream->stream_stats.mpeg_ts.pcr_period, period); + } else + period = 0; + + if (stream->ts_parser.pcr_count < 10) { + printf("pcr(ns): %llu(%lld) ts(ns): %llu(%lld) pcr delay(ns): %lld period(ns): %d\n", + stream->ts_parser.pcr0, pcr - stream->ts_parser.pcr0, + stream->ts_parser.buffer_pts0, buffer_pts - stream->ts_parser.buffer_pts0, + (pcr - stream->ts_parser.pcr0) - (buffer_pts - stream->ts_parser.buffer_pts0), period); + } + + stream->stream_stats.mpeg_ts.pcr_prev = pcr; + stream->ts_parser.pcr_count++; + } +} + + +#define CVF_MJPEG_SPLASH_CAPTURE 0 + +#if CVF_MJPEG_SPLASH_CAPTURE +/* Splash screen file format: records of 32-bit frame_size followed by frame_size bytes + */ +#define CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD CVF_MJPEG_SPLASH_MAX_FRAME_SIZE +#define CVF_MJPEG_CAPTURE_FILENAME "/tmp/cvf_mjpeg_splash_screen.mjpg" + +static void cvf_mjpeg_splash_capture(struct gstreamer_stream *stream) +{ + static unsigned int capture_splash_previous_frame_size = 0; + static int capture_splash_fd = -1; + unsigned int frame_size = stream->pipe_source.buffer_byte_count; + + // Only save the splash screen from the first camera + if (stream->source_index == 0) { + + // Start a new splash screen + if ((capture_splash_previous_frame_size > CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD) && (frame_size < CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD)) { + printf("Starting splash screen\n"); + capture_splash_fd = open(CVF_MJPEG_CAPTURE_FILENAME, O_WRONLY | O_CREAT | O_TRUNC); + if (capture_splash_fd < 0) + printf("stream(%u): Could not open capture file\n", stream->source_index); + } + + // Continuing the current splash screen + if ((frame_size < CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD) && (capture_splash_fd >= 0)) { + write(capture_splash_fd, &frame_size, 4); + write(capture_splash_fd, stream->pipe_source.info.data, frame_size); + } + + // Close the current splash screen + if ((capture_splash_previous_frame_size < CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD) && (frame_size > CVF_MJPEG_SPLASH_CAPTURE_THRESHOLD)) { + close(capture_splash_fd); + printf("Closing splash screen\n"); + } + + capture_splash_previous_frame_size = frame_size; + //printf("stream(%d) frame size %d\n", stream->source_index, stream->gst_stream.buffer_byte_count); + } + +} +#endif + +int gst_cvf_mjpeg_warm_up_pipeline(struct gstreamer_pipeline *gst) +{ + GstBuffer *buffer; + GstMapInfo info; + GstState state = GST_STATE_NULL; + unsigned char *data_buf; + int splash_fd; + int rc = 0; + unsigned char *buf; + unsigned int frame_size, i, count; + unsigned long long gsttime = 0; + + printf("Warming up pipeline\n"); + + buf = malloc(CVF_MJPEG_SPLASH_MAX_FRAME_SIZE); + if (!buf) { + printf("Couldn't not allocate buffer to warm up Gstreamer pipeline\n"); + return -1; + } + + splash_fd = open(CVF_MJPEG_SPLASH_FILENAME, O_RDONLY); + if (splash_fd < 0) { + printf("Couldn't not warm up Gstreamer pipeline: %s(%d)\n", strerror(errno), errno); + rc = 0; + goto err_open; + } + + + // Wait for pipeline to become ready. + rc = gst_element_get_state(gst->pipeline, &state, NULL, 0); + while ((state != GST_STATE_PLAYING) && (state != GST_STATE_PAUSED)) { + usleep(10000); // 10ms + rc = gst_element_get_state(gst->pipeline, &state, NULL, 0); + + } + printf("Pipeline ready (state %d)\n", state); + + gsttime = gst_clock_get_time(gst->clock) - gst_element_get_base_time(GST_ELEMENT(gst->pipeline)); + + rc = read(splash_fd, &frame_size, 4); + if (rc != 4) { + printf("Short read while warming up Gstreamer pipeline (%d, expected %d)\n", rc, 4); + rc = -1; + goto exit; + } + + rc = read(splash_fd, buf, frame_size); + if (rc != frame_size) { + printf("Short read while warming up Gstreamer pipeline (%d, expected %d)\n", rc, frame_size); + rc = -1; + goto exit; + } + + count = 0; + while (read(splash_fd, &frame_size, 4) == 4) { + printf ("Reading frame %d size %d ts %llu base_time %" G_GUINT64_FORMAT " now %" G_GUINT64_FORMAT "\n", + count, frame_size, gsttime, gst_element_get_base_time(GST_ELEMENT(gst->pipeline)), gst_clock_get_time(gst->clock)); + rc = read(splash_fd, buf, frame_size); + if (rc != frame_size) { + printf("Short read while warming up Gstreamer pipeline (%d, expected %d)\n", rc, frame_size); + rc = -1; + goto exit; + } + + for (i = 0; i < gst->u.listener.num_sources; i++) { + /* Create a new empty buffer */ + buffer = gst_buffer_new_allocate(NULL, frame_size, NULL); + if (!buffer) { + printf("Couldn't allocate Gstreamer buffer\n"); + rc = -1; + goto exit; + } + + buffer = gst_buffer_make_writable(buffer); + + if (gst_buffer_map(buffer, &info, GST_MAP_WRITE)) + data_buf = info.data; + else + data_buf = NULL; + + if (!data_buf) { + printf("Couldn't get data buffer\n"); + + gst_buffer_unref(buffer); + + rc = -1; + goto exit; + } + + /* copy data from file + */ + memcpy(data_buf, buf, frame_size); + gst_buffer_unmap(buffer, &info); + gst_buffer_set_size(buffer, frame_size); + + + GST_BUFFER_PTS(buffer) = gsttime + SALSA_LATENCY; // Arbitrary delay + + rc = gst_app_src_push_buffer(gst->u.listener.source[i].source, buffer); + if (rc != GST_FLOW_OK) { + if (rc == GST_FLOW_FLUSHING) + printf("Pipeline not in PAUSED or PLAYING state\n"); + else + printf("End-of-Stream occurred\n"); + + rc = -1; + goto exit; + } else + rc = 1; + } + + gsttime += NSECS_PER_SEC / CVF_MJPEG_SPLASH_FPS; + usleep(USECS_PER_SEC / CVF_MJPEG_SPLASH_FPS); + count++; + } + + + +exit: + close(splash_fd); +err_open: + free(buf); + + return rc; +} + + +#define CVF_TS_VALID_WINDOW 500000000 // 500ms +int listener_gst_handler_cvf(struct gstreamer_stream *stream, unsigned int events) +{ + struct gstreamer_pipeline *gst = stream->pipe_source.gst_pipeline; + unsigned int event_len; + struct avb_event event[EVENT_BUF_SZ]; + int nbytes; + int rc = 1; + unsigned long long gsttime; + unsigned long long base_ts; + avb_s32 ts_diff; + unsigned int pushed = 0; + uint64_t start = 0, end = 0; + time_t tnow; + + tnow = time(NULL); + if ((tnow - stream->pipe_source.tlast) > 10) { + stream_dump_stats(stream); + stream->pipe_source.tlast = tnow; + } + + gettime_ns(&start); + + while (pushed < stream->batch_size) { + if (!(stream->pipe_source.count % 10000)) + printf("stream(%u): bytes: %llu\n", stream->source_index, stream->pipe_source.byte_count); + + stream->pipe_source.count++; + + /* Create a new empty buffer if needed */ + if (!stream->pipe_source.buffer) { + stream->pipe_source.buffer = gst_buffer_new(); + if (!stream->pipe_source.buffer) { + printf("stream(%u): Couldn't allocate Gstreamer buffer\n", stream->source_index); + //gst_memory_unref(memory); + rc = -1; + goto exit; + } + } + + if (!stream->pipe_source.memory) { + stream->pipe_source.memory = gst_allocator_alloc(NULL, GST_MEMORY_OBJ_SIZE, NULL); + if (!stream->pipe_source.memory) { + printf("stream(%u): Couldn't allocate Gstreamer memory object\n", stream->source_index); + gst_buffer_unref(stream->pipe_source.buffer); + rc = -1; + goto exit; + } + + if (gst_memory_map(stream->pipe_source.memory, &stream->pipe_source.info, GST_MAP_WRITE)) + stream->pipe_source.data_buf = stream->pipe_source.info.data; + else + stream->pipe_source.data_buf = NULL; + + if (!stream->pipe_source.data_buf) { + printf("stream(%u): Couldn't get data buffer\n", stream->source_index); + gst_buffer_unref(stream->pipe_source.buffer); + gst_memory_unref(stream->pipe_source.memory); + rc = -1; + goto exit; + } + + gst_buffer_append_memory(stream->pipe_source.buffer, stream->pipe_source.memory); + } + + stream->pipe_source.buffer = gst_buffer_make_writable(stream->pipe_source.buffer); + + + /* read data from stack... + * TODO: make a single call to genavb lib with an iovec of gst buffers + */ + event_len = EVENT_BUF_SZ; + nbytes = avb_stream_receive(stream->stream_h, stream->pipe_source.data_buf, GST_MEMORY_OBJ_SIZE - stream->pipe_source.memory_byte_count, event, &event_len); + //printf("Received %d bytes from genavb ... \n", nbytes); + + if (nbytes < 0) { + printf("stream(%u): avb_stream_receive() failed: %s\n", stream->source_index, avb_strerror(nbytes)); + + stream->pipe_source.buffer_byte_count += stream->pipe_source.memory_byte_count; + gst_memory_unmap(stream->pipe_source.memory, &stream->pipe_source.info); + gst_buffer_unref(stream->pipe_source.buffer); + stream->pipe_source.buffer_byte_count = 0; + stream->pipe_source.memory_byte_count = 0; + stream->pipe_source.buffer = NULL; + stream->pipe_source.memory = NULL; + + rc = nbytes; + goto exit; + } + if (nbytes == 0) { + goto exit; + } + + if (!stream->pipe_source.dropping) { + pushed += nbytes; + stream->pipe_source.data_buf += nbytes; + stream->pipe_source.memory_byte_count += nbytes; + } + + gsttime = gst_clock_get_time(gst->clock); + if (gsttime == gst->time) { + printf("ERROR: Clock jumped into the past, Gstreamer pipeline likely stalled...\n"); + //FIXME + } + gst->time = gsttime; + + /* FIXME ? Doesn't filter packets that are too late or too early, which usually occur when gptp has been disrupted*/ + if (event_len != 0) { + // printf("stream(%u) event_len(%d) mask0 0x%x maskn 0x%x size %d nbytes %d\n",stream->source_index, + // event_len, event[0].event_mask, event[event_len - 1].event_mask, stream->gst_stream.buffer_byte_count, nbytes); + + if (event[0].event_mask & AVTP_MEDIA_CLOCK_RESTART) { + stream->pipe_source.dropping = 1; + stream->stream_stats.mr++; + } + + if (event[0].event_mask & AVTP_PACKET_LOST) { + stream->pipe_source.dropping = 1; + stream->stream_stats.pkt_lost++; + } + + if (event[event_len - 1].event_mask & AVTP_END_OF_FRAME) { + stream->pipe_source.buffer_byte_count += stream->pipe_source.memory_byte_count; +#if CVF_MJPEG_SPLASH_CAPTURE + cvf_mjpeg_splash_capture(stream); +#endif + + gst_memory_unmap(stream->pipe_source.memory, &stream->pipe_source.info); + gst_memory_resize(stream->pipe_source.memory, 0, stream->pipe_source.memory_byte_count); + gst_buffer_set_size(stream->pipe_source.buffer, stream->pipe_source.buffer_byte_count); + + if (!(event[event_len - 1].event_mask & AVTP_TIMESTAMP_INVALID)) { + ts_diff = (avb_s32)event[0].ts - (avb_s32)(gsttime & 0xffffffff); + if ((ts_diff > -CVF_TS_VALID_WINDOW) && (ts_diff < CVF_TS_VALID_WINDOW)) { + base_ts = gsttime & 0xffffffff00000000; + GST_BUFFER_PTS(stream->pipe_source.buffer) = base_ts | event[0].ts; + /* Handle 32bit wrap */ + if (avtp_after(event[0].ts, gsttime & 0xffffffff)) { + /* Timestamp in the future */ + if ((event[0].ts < (gsttime & 0xffffffff))) + GST_BUFFER_PTS(stream->pipe_source.buffer) += 0x100000000ULL; + } else { + /* Timestamp in the past */ + if ((event[0].ts > (gsttime & 0xffffffff))) + GST_BUFFER_PTS(stream->pipe_source.buffer) -= 0x100000000ULL; + } + } else { + GST_BUFFER_PTS(stream->pipe_source.buffer) = gsttime; + stream->stream_stats.ts_err++; + } + } + + if (event[event_len - 1].event_mask & (AVTP_TIMESTAMP_INVALID | AVTP_TIMESTAMP_UNCERTAIN)) { + GST_BUFFER_PTS(stream->pipe_source.buffer) = gsttime; + stream->stream_stats.ts_err++; + } + + GST_BUFFER_PTS(stream->pipe_source.buffer) += stream->pipe_source.gst_pipeline->pts_offset + stream->pipe_source.gst_pipeline->local_pts_offset; + GST_BUFFER_PTS(stream->pipe_source.buffer) += (avb_u64)33333333- (GST_BUFFER_PTS(stream->pipe_source.buffer) % (avb_u64)33333333); + stream_update_stats(stream, stream->pipe_source.byte_count, gsttime, GST_BUFFER_PTS(stream->pipe_source.buffer)); + + if (!GST_CLOCK_TIME_IS_VALID(gst->basetime)) + gst->basetime = gst_element_get_base_time(GST_ELEMENT(gst->pipeline)); + + if (GST_BUFFER_PTS(stream->pipe_source.buffer) < gst->basetime) { + printf("stream(%u): frame in the past, dropping (frame %" G_GUINT64_FORMAT ", basetime %llu now %llu)\n", + stream->source_index, GST_BUFFER_PTS(stream->pipe_source.buffer), gst->basetime, gsttime); + stream->pipe_source.dropping = 1; + rc = 1; + } else { + GST_BUFFER_PTS(stream->pipe_source.buffer) -= gst->basetime; + } + + stream->pipe_source.byte_count += stream->pipe_source.buffer_byte_count; + + // printf("stream(%u): Handling frame with PTS %llu, basetime %llu now %llu size %d dropping %d)\n", + // stream->source_index, GST_BUFFER_PTS(stream->gst_stream.buffer), gst->basetime, gsttime, stream->gst_stream.buffer_byte_count, stream->gst_stream.dropping); + + if (!stream->pipe_source.dropping) { + GST_BUFFER_DURATION(stream->pipe_source.buffer) = GST_CLOCK_TIME_NONE; + + rc = gst_app_src_push_buffer(stream->pipe_source.source, stream->pipe_source.buffer); + //printf("Pushed %d bytes to Gstreamer ts %llu\n", stream->pipe_source.buffer_byte_count, GST_BUFFER_PTS(stream->pipe_source.buffer)); + + if (rc != GST_FLOW_OK) { + if (rc == GST_FLOW_FLUSHING) + printf("stream(%u): Pipeline not in PAUSED or PLAYING state\n", stream->source_index); + else + printf("stream(%u): End-of-Stream occurred\n", stream->source_index); + + rc = -1; + } else { + rc = 1; + } + } else { + gst_buffer_unref(stream->pipe_source.buffer); + stream->pipe_source.dropping = 0; + } + + stream->pipe_source.buffer_byte_count = 0; + stream->pipe_source.memory_byte_count = 0; + stream->pipe_source.buffer = NULL; + stream->pipe_source.memory = NULL; + + goto exit; // We reached an end-of-frame, so there is not much data left and we might as well go back to sleep until another full batch. + + } + } + + if (stream->pipe_source.memory_byte_count >= (GST_MEMORY_OBJ_SIZE - MAX_PKT_SIZE_CVF)) { + gst_memory_unmap(stream->pipe_source.memory, &stream->pipe_source.info); + gst_memory_resize(stream->pipe_source.memory, 0, stream->pipe_source.memory_byte_count); + stream->pipe_source.buffer_byte_count += stream->pipe_source.memory_byte_count; + stream->pipe_source.memory_byte_count = 0; + stream->pipe_source.memory = NULL; + printf("stream(%u) Current buffer byte count: %d\n", stream->source_index, stream->pipe_source.buffer_byte_count); + } + } + +exit: + gettime_ns(&end); + + stats_update(&stream->stream_stats.write_delay, end - start); + + gst_process_bus_messages(gst); + return rc; +} + +int listener_gst_handler(struct gstreamer_stream *stream, unsigned int events) +{ + struct gstreamer_pipeline *gst = stream->pipe_source.gst_pipeline; + unsigned int event_len; + GstBuffer *buffer; + GstMapInfo info; + unsigned char *data_buf; + struct avb_event event[EVENT_BUF_SZ]; + int nbytes; + int rc = 0; + unsigned long long base_ts; + unsigned long long gsttime; + unsigned int pushed = 0; + uint64_t start = 0, end = 0; + time_t tnow; + int delta; + + tnow = time(NULL); + if ((tnow - stream->pipe_source.tlast) > 10) { + stream_dump_stats(stream); + stream->pipe_source.tlast = tnow; + } + + gettime_ns(&start); + + while (pushed != stream->batch_size) { + if (!(stream->pipe_source.count % 10000)) + printf("bytes: %llu\n", stream->pipe_source.byte_count); + + stream->pipe_source.count++; + + /* Create a new empty buffer */ + buffer = gst_buffer_new_allocate(NULL, stream->frame_size, NULL); + if (!buffer) { + printf("Couldn't allocate Gstreamer buffer\n"); + rc = -1; + goto exit; + } + + buffer = gst_buffer_make_writable(buffer); + + if (gst_buffer_map(buffer, &info, GST_MAP_WRITE)) + data_buf = info.data; + else + data_buf = NULL; + + if (!data_buf) { + printf("Couldn't get data buffer\n"); + + gst_buffer_unref(buffer); + + rc = -1; + goto exit; + } + + /* read data from stack... + * TODO: make a single call to genavb lib with an iovec of gst buffers + */ + event_len = EVENT_BUF_SZ; + nbytes = avb_stream_receive(stream->stream_h, data_buf, stream->frame_size, event, &event_len); + gst_buffer_unmap(buffer, &info); + if (nbytes <= 0) { + if (nbytes < 0) + printf("avb_stream_receive() failed: %s\n", avb_strerror(nbytes)); + else + printf("avb_stream_receive() incomplete\n"); + + gst_buffer_unref(buffer); + + rc = nbytes; + goto exit; + } + if (nbytes != stream->frame_size) + printf("Short read: GenAVB returned less data (%d) than requested (%d).\n", nbytes, stream->frame_size); + + gst_buffer_set_size(buffer, nbytes); + + if (event[0].event_mask & AVTP_MEDIA_CLOCK_RESTART) + printf ("AVTP media clock restarted\n"); + + if (event[0].event_mask & AVTP_PACKET_LOST) + printf ("AVTP packet lost\n"); + + if (!(event[0].event_mask & (AVTP_TIMESTAMP_INVALID | AVTP_TIMESTAMP_UNCERTAIN))) { + gsttime = gst_clock_get_time(gst->clock); + if (gsttime == gst->time) { + printf("Error: clock likely jumped into the past, restarting pipeline\n"); + gst_buffer_unref(buffer); + rc = -1; + goto exit; + } else + gst->time = gsttime; + base_ts = gsttime & 0xffffffff00000000; + + if (!GST_CLOCK_TIME_IS_VALID(gst->basetime)) + gst->basetime = gst_element_get_base_time(GST_ELEMENT(gst->pipeline)); + + delta = (int)event[0].ts - (int)(gsttime & 0xffffffff); + + /* Filter packets that are too late or too early, which usually occur when gptp has been disrupted */ + if ((stream->pipe_source.dropping && ((delta < -AVTP_TS_MIN_DELTA) || (delta > AVTP_TS_MAX_DELTA))) || + (!stream->pipe_source.dropping && ((delta < -AVTP_TS_MIN_DELTA_STOP) || (delta > AVTP_TS_MAX_DELTA_STOP))) + ) { + stream->pipe_source.late_count++; + stream->pipe_source.ontime_count = 0; + + if (!stream->pipe_source.dropping) { + printf("stop playing, late buffer %u %u %d\n", event[0].ts, (unsigned int)(gsttime & 0xffffffff), delta); + + stream->pipe_source.dropping = 1; + } + + gst_buffer_unref(buffer); + + goto exit; + } + + stream->pipe_source.ontime_count++; + + if (stream->pipe_source.dropping) { + if (stream->pipe_source.ontime_count > 1000) { + printf("start playing %llu\n", stream->pipe_source.late_count); + + stream->pipe_source.late_count = 0; + stream->pipe_source.dropping = 0; + } else { + gst_buffer_unref(buffer); + + goto exit; + } + } + + // TODO Handle event.index + GST_BUFFER_PTS(buffer) = base_ts | event[0].ts; + + /* Handle 32bit wrap */ + if (avtp_after(event[0].ts, gsttime & 0xffffffff)) { + /* Timestamp in the future */ + if ((event[0].ts < (gsttime & 0xffffffff))) + GST_BUFFER_PTS(buffer) += 0x100000000ULL; + } else { + /* Timestamp in the past */ + if ((event[0].ts > (gsttime & 0xffffffff))) + GST_BUFFER_PTS(buffer) -= 0x100000000ULL; + } + + stream_update_stats(stream, stream->pipe_source.byte_count + event[0].index, gsttime, GST_BUFFER_PTS(buffer)); + + if (avdecc_format_is_61883_4(&stream->params.format)) + stream_61883_4_update_stats(stream, data_buf, GST_BUFFER_PTS(buffer)); + + GST_BUFFER_PTS(buffer) += stream->pipe_source.gst_pipeline->local_pts_offset; + + GST_BUFFER_PTS(buffer) -= gst->basetime; + } + + // TODO: with Gstreamer 1.x, pass a list of buffers in a single call + rc = gst_app_src_push_buffer(stream->pipe_source.source, buffer); + if (rc != GST_FLOW_OK) { + if (rc == GST_FLOW_FLUSHING) + printf("Pipeline not in PAUSED or PLAYING state\n"); + else + printf("End-of-Stream occurred\n"); + + rc = -1; + goto exit; + } else + rc = 1; + + pushed += nbytes; + stream->pipe_source.byte_count += nbytes; + } +exit: + gettime_ns(&end); + + stats_update(&stream->stream_stats.write_delay, end - start); + + gst_process_bus_messages(gst); + + if (rc == 0) + rc = pushed; + return rc; +} + +void apply_config(struct gstreamer_stream *stream, struct avb_stream_params *stream_params) +{ + memcpy(&stream->params, stream_params, sizeof(struct avb_stream_params)); + stream->params.clock_domain = AVB_MEDIA_CLOCK_DOMAIN_STREAM; + + stream->flags = AVTP_NONBLOCK; + + if (!avdecc_format_is_61883_6(&stream_params->format)) + stream->params.flags &= ~AVB_STREAM_FLAGS_MCR; + + switch (stream_params->format.u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + stream->batch_size = BATCH_SIZE; + if (stream_params->format.u.s.subtype_u.iec61883.sf == 0) { + printf("Unsupported 61883_IIDC format\n"); + break; + } else + switch (stream_params->format.u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_6: + stream->frame_size = stream->batch_size; + break; + + case IEC_61883_CIP_FMT_4: + stream->frame_size = avdecc_fmt_sample_size(&stream_params->format); + break; + + case IEC_61883_CIP_FMT_8: + default: + printf("Unsupported IEC-61883 format: %d\n", stream_params->format.u.s.subtype); + break; + } + + stream->pipe_source.dropping = 0; + stream->listener_gst_handler = listener_gst_handler; + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + stream->batch_size = BATCH_SIZE_CVF; + stream->pipe_source.dropping = 1; // Always start by dropping frames, to ensure the data passed to Gstreamer starts on a frame boundary (by waiting for an EOF event) + stream->listener_gst_handler = listener_gst_handler_cvf; + break; +#endif + + default: + printf("Unsupported AVTP subtype: %d\n", stream_params->format.u.s.subtype); + break; + } + /* FIX ME: PTS offset can't be too small because CVF case for camera. Need to be fixed if audio/video mode implemented + if (stream->gst_stream.gst->gst.pts_offset < (stream->gst_stream.gst->gst.pipeline_latency + stream->gst_stream.local_pts_offset)) { + stream->gst_stream.gst->gst.pts_offset = stream->gst_stream.gst->gst.pipeline_latency + stream->gst_stream.local_pts_offset; + printf("Warning: PTS offset too small, resetting to %lld ns.\n", stream->gst_stream.gst->gst.pipeline_latency + stream->gst_stream.local_pts_offset); + } + stream->gst_stream.gst->gst.pts_offset -= stream->gst_stream.local_pts_offset; + */ + stream->pipe_source.buffer = NULL; + stream->pipe_source.memory = NULL; + stream->pipe_source.buffer_byte_count = 0; + stream->pipe_source.memory_byte_count = 0; +} diff --git a/apps/linux/genavb-video-player-app/gstreamer_single.h b/apps/linux/genavb-video-player-app/gstreamer_single.h new file mode 100644 index 0000000..edbaddb --- /dev/null +++ b/apps/linux/genavb-video-player-app/gstreamer_single.h @@ -0,0 +1,140 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _GSTREAMER_SINGLE_H_ +#define _GSTREAMER_SINGLE_H_ + +#include +#include +#include + +#include +#include "../common/stats.h" +#include "../common/time.h" +#include "../common/ts_parser.h" +#include "gstreamer.h" +#include "gst_pipelines.h" +#include "../common/common.h" +#include "genavb/avdecc.h" + +#define FILENAME_SIZE SCHAR_MAX + +// We currently need a low batch size to improve timestamping and synchronization accuracy, +//so we set it to the minimum accepted value +#define BATCH_SIZE 2048 // in bytes +#define BATCH_SIZE_CVF 65536 //131072 //in bytes +#define MAX_PKT_SIZE_CVF 2000 + +#define AVTP_TS_MIN_DELTA 50000000 +#define AVTP_TS_MAX_DELTA 50000000 +#define AVTP_TS_MIN_DELTA_STOP 150000000 +#define AVTP_TS_MAX_DELTA_STOP 50000000 + +#define GST_MEMORY_OBJ_SIZE (300000) + +#define GST_PRIORITY 1 /* RT_FIFO priority to be used for the process */ + +/** + * Generic gstreamer stream parameters + */ +struct gstreamer_pipeline_source { + struct gstreamer_pipeline *gst_pipeline; + + unsigned long long byte_count; + unsigned int count; + unsigned long long late_count; + unsigned long long ontime_count; + + unsigned int dropping; + unsigned int pipeline_state; + + GstBuffer *buffer; + GstMapInfo info; + GstMemory *memory; + unsigned char *data_buf; + GstAppSrc *source; + + int buffer_byte_count; + int memory_byte_count; + + time_t tlast; +}; + +/** + * Generic mpeg_ts stats + */ +struct mpeg_ts_stats { + + struct stats pcr_delay; + struct stats pcr_period; + unsigned long long pcr_prev; +}; + +/** + * Generic stream stats + */ +struct gstreamer_stats { + + unsigned long long byte_count_prev; + unsigned long long buffer_pts_prev; + + unsigned long long ts_err; + unsigned long long pkt_lost; + unsigned long long mr; + + struct stats write_delay; + struct stats delay; + struct stats period; + struct stats rate; + + struct mpeg_ts_stats mpeg_ts; +}; + +struct gstreamer_stream { + + unsigned int source_index; + + struct avb_stream_handle *stream_h; + + unsigned int created; + void *thread; + + unsigned int state; + int started; + int stream_fd; + + struct avb_stream_params params; + unsigned int batch_size; + unsigned int frame_size; + unsigned int flags; + + struct gstreamer_pipeline_source pipe_source; + struct gstreamer_stats stream_stats; + struct ts_parser ts_parser; + + char media_file_name[FILENAME_SIZE]; //not same path in video-player + int media_fd; + + int (*listener_gst_handler)(struct gstreamer_stream *stream, unsigned int events); +}; + +void stream_init_stats(struct gstreamer_stream *stream); +void stream_update_stats(struct gstreamer_stream *stream, unsigned long long byte_count, unsigned long long now, unsigned long long buffer_pts); +void stream_dump_stats(struct gstreamer_stream *stream); +void stream_61883_4_update_stats(struct gstreamer_stream *stream, unsigned char *buf, unsigned long long buffer_pts); +void dump_stream_infos(struct gstreamer_stream *stream); +int listener_gst_handler_cvf(struct gstreamer_stream *stream, unsigned int events); +int listener_gst_handler(struct gstreamer_stream *stream, unsigned int events); +void apply_config(struct gstreamer_stream *stream, struct avb_stream_params *stream_params); + + +#define CVF_MJPEG_SPLASH_FILENAME "/home/media/cvf_splash_screen.mjpg" +#define CVF_MJPEG_SPLASH_MAX_FRAME_SIZE 60000 +#define CVF_MJPEG_SPLASH_FPS 30 +int gst_cvf_mjpeg_warm_up_pipeline(struct gstreamer_pipeline *gst); + +#endif /* _GSTREAMER_SINGLE_H_ */ diff --git a/apps/linux/genavb-video-player-app/main.c b/apps/linux/genavb-video-player-app/main.c new file mode 100644 index 0000000..ac2ed94 --- /dev/null +++ b/apps/linux/genavb-video-player-app/main.c @@ -0,0 +1,873 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../common/common.h" +#include "../common/stats.h" +#include "../common/time.h" +#include "../common/ts_parser.h" +#include "gstreamer.h" +#include "gst_pipelines.h" +#include "gstreamer_single.h" + +//#define POLL_STATS + +#define OPT_MODE_STREAMING (1 << 0) +#define OPT_MODE_LOCAL_FILE (1 << 1) + +#define OPT_TYPE_VIDEO (1 << 0) +#define OPT_TYPE_AUDIO (1 << 1) +#define OPT_TYPE_DEBUG (1 << 2) +#define OPT_TYPE_CAMERA (1 << 4) + + +#define APP_PRIORITY 1 /* RT_FIFO priority to be used for the process */ +#define GST_PRIORITY 1 /* RT_FIFO priority to be used for the process */ + + +#ifdef CFG_AVTP_1722A +struct avb_stream_params salsa_camera_stream_params = { + .direction = AVTP_DIRECTION_LISTENER, + .subtype = AVTP_SUBTYPE_CVF, + .stream_class = SR_CLASS_B, + .clock_domain = AVB_MEDIA_CLOCK_DOMAIN_STREAM, + .flags = 0, + .format.u.s = { + .v = 0, + .subtype = AVTP_SUBTYPE_CVF, + .subtype_u.cvf = { + .format = CVF_FORMAT_RFC, + .subtype = CVF_FORMAT_SUBTYPE_MJPEG, + .format_u.mjpeg = { + .p = CVF_MJPEG_P_PROGRESSIVE, + .type = CVF_MJPEG_TYPE_YUV420, + .width = 160, + .height = 100, + }, + }, + }, + .port = 0, + .stream_id = { 0x00, 0x00, 0x00, 0x04, 0x9f, 0x00, 0x4a, 0x50 }, + .dst_mac = { 0x91, 0xe0, 0xf0, 0x00, 0x06, 0x00 }, +}; +#endif + +struct media_app { + unsigned int mode; + unsigned int config; + char *media_file_name; + struct avb_control_handle *ctrl_h; + + struct gstreamer_pipeline gst_pipeline; + + char *device; + unsigned int width; + unsigned int height; + unsigned int type; + + unsigned long long pts_offset; + + struct stats poll_delay; + + struct gstreamer_stream stream; +}; + +static int signal_terminate = 0; + +#ifdef POLL_STATS +void poll_stats_show(struct stats *s) +{ + printf("delay (us): poll: %7d / %7d / %7d\n", + s->min, s->mean, s->max); +} +#endif + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\nOptions:\n" + "\t-v video rendering\n" + "\t-a audio rendering\n" + "\t-d display device (lvds (default), hdmi)\n" + "\t-s scaling (1080, 720, 768 (default), 480)\n" + "\t-p media stack presentation time offset (in ns)\n" + "\t-f read media from local file (no streaming)\n" +#ifdef CFG_AVTP_1722A + "\t-S Salsa camera mode, static stream configuration\n" +#endif + "\t-c use the specified custom gstreamer pipeline\n" + "\t instead of the hard-coded ones. Must be the\n" + "\t last option on the command-line, overrides\n" + "\t all other options except -f.\n" + "\t-h print this help text\n"); + printf("\nDefault: audio and video (768p lvds) in streaming mode\n"); +} + + +static void dump_gst_config(const struct media_app *app) +{ + printf("\nPLAYOUT TYPE: "); + if (app->type & OPT_TYPE_VIDEO) + printf("VIDEO "); + if (app->type & OPT_TYPE_AUDIO) + printf("AUDIO "); + + printf("\nDISPLAY DEVICE: %s", app->device); + + printf("\nDISPLAY SCALING: %dx%d\n", app->width, app->height); + + printf("\nPresentation offset: %llu ns\n", app->stream.pipe_source.gst_pipeline->pts_offset + app->stream.pipe_source.gst_pipeline->local_pts_offset); + + if (app->mode == OPT_MODE_LOCAL_FILE) + printf("\nMEDIA FILE: %s\n", app->media_file_name); +} + + +static void set_avb_config(unsigned int *avb_flags) +{ + *avb_flags = 0; +} + +static int handle_avdecc_event(struct avb_control_handle *ctrl_h, unsigned int *msg_type, union avb_media_stack_msg *msg) +{ + unsigned int msg_len = sizeof(union avb_media_stack_msg); + int rc; + + rc = avb_control_receive(ctrl_h, msg_type, msg, &msg_len); + if (rc != AVB_SUCCESS) + goto receive_error; + + switch (*msg_type) { + case AVB_MSG_MEDIA_STACK_CONNECT: + printf("\nevent: AVB_MSG_MEDIA_STACK_CONNECT\n"); + break; + + case AVB_MSG_MEDIA_STACK_DISCONNECT: + printf("\nevent: AVB_MSG_MEDIA_STACK_DISCONNECT\n"); + break; + + default: + break; + } + +receive_error: + return rc; +} + + +static int open_media_file(char *media_file_name) +{ + int media_fd = 0; + + media_fd = open(media_file_name, O_RDONLY); + if (media_fd < 0) + printf("open(%s) failed: %s\n", media_file_name, strerror(errno)); + + return media_fd; +} + + +static int read_media_file (int media_fd, struct gstreamer_pipeline *gst) +{ + GstBuffer *buffer; + GstMapInfo info; + unsigned char *data_buf; + int nbytes; + int rc = 0; + static int count = 0; + + while (1) { + if (signal_terminate) { + printf("processing terminate signal\n"); + rc = -1; + goto exit; + } + + /* Create a new empty buffer */ + buffer = gst_buffer_new_allocate(NULL, BATCH_SIZE, NULL); + if (!buffer) { + printf("Couldn't allocate Gstreamer buffer\n"); + rc = -1; + goto exit; + } + + buffer = gst_buffer_make_writable(buffer); + + if (gst_buffer_map(buffer, &info, GST_MAP_WRITE)) + data_buf = info.data; + else + data_buf = NULL; + + if (!data_buf) { + printf("Couldn't get data buffer\n"); + rc = -1; + goto exit; + } + + nbytes = read(media_fd, data_buf, BATCH_SIZE); + gst_buffer_unmap(buffer, &info); + /* no more data to read, we are done*/ + if (nbytes <= 0) { + if (nbytes < 0) + printf("read() failed: %s\n", strerror(errno)); + + gst_buffer_unref(buffer); + + goto exit; + } + + gst_buffer_set_size(buffer, nbytes); + + /* Send data to Gstreamer pipeline */ + rc = gst_app_src_push_buffer(gst->u.listener.source[0].source, buffer); + if (rc != GST_FLOW_OK) { + if (rc == GST_FLOW_FLUSHING) + printf("Pipeline not in PAUSED or PLAYING state\n"); + else + printf("End-of-Stream occurred\n"); + + goto exit; + } + + count += nbytes; + } + +exit: + if (rc == 0) + gst_process_bus_messages(gst); + + return rc; +} + + +static int apply_gst_config(struct gstreamer_stream *stream, struct media_app *app) +{ + struct gstreamer_pipeline *gst_pipeline = stream->pipe_source.gst_pipeline; + const struct avdecc_format *format = &stream->params.format; + int rc = 0; + + gst_pipeline->local_pts_offset = 0; + + /* apply requested gstreamer pipeline */ + +#if 0 + gst_pipeline->pipeline_string = pipeline_listener_debug; + printf("pipeline_listener_debug selected\n"); +#else + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + if (format->u.s.subtype_u.iec61883.sf == 0) { + printf("Unsupported 61883_IIDC format\n"); + return -1; + } else + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_6: + gst_pipeline->pipeline_string = pipeline_listener_61883_6; + gst_pipeline->pipeline_latency = latency_61883_6; + stream->frame_size = stream->batch_size; + printf("pipeline_listener_61883_6 selected\n"); + break; + + case IEC_61883_CIP_FMT_4: + stream->frame_size = avdecc_fmt_sample_size(format); + + if (app->type == OPT_TYPE_VIDEO) { + gst_pipeline->pipeline_string = pipeline_listener_61883_4_video_only; + gst_pipeline->pipeline_latency = latency_61883_4_video_only; + printf("pipeline_listener_61883_4_video_only selected\n"); + } + else if (app->type == OPT_TYPE_AUDIO) { + gst_pipeline->pipeline_string = pipeline_listener_61883_4_audio_only; + gst_pipeline->pipeline_latency = latency_61883_4_audio_only; + printf("pipeline_61883_4_audio_only selected\n"); + } + else { + gst_pipeline->pipeline_string = pipeline_listener_61883_4_audio_video; + gst_pipeline->pipeline_latency = latency_61883_4_audio_video; + printf("pipeline_listener_61883_4_audio_video selected\n"); + } + + break; + + case IEC_61883_CIP_FMT_8: + default: + printf("Unsupported IEC-61883 format: %d\n", format->u.s.subtype); + return -1; + } + + stream->pipe_source.dropping = 0; + gst_pipeline->local_pts_offset = LOCAL_PTS_OFFSET; + + if (app->pts_offset == GST_CLOCK_TIME_NONE) + gst_pipeline->pts_offset = DEFAULT_PTS_OFFSET; + else + gst_pipeline->pts_offset = app->pts_offset; + + stream->listener_gst_handler = listener_gst_handler; + + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + if (format->u.s.subtype_u.cvf.format == CVF_FORMAT_RFC) { + switch (format->u.s.subtype_u.cvf.subtype) { + case CVF_FORMAT_SUBTYPE_MJPEG: + gst_pipeline->pipeline_string = pipeline_listener_cvf_mjpeg; + gst_pipeline->pipeline_latency = latency_cvf_mjpeg; + + printf("pipeline_listener_cvf_mjpeg selected\n"); + break; + + case CVF_FORMAT_SUBTYPE_H264: + case CVF_FORMAT_SUBTYPE_JPEG2000: + default: + printf("Unsupported CVF subtype: %d\n", format->u.s.subtype_u.cvf.subtype); + return -1; + } + } else { + printf("Unsupported CVF format: %d\n", format->u.s.subtype_u.cvf.format); + return -1; + } + + stream->pipe_source.dropping = 1; + gst_pipeline->local_pts_offset = CVF_PTS_OFFSET; + + if (app->pts_offset == GST_CLOCK_TIME_NONE) + gst_pipeline->pts_offset = CVF_PTS_OFFSET; + else + gst_pipeline->pts_offset = app->pts_offset; + + stream->listener_gst_handler = listener_gst_handler_cvf; + + break; +#endif + + default: + printf("Unsupported AVTP subtype: %d\n", format->u.s.subtype); + return -1; + } +#endif + + if (gst_pipeline->pts_offset < (gst_pipeline->pipeline_latency + gst_pipeline->local_pts_offset)) { + gst_pipeline->pts_offset = gst_pipeline->pipeline_latency + gst_pipeline->local_pts_offset; + printf("Warning: PTS offset too small, resetting to %lld ns.\n", gst_pipeline->pipeline_latency + gst_pipeline->local_pts_offset); + } + + gst_pipeline->pts_offset -= gst_pipeline->local_pts_offset; + + stream->pipe_source.buffer = NULL; + stream->pipe_source.memory = NULL; + + stream->pipe_source.buffer_byte_count = 0; + stream->pipe_source.memory_byte_count = 0; + + gst_pipeline->device = app->device; + gst_pipeline->overlay_height = app->height; + gst_pipeline->overlay_width = app->width; + gst_pipeline->crop_height = 0; + gst_pipeline->crop_width = 0; + + gst_pipeline->u.listener.num_sources = 1; + + dump_gst_config(app); + + return rc; +} + + +static void signal_terminate_handler (int signal_num) +{ + signal_terminate = 1; +} + + +#define LISTENER_POLL_DELAY_MS 100 +#define LISTENER_POLL_RETRIES 20 /* Retry polling 20 times before considering end-of-stream has been reached */ + +static int run_listener(struct media_app *app) +{ + struct pollfd poll_fds[2]; + int rc = 0; + int ctrl_rx_fd = -1; + int ready, i, n, nfds; + unsigned int event_type; + unsigned int failed_count = 0; + union avb_media_stack_msg msg; + struct gstreamer_stream *stream = &app->stream; +#ifdef POLL_STATS + uint64_t poll_time = 0; + uint64_t now = 0; + unsigned char poll_done = 0; + + stats_init(&app->poll_delay, 7, &app, poll_stats_show); +#endif + + printf("Starting listener loop, non-blocking mode, timeout %dms\n", LISTENER_POLL_DELAY_MS); + + stream_init_stats(stream); + + /* + * listen to read event from the stack + */ + + nfds = 0; + + poll_fds[0].fd = stream->stream_fd; + poll_fds[0].events = POLLIN; + poll_fds[0].revents = 0; + nfds++; + + if (app->type != OPT_TYPE_CAMERA) { + ctrl_rx_fd = avb_control_rx_fd(app->ctrl_h); + poll_fds[1].fd = ctrl_rx_fd; + poll_fds[1].events = POLLIN; + poll_fds[1].revents = 0; + nfds++; + } + + while (1) { + if (signal_terminate) { + printf("processing terminate signal\n"); + rc = -1; + goto exit; + } + +#ifdef POLL_STATS + if (poll_done) { + gettime_us(&poll_time); + poll_done = 0; + } +#endif + if ((ready = poll(poll_fds, nfds, LISTENER_POLL_DELAY_MS)) == -1) { + if (errno == EINTR) { + continue; + } else { + printf("poll(%d) failed while processing listener errno %d: %s\n", stream->stream_fd, errno, strerror(errno)); + rc = -1; + goto exit; + } + } + +#ifdef POLL_STATS + poll_done = 1; + if (poll_time) { + gettime_us(&now); + stats_update(&app->poll_delay, (now - poll_time)); + } +#endif + if (ready > 0) { + for (n = 0, i = 0; i < nfds && n < ready; i++) { + if (poll_fds[i].revents & POLLIN) { + if (poll_fds[i].fd == ctrl_rx_fd) { + n++; + + /* + * read control event from avdecc + */ + if (handle_avdecc_event(app->ctrl_h, &event_type, &msg) == AVB_SUCCESS) { + if (event_type == AVB_MSG_MEDIA_STACK_DISCONNECT) { + rc = 0; + goto exit; /* disconnected, stop processing on this stream */ + } + } + } else if (poll_fds[i].fd == stream->stream_fd) { + n++; + + stream->started = 1; + + /* + * read data from avb stack and write back to gstreamer pipeline + */ + rc = stream->listener_gst_handler(stream, POLLIN); + if (rc < 0) + goto exit; + } + } + } + } else { + if (stream->started) { + /* some data already received, likely end of stream, flush pending data */ + rc = stream->listener_gst_handler(stream, 0); + + if ( rc <= 0) { + failed_count++; + if (failed_count > LISTENER_POLL_RETRIES) + goto exit; + } + + } else { + /* no data received so far, please wait...*/ + continue; + } + } + } +exit: + listener_stream_flush(stream->stream_h); + + return rc; +} + + +int main(int argc, char *argv[]) +{ + struct media_app app; + struct avb_handle *avb_h; + union avb_media_stack_msg msg; + unsigned int avb_flags; + int option; + unsigned int event_type; + int ctrl_rx_fd; + struct pollfd ctrl_poll; + int media_fd; + int rc = 0; + struct sched_param param = { + .sched_priority = APP_PRIORITY, + }; + struct sigaction action; + + setlinebuf(stdout); + + printf("NXP's GenAVB reference video player application\n"); + + app.stream.pipe_source.gst_pipeline = &app.gst_pipeline; + memset(&app.gst_pipeline, 0, sizeof(app.gst_pipeline)); + gstreamer_init(); + + /* + * Increase process priority to match the AVTP thread priority + */ + + if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) { + printf("sched_setscheduler(), %s\n", strerror(errno)); + rc = -1; + goto exit; + } + + /* + * retrieve user's configuration parameters + */ + + app.type = 0; + app.device = LVDS_DEVICE_FILE; + app.width = 0; + app.height = 0; + app.mode = OPT_MODE_STREAMING; + app.pts_offset = GST_CLOCK_TIME_NONE; + app.stream.source_index = 0; + +#ifdef CFG_AVTP_1722A + while ((option = getopt(argc, argv,"vaSd:s:f:hp:t:")) != -1) { +#else + while ((option = getopt(argc, argv,"vad:s:f:hp:t:")) != -1) { +#endif + switch (option) { + case 'p': + if (h_strtoull(&app.pts_offset, optarg, NULL, 0) < 0) + goto exit; + + if (app.pts_offset > MAX_PTS_OFFSET) + app.pts_offset = MAX_PTS_OFFSET; + break; + + case 'v': + app.type |= OPT_TYPE_VIDEO; + break; + + case 'a': + app.type |= OPT_TYPE_AUDIO; + break; + + case 'f': + app.media_file_name = optarg; + app.mode = OPT_MODE_LOCAL_FILE; + break; + + case 'd': + if (!strcasecmp(optarg, "lvds")) + app.device = LVDS_DEVICE_FILE; + else if (!strcasecmp(optarg, "hdmi")) + app.device = HDMI_DEVICE_FILE; + else { + usage(); + goto exit; + } + + break; + + case 's': + if (!strcasecmp(optarg, "1080")) { + app.width = 1920; + app.height = 1080; + } else if (!strcasecmp(optarg, "720")) { + app.width = 1280; + app.height = 720; + } else if (!strcasecmp(optarg, "768")) { + app.width = 1024; + app.height = 768; + } else if (!strcasecmp(optarg, "480")) { + app.width = 640; + app.height = 480; + } else { + usage(); + goto exit; + } + + break; +#ifdef CFG_AVTP_1722A + case 'S': + app.type = OPT_TYPE_CAMERA; + break; +#endif + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + + } + + /* + * set signals handler + */ + action.sa_handler = signal_terminate_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGTERM, &action, NULL) < 0) /* Termination signal */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGQUIT, &action, NULL) < 0) /* Quit from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGINT, &action, NULL) < 0) /* Interrupt from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + /* + * apply default configuration + */ + if (!app.type) + app.type = OPT_TYPE_VIDEO | OPT_TYPE_AUDIO; + + + /* + * setup avb stack + */ + if (app.mode == OPT_MODE_STREAMING) { + printf("Running in streaming mode\n"); + + set_avb_config(&avb_flags); + + rc = avb_init(&avb_h, avb_flags); + if (rc != AVB_SUCCESS) { + printf("avb_init() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + if(app.type != OPT_TYPE_CAMERA) { + /* + * listen to avdecc events to get stream parameters + */ + + rc = avb_control_open(avb_h, &app.ctrl_h, AVB_CTRL_AVDECC_MEDIA_STACK); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() failed: %s\n", avb_strerror(rc)); + goto error_control_open; + } + + ctrl_rx_fd = avb_control_rx_fd(app.ctrl_h); + ctrl_poll.fd = ctrl_rx_fd; + ctrl_poll.events = POLLIN; + } + +wait_new_stream: + printf("\nwait for new stream...\n"); + + if(app.type != OPT_TYPE_CAMERA) { + while (1) { + + if (poll(&ctrl_poll, 1, -1) == -1) { + printf("poll(%d) failed on waiting for connect\n", ctrl_poll.fd); + rc = -1; + goto error_ctrl_poll; + } + + if (ctrl_poll.revents & POLLIN) { + /* + * read control event from avdecc + */ + if (handle_avdecc_event(app.ctrl_h, &event_type, &msg) == AVB_SUCCESS) { + if (event_type == AVB_MSG_MEDIA_STACK_CONNECT) { + break; /* connected, start stream processing */ + } + } + } + } + + /* + * setup the stream + */ + + apply_config(&app.stream, &msg.media_stack_connect.stream_params); + } else { +#ifdef CFG_AVTP_1722A + apply_config(&app.stream, &salsa_camera_stream_params); + if (system("salsa-camera.sh start")) { + printf("Couldn't start Salsa camera, aborting.\n"); + goto error_start_salsa_camera; + } + +#endif + } + + rc = avb_stream_create (avb_h, &app.stream.stream_h, &app.stream.params, &app.stream.batch_size, app.stream.flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", app.stream.batch_size); + + /* + * retrieve the file descriptor associated to the stream + */ + app.stream.stream_fd = avb_stream_fd(app.stream.stream_h); + if (app.stream.stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(app.stream.stream_fd)); + rc = -1; + goto error_stream_fd; + } + + apply_gst_config(&app.stream, &app); + +gst_restart: + /* + * setup and kick-off gstreamer pipeline + */ + if (gst_start_pipeline(&app.gst_pipeline, GST_PRIORITY, GST_DIRECTION_LISTENER) < 0) { + printf("start_gst_pipeline() failed\n"); + rc = -1; + goto error_gst_pipeline; + } + app.stream.pipe_source.source = app.gst_pipeline.u.listener.source[0].source; + + /* + * handle media data from avb stack + */ + rc = run_listener(&app); + + gst_stop_pipeline(&app.gst_pipeline); + + /* + * main processing loop exited. could be due to error or avdecc disconnect + */ + if (rc < 0) { + printf("Loop function exited with error code %d\n", rc); + + /* kill child process upon parent termination */ + if (signal_terminate) { + printf("stop gst pipeline\n"); + } else { + printf("restart gst pipeline\n"); + goto gst_restart; + } + } else { + if (rc) { + printf("Loop function exited upon avdecc disconnect\n"); + avb_stream_destroy(app.stream.stream_h); + goto wait_new_stream; + } else { + printf("Loop function exited upon end of media (received %llu bytes)\n", app.stream.pipe_source.byte_count); + goto gst_restart; + } + } + } else if (app.mode == OPT_MODE_LOCAL_FILE) { + printf("Running in local file mode\n"); + +gst_restart_local: + /* + * no avb streaming, gstreamer feed from local media file + */ + if ((media_fd = open_media_file(app.media_file_name)) < 0) { + rc = -1; + goto error_media_file; + } + + /* + * setup and kick-off gstreamer pipeline + */ + if (gst_start_pipeline(&app.gst_pipeline, GST_PRIORITY, GST_DIRECTION_LISTENER) < 0) { + printf("start_gst_pipeline() failed\n"); + rc = -1; + goto exit; + } + + + read_media_file(media_fd, app.stream.pipe_source.gst_pipeline); + + close(media_fd); + + gst_stop_pipeline(&app.gst_pipeline); + + if (!signal_terminate) + goto gst_restart_local; + } + else { + printf("Unknown app mode\n"); + goto exit; + } + /* + * destroy the stream, clean-up avb stack, close gst pipeline... + */ + +error_media_file: + /* local file mode, just quit application once we are done */ + if (app.mode == OPT_MODE_LOCAL_FILE) + goto exit; + +error_gst_pipeline: +error_stream_fd: + avb_stream_destroy(app.stream.stream_h); + +error_start_salsa_camera: +error_stream_create: +error_ctrl_poll: + if (app.ctrl_h) + avb_control_close(app.ctrl_h); + +error_control_open: + avb_exit(avb_h); + +error_avb_init: +exit: + gstreamer_reset(); + + return rc; +} diff --git a/apps/linux/genavb-video-player-app/salsa-camera.sh b/apps/linux/genavb-video-player-app/salsa-camera.sh new file mode 100755 index 0000000..070c723 --- /dev/null +++ b/apps/linux/genavb-video-player-app/salsa-camera.sh @@ -0,0 +1,31 @@ +#! /bin/sh + +# Use eth0:1 to avoid changing any already assigned IP, use a /30 netmask to limit risks of IP/routing conflicts with main IP address. +# Timeout wget requests after 1 try and 1 second waits. +# wget will return 1 in case of failure, 0 for success (this return code can be checked by the app calling the script). + +case "$1" in +stop | start) + sleep 1 + ifconfig eth0:1 192.168.1.1 netmask 255.255.255.252 + wget http://192.168.1.2/?stop=Stop+streaming -q -O /dev/null -t 1 -T 1 + ret=$? + + if [ $1 == "start" ]; then + sleep 1 + wget http://192.168.1.2/?startAVB=Go+AVB+stream -q -O /dev/null -t 1 -T 1 + ret=$? + fi + + ifconfig eth0:1 down + + exit $ret + ;; +*) + echo "Usage: $0 start|stop" >&2 + exit 3 + ;; +esac + + + diff --git a/apps/linux/genavb-video-server-app/CMakeLists.txt b/apps/linux/genavb-video-server-app/CMakeLists.txt new file mode 100644 index 0000000..5eca592 --- /dev/null +++ b/apps/linux/genavb-video-server-app/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.10) + +project(genavb-video-server-app) + +option(WAYLAND_BACKEND "Build application with support for wayland backend" ON) + +find_package(PkgConfig) +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0 gstreamer-app-1.0) + +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + ../common/common.c + ../common/stats.c + ../common/time.c + ../common/ts_parser.c + ../common/file_buffer.c + ../common/aecp.c + ../common/gstreamer.c + ../common/gst_pipeline_definitions.c + ../common/gstreamer_multisink.c + ../common/gstreamer_custom_rt_pool.c +) + +if(WAYLAND_BACKEND) + target_compile_definitions(${PROJECT_NAME} PUBLIC WL_BUILD) +endif() + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) +target_link_libraries(${PROJECT_NAME} pthread) +target_link_libraries(${PROJECT_NAME} ${GSTREAMER_LIBRARIES}) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/genavb-video-server-app/main.c b/apps/linux/genavb-video-server-app/main.c new file mode 100644 index 0000000..6bb8c8e --- /dev/null +++ b/apps/linux/genavb-video-server-app/main.c @@ -0,0 +1,737 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2020, 2022-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../common/gstreamer_multisink.h" +#include "../common/common.h" +#include "../common/stats.h" +#include "../common/time.h" +#include "../common/ts_parser.h" +#include "../common/file_buffer.h" +#include "../common/gstreamer.h" +#include "../common/aecp.h" +#include "../common/gst_pipeline_definitions.h" + +//#define AVB_TALKER_TS_LOG + +#define APP_PRIORITY 1 /* RT_FIFO priority to be used for the process */ + +#define K 1024 +#define DATA_BUF_SZ (16*K) +#define EVENT_BUF_SZ (K) +#define BATCH_SIZE 2048 +#define BATCH_SIZE_H264 102400 // in bytes /* Set batch size of h264 stream to 100kb*/ + +#define EVENT_MAX (BATCH_SIZE / PES_SIZE) + +/* default input video file name */ +#define DEFAULT_MEDIA_FILE_NAME "sample1.mp4" + + + +struct media_app { + + struct talker_gst_media gst; + struct talker_gst_multi_app stream[APP_MAX_ACTIVE_STREAMS]; + + struct stats poll_delay; + struct stats poll_data; + + unsigned int ts_parser_enabled; + + unsigned int rendering_delay; + + avb_u8 input_media_file_index; + unsigned int n_input_media_files; + char **input_media_files; + char *input_media_file_name; + + unsigned int width; + unsigned int height; + unsigned int type; + + char *device; + + struct media_thread thread; + struct gstreamer_pipeline gst_pipe; +}; + +static int signal_terminate = 0; +static int signal_child_terminate = 0; + + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\nOptions:\n" + "\t-f video file name (default video.ts)\n" + "\t-l local video preview\n" + "\t-v Video H264 streaming (No local preview)\n" + "\t-L local video preview rendering latency (in ms)\n" + "\t-d display device for local preview (lvds (default), hdmi)\n" + "\t-s scaling for local preview (1080, 720, 768 (default), 480)\n" + "\t-c use the specified custom gstreamer pipeline\n" + "\t instead of the hard-coded ones. Must be the\n" + "\t last option on the command-line, overrides\n" + "\t all other options.\n" + "\t-h print this help text\n"); +} + + +/* + * scandir will skip the file if input_file_filter_mp4 returns 0, and add it to the list otherwise. + */ +int input_file_filter_mp4(const struct dirent *file) +{ + if (!strcasestr(file->d_name, ".mp4")) + return 0; + else + return 1; + +} + +int build_media_file_list(struct media_app *app) +{ + struct stat status; + struct dirent **file_list; + int rc = 0; + int n = 1; + int i = 0; + int len; + + + if (stat(app->input_media_file_name, &status) < 0) { + printf("Couldn't get file status for %s, got error %s\n", app->input_media_file_name, strerror(errno)); + goto err; + } + + if (S_ISDIR(status.st_mode)) { + n = scandir(app->input_media_file_name, &file_list, input_file_filter_mp4, alphasort); + if (n < 0) { + printf("Couldn't scan directory %s, got error %s\n", app->input_media_file_name, strerror(errno)); + goto err; + } + if (n == 0) { + printf("Didn't find any media files in directory %s\n", app->input_media_file_name); + goto err; + } + + app->n_input_media_files = n; + app->input_media_files = malloc(n*sizeof(char *)); + + if (!app->input_media_files) { + printf("Error while allocating %d input media files\n", n); + goto err_inputs_alloc; + } + + for (i = 0; i < n; i++) { + len = strlen(app->input_media_file_name) + strlen(file_list[i]->d_name) + 1; + app->input_media_files[i] = malloc(len); + + if (!app->input_media_files[i]) { + printf("Error while allocating input media files at index %d\n", i); + goto err_input_member_alloc; + } + + rc = snprintf(app->input_media_files[i], len, "%s%s", app->input_media_file_name, file_list[i]->d_name); + if ((rc < 0) || (rc >= len)) { + printf("Error %d while generating filenames\n", rc); + goto err_name_gen; + } + + } + + } else { + app->n_input_media_files = 1; + app->input_media_files = malloc(sizeof(char *)); + + if (!app->input_media_files) { + printf("Error while allocating input media files\n"); + goto err_inputs_alloc; + } + + *app->input_media_files = app->input_media_file_name; + } + + return 0; + +err_name_gen: + free(app->input_media_files[i]); + +err_input_member_alloc: + while (i--) { + free(app->input_media_files[i]); + } + + free(app->input_media_files); + +err_inputs_alloc: +err: + return -1; +} + +static int apply_gst_config(struct talker_gst_media *gst, struct media_app *app) +{ + int rc = 0; + + gst->gst_pipeline = &app->gst_pipe; + + //TODO Add local preview for H264 + if (app->type == OPT_TYPE_VIDEO_ONLY) { + gst->gst_pipeline->definition = &pipeline_talker_file_cvf_h264; + printf("pipeline_talker_file_cvf_h264 selected\n"); + + } else { + if (app->type == OPT_TYPE_PREVIEW) { + gst->gst_pipeline->definition = &pipeline_talker_file_61883_4_61883_6_preview; + printf("pipeline_talker_file_61883_4_61883_6_preview selected\n"); + } else { + gst->gst_pipeline->definition = &pipeline_talker_file_61883_4_61883_6; + printf("pipeline_talker_file_61883_4_61883_6 selected\n"); + } + } + gst->gst_pipeline->config.device = app->device; + gst->gst_pipeline->config.height = app->height; + gst->gst_pipeline->config.width = app->width; + gst->gst_pipeline->config.crop_height = 0; + gst->gst_pipeline->config.crop_width = 0; + gst->gst_pipeline->config.sync_render_to_clock = 1; + + /* FIXME for now use the same file for all pipelines */ + gst->gst_pipeline->config.talker.file_src_location = app->input_media_files[app->input_media_file_index]; + + gst->gst_pipeline->config.talker.preview_ts_offset = (app->rendering_delay * 1000000); + + if (app->ts_parser_enabled) + gst->gst_pipeline->talker.sync = 0; + else + gst->gst_pipeline->talker.sync = 1; + + return rc; +} + + +static void dump_gst_config(struct media_app *app) +{ + printf("INPUT MEDIA FILE: %s\n", app->input_media_file_name); + + if (app->type == OPT_TYPE_PREVIEW) { + printf("LOCAL DISPLAY DEVICE: %s\n", app->device); + + printf("LOCAL DISPLAY SCALING: %dx%d\n", app->width, app->height); + } +} + + +static void set_avb_config(unsigned int *avb_flags) +{ + *avb_flags = 0; +} + + +static int config_handler(struct media_stream *_stream) +{ + struct media_app *app = _stream->thread->data; + + _stream->params.clock_domain = AVB_MEDIA_CLOCK_DOMAIN_PTP; + _stream->params.talker.latency = max(CFG_TALKER_LATENCY_NS, sr_class_interval_p(_stream->params.stream_class) / sr_class_interval_q(_stream->params.stream_class)); + + if (_stream->params.format.u.s.subtype_u.cvf.subtype == CVF_FORMAT_SUBTYPE_H264) + _stream->batch_size = BATCH_SIZE_H264; + else + _stream->batch_size = BATCH_SIZE; + + _stream->flags = AVTP_NONBLOCK; + + print_stream_id(_stream->params.stream_id); + + dump_gst_config(app); + + return 0; +} + + +static void signal_child_terminate_handler(int signal_num) +{ + printf("child terminated\n"); + signal_child_terminate = 1; +} + + +static void signal_terminate_handler(int signal_num) +{ + signal_terminate = 1; +} + + +static int data_handler(struct media_stream *_stream) +{ + struct talker_gst_multi_app *stream = _stream->data; + return talker_gst_multi_stream_fsm(stream, STREAM_EVENT_DATA); +} + + +static void start_stream(struct media_thread *thread) +{ + int i; + for (i = 0; i < thread->num_streams; i++) + talker_gst_multi_stream_fsm(thread->stream[i].data, STREAM_EVENT_PLAY); + +} + +static void stop_stream(struct media_thread *thread) +{ + int i; + for (i = 0; i < thread->num_streams; i++) + talker_gst_multi_stream_fsm(thread->stream[i].data, STREAM_EVENT_STOP); +} + + +void aem_send_aecp_playstop_control_unsolicited_response(struct media_app *app, avb_u8 play_stop) +{ + aecp_aem_send_set_control_single_u8_unsolicited_response(app->thread.controlled.handle, 1, &play_stop); +} + +void aem_send_media_track_name_control_unsolicited_response(struct media_app *app, const char *track) +{ + aecp_aem_send_set_control_utf8_unsolicited_response(app->thread.controlled.handle, 3, track); +} + +void aem_send_media_track_control_unsolicited_response(struct media_app *app, avb_u8 track) +{ + aecp_aem_send_set_control_single_u8_unsolicited_response(app->thread.controlled.handle, 2, &track); +} + + +static int aem_set_control_handler(struct avdecc_controlled *controlled, avb_u16 ctrl_index, void *ctrl_value) +{ + struct media_thread *thread = (struct media_thread *)controlled->data; + struct talker_gst_multi_app *stream = thread->stream[0].data; + struct media_app *app = thread->data; + avb_u8 value = *(avb_u8 *)ctrl_value; + int rc = AECP_AEM_SUCCESS; + int restart_stream; + + /* Only descriptor indices 1,2 have been mapped here */ + switch(ctrl_index) { + case 1: /* play/stop */ + if (value) { /* PLAY <--> 255 */ + printf("Start playing stream\n"); + start_stream(thread); + } else { /* STOP <--> 0 */ + printf("Stop playing stream\n"); + stop_stream(thread); + } + break; + + case 2: /* media track ID */ + printf("Received SET_CONTROL command for media track(%d) from AVDECC\n", value); + + if ((value == 0) || (value > app->n_input_media_files)) { + rc = AECP_AEM_BAD_ARGUMENTS; + value = app->input_media_file_index + 1; + printf("media track index out-of-bounds, clamping to %d\n", value); + } + + *(avb_u8 *)ctrl_value = value; + if (value != (app->input_media_file_index + 1)) { + if (app->gst.state != GST_STATE_STOPPED) { + stop_stream(thread); + restart_stream = 1; + } else + restart_stream = 0; + app->input_media_file_index = value - 1; + stream->gst->gst_pipeline->config.talker.file_src_location = app->input_media_files[app->input_media_file_index]; + aem_send_media_track_name_control_unsolicited_response(app, stream->gst->gst_pipeline->config.talker.file_src_location); + printf("Setting input file to %s\n", stream->gst->gst_pipeline->config.talker.file_src_location); + sleep(PREVNEXT_STOP_DURATION); + if (restart_stream) { + start_stream(thread); + } + } + break; + + default: + printf("Unsupported index(%d) for CONTROL descriptor.\n", ctrl_index); + rc = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + return rc; +} + + +static int connect_handler(struct media_stream *_stream) +{ + struct media_app *app = (struct media_app *)_stream->thread->data; + aem_send_aecp_playstop_control_unsolicited_response(app, 1); + aem_send_media_track_control_unsolicited_response(app, app->input_media_file_index + 1); + aem_send_media_track_name_control_unsolicited_response(app, app->input_media_files[app->input_media_file_index]); + + struct talker_gst_multi_app *gst_multi_app = (struct talker_gst_multi_app *) _stream->data; + gst_multi_app->batch_size = _stream->batch_size; + gst_multi_app->stream_h = _stream->handle; + memcpy(&gst_multi_app->params, &_stream->params, sizeof(struct avb_stream_params)); + + return talker_gst_multi_stream_fsm(gst_multi_app, STREAM_EVENT_CONNECT); +} + +static int disconnect_handler(struct media_stream *_stream) +{ + aem_send_aecp_playstop_control_unsolicited_response(_stream->thread->data, 0); + return talker_gst_multi_stream_fsm(_stream->data, STREAM_EVENT_DISCONNECT); +} + +static int timeout_handler(struct media_thread *thread) +{ + struct media_app *app = thread->data; + int rc; + int i; + + for (i = 0; i < thread->num_streams; i++) { + struct media_stream *_stream = &thread->stream[i]; + + rc = talker_gst_multi_stream_fsm(_stream->data, STREAM_EVENT_TIMER); + if (rc < 0) + goto err; + } + + rc = talker_gst_multi_fsm(&app->gst, GST_EVENT_TIMER); + if (rc < 0) + goto err; + + return 0; + +err: + return rc; +} + +static int signal_handler(struct media_thread *thread) +{ + int rc = 0; + + if (signal_terminate) { + signal_terminate = 0; + printf("processing terminate signal\n"); + rc = -1; + } + +#if 0 + if (signal_child_terminate) { + signal_child_terminate = 0; + printf("processing child terminate signal\n"); + rc = -1; + } +#endif + + return rc; +} + +static int init_handler(struct media_thread *thread) +{ + printf("%s\n", __func__); + + return 0; +} + +static int exit_handler(struct media_thread *thread) +{ + struct media_app *app = thread->data; + int i; + + printf("%s\n", __func__); + + for (i = 0; i < app->thread.num_streams; i++) + talker_gst_multi_stream_fsm(app->thread.stream[i].data, STREAM_EVENT_DISCONNECT); + + return 0; +} +static void video_server_stream_poll_set (void* data, int enable) +{ + struct media_stream *stream = (struct media_stream *) data; + media_stream_poll_set(stream, enable); +} + + +int main(int argc, char *argv[]) +{ + struct media_app app; + unsigned int avb_flags; + int option; + int i; + int rc = 0; + struct sched_param param = { + .sched_priority = APP_PRIORITY, + }; + struct sigaction action; + unsigned long optval_ul; + + setlinebuf(stdout); + + printf("NXP's GenAVB reference video server application\n"); + + memset(&app, 0, sizeof(struct media_app)); + + gstreamer_init(); + + /* + * Increase process priority to match the AVTP thread priority + */ + + if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) { + printf("sched_setscheduler() failed: %s\n", strerror(errno)); + rc = -1; + goto exit; + } + + /* + * retrieve user's configuration parameters + */ + + app.type = OPT_TYPE_DEFAULT; + app.input_media_file_index = 0; + app.n_input_media_files = 1; + app.input_media_file_name = DEFAULT_MEDIA_FILE_NAME; + app.ts_parser_enabled = 0; + app.rendering_delay = 0; + app.device = V4L2_LVDS_DEVICE_FILE; + app.width = 0; + app.height = 0; + + while ((option = getopt(argc, argv,"f:lL:d:s:htv")) != -1) { + + switch (option) { + case 'f': + { + app.input_media_file_name = optarg; + rc = build_media_file_list(&app); + if (rc < 0) + goto exit; + + if (app.n_input_media_files > 1) { + app.input_media_file_index = app.n_input_media_files - 1; + printf("Found %d media files in %s, using %s as first file.\n", app.n_input_media_files, app.input_media_file_name, app.input_media_files[app.input_media_file_index]); + } + + + break; + } + case 'l': + app.type = OPT_TYPE_PREVIEW; + break; + + case 'v': + app.type = OPT_TYPE_VIDEO_ONLY; + break; + + case 'L': + if (h_strtoul(&optval_ul, optarg, NULL, 10) < 0) + goto exit; + app.rendering_delay = (unsigned int)optval_ul; + break; + + case 'd': + if (!strcasecmp(optarg, "lvds")) + app.device = V4L2_LVDS_DEVICE_FILE; + else if (!strcasecmp(optarg, "hdmi")) + app.device = V4L2_HDMI_DEVICE_FILE; + else { + usage(); + goto exit; + } + + break; + + case 's': + if (!strcasecmp(optarg, "1080")) { + app.width = 1920; + app.height = 1080; + } else if (!strcasecmp(optarg, "720")) { + app.width = 1280; + app.height = 720; + } else if (!strcasecmp(optarg, "768")) { + app.width = 1024; + app.height = 768; + } else if (!strcasecmp(optarg, "480")) { + app.width = 640; + app.height = 480; + } else { + usage(); + goto exit; + } + + break; + + case 't': + app.ts_parser_enabled = 1; + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + + } + + /* + * set signals handler + */ + action.sa_handler = signal_terminate_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGTERM, &action, NULL) < 0) /* Termination signal */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGQUIT, &action, NULL) < 0) /* Quit from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGINT, &action, NULL) < 0) /* Interrupt from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + action.sa_handler = signal_child_terminate_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGCHLD, &action, NULL) < 0) /* Child process termination signal */ + printf("sigaction(): %s\n", strerror(errno)); + + /* + * setup avb stack + */ + printf("Running in streaming mode\n"); + + set_avb_config(&avb_flags); + + rc = avb_init(&app.thread.avb_h, avb_flags); + if (rc != AVB_SUCCESS) { + printf("avb_init() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + /* + * listen to avdecc events to get stream parameters + */ + + rc = avb_control_open(app.thread.avb_h, &app.thread.ctrl.handle, AVB_CTRL_AVDECC_MEDIA_STACK); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() for AVB_CTRL_AVDECC_MEDIA_STACK channel failed: %s\n", avb_strerror(rc)); + goto error_control_open; + } + + rc = avb_control_open(app.thread.avb_h, &app.thread.controlled.handle, AVB_CTRL_AVDECC_CONTROLLED); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() for AVB_CTRL_AVDECC_CONTROLLED channel failed: %s\n", avb_strerror(rc)); + goto error_controlled_open; + } + + + app.gst.state = GST_STATE_STOPPED; + apply_gst_config(&app.gst, &app); + + app.thread.init_handler = init_handler; + app.thread.exit_handler = exit_handler; + app.thread.signal_handler = signal_handler; + app.thread.timeout_handler = timeout_handler; + app.thread.data_handler = data_handler; + + app.thread.controlled.aem_set_control_handler = aem_set_control_handler; + + app.thread.ctrl.connect_handler = connect_handler; + app.thread.ctrl.config_handler = config_handler; + app.thread.ctrl.disconnect_handler = disconnect_handler; + + app.thread.timeout_ms = POLL_TIMEOUT_MS; + + app.thread.data = &app; + + for (i = 0; i < APP_MAX_ACTIVE_STREAMS; i++) { + + app.stream[i].stream_poll_data = (void *) &app.thread.stream[i]; + app.stream[i].stream_poll_set = video_server_stream_poll_set; + + app.stream[i].index = i; + + app.thread.stream[i].data = &app.stream[i]; + + app.stream[i].state = STREAM_STATE_DISCONNECTED; + } + + app.thread.max_supported_streams = APP_MAX_SUPPORTED_STREAMS; + + if (app.gst.gst_pipeline->definition->num_sinks > 1) { + /* Fixed mapping between streams (talker_gst_multi_app) and gst pipeline sinks, must match avdecc mapping */ + /* Audio stream */ + app.stream[0].gst = &app.gst; + app.stream[0].sink_index = 1; + app.stream[0].ts_parser_enabled = 0; + app.stream[0].audio = 1; + app.gst.gst_pipeline->sink[1].data = app.thread.stream[0].data; + + /* Video stream */ + app.stream[1].gst = &app.gst; + app.stream[1].sink_index = 0; + app.stream[1].ts_parser_enabled = app.ts_parser_enabled; + app.stream[1].audio = 0; + app.gst.gst_pipeline->sink[0].data = app.thread.stream[1].data; + + app.thread.num_streams = 2; + } else { + /* Fixed mapping between streams and gst pipeline sinks, must match avdecc mapping */ + /* H264 Video stream will be at stream_index 2*/ + app.stream[0].gst = &app.gst; + app.stream[0].sink_index = 0; + app.stream[0].ts_parser_enabled = app.ts_parser_enabled; + app.stream[0].audio = 0; + app.gst.gst_pipeline->sink[0].data = app.thread.stream[0].data; + + app.thread.num_streams = 1; + } + + media_thread_loop(&app.thread); + + avb_control_close(app.thread.controlled.handle); + +error_controlled_open: + avb_control_close(app.thread.ctrl.handle); + +error_control_open: + avb_exit(app.thread.avb_h); + +error_avb_init: +exit: + gstreamer_reset(); + + return rc; +} diff --git a/apps/linux/genavb-video-server-app/pipeline_sm.dot b/apps/linux/genavb-video-server-app/pipeline_sm.dot new file mode 100644 index 0000000..1b6b55e --- /dev/null +++ b/apps/linux/genavb-video-server-app/pipeline_sm.dot @@ -0,0 +1,27 @@ +graph "pipeline state machine" +{ +STARTING; +STARTED; +STOPPED; +LOOP; +ALL_STOPPED1 [label="All stopped?", shape=diamond]; +ALL_STOPPED2 [label="All stopped?", shape=diamond]; +ALL_LOOPING [label="All looping?", shape=diamond]; +TIMEOUT [label="Timeout?", shape=diamond]; + + +STARTING -- STARTED [dir=forward]; +STARTED -- ALL_STOPPED1 [label="STOP", dir=forward, arrowhead=normal]; +ALL_STOPPED1 -- STARTED [label="No", dir=forward, arrowhead=normal]; +ALL_STOPPED1 -- STOPPED [label="Yes", dir=forward, arrowhead=normal]; +STARTED -- ALL_LOOPING [label="LOOP", dir=forward, arrowhead=normal]; +ALL_LOOPING -- STARTED [label="No", dir=forward, arrowhead=normal]; +ALL_LOOPING -- LOOP [label="Yes", dir=forward, arrowhead=normal]; +STOPPED -- STARTING [label="START", dir=forward]; +LOOP -- TIMEOUT [label="TIMER", dir=forward]; +TIMEOUT -- LOOP [label="No", dir=forward, arrowhead=normal]; +TIMEOUT -- STARTING [label="Yes", dir=forward, arrowhead=normal]; +LOOP -- ALL_STOPPED2 [label="STOP", dir=forward]; +ALL_STOPPED2 -- LOOP [label="No", dir=forward, arrowhead=normal]; +ALL_STOPPED2 -- STOPPED [label="Yes", dir=forward, arrowhead=normal]; +} diff --git a/apps/linux/genavb-video-server-app/readme.txt b/apps/linux/genavb-video-server-app/readme.txt new file mode 100644 index 0000000..b10104a --- /dev/null +++ b/apps/linux/genavb-video-server-app/readme.txt @@ -0,0 +1,63 @@ +Gstreamer talker application + +Supports one gstreamer pipeline with two sinks, using appsink or fdsink interface. +Two interconnected state machines are used. One manages the pipeline state, the +other the stream(s)/sink(s) state. +A single thread is used to manage the different state machines. + +The application main loop listens to avdecc events (to create/destroy streams), +avb library events (to request more stream data for transmission) and timer events. + +The pipeline state machine has the following states: + +GST_STATE_STARTING - transition state to GST_STATE_STARTED +GST_STATE_STARTED - pipeline is started. At least one stream is started. +GST_STATE_STOPPED - pipeline is stopped. All streams are stopped. +GST_STATE_LOOP - pipeline is stopped. All streams are looping. Waiting for timeout before switching to GST_STATE_STARTING. + +and events: + +GST_EVENT_START - start event from stream state machine +GST_EVENT_STOP - stop event from stream state machine +GST_EVENT_LOOP - loop event from gst state machine, when all streams are in looping state +GST_EVENT_TIMER - timer event from thread loop timeout + + + +---------------------------------------------------------------- Yes -------------------------+ + | +-------------No-------------+ | + | | | ---- + + | | +---- LOOP --> | Are all streams looping? | -- Yes -+-> LOOP -- Timer --> | Timeout reached | -+ + v | | | ---- | +-------- | ------- +--------------------------- No -----------+ +STARTING -+> STARTED +-------- ------- + ^ | ---- + | +---- STOP --> | Are all streams stopped? | -- Yes --> STOP -- START --+ + | ---- | + +-----------------------------------------------------------------------------------+ + + +The stream state machines has the following states: +STREAM_STATE_CONNECTING - transition state to CONNECTED +STREAM_STATE_CONNECTED - avb stream is open and being polled, actively reading from media stack +STREAM_STATE_DISCONNECTING - transition state to DISCONNECTED +STREAM_STATE_DISCONNECTED - avb stream is closed, not reading from media stack. Sink data is read from timer event in pipeline state machine. +STREAM_STATE_WAIT_DATA - avb stream is open but not polled, actively reading from media stack (based on a timer). On timeout transition to STREAM_STATE_LOOP +STREAM_STATE_LOOP - avb stream is open but not polled, not reading from media stack. On timeout, loop media file and transition to STREAM_STATE_CONNECTING + +and events: +STREAM_EVENT_CONNECT - Connect event from control handler +STREAM_EVENT_DISCONNECT - Disconnect event from control handler +STREAM_EVENT_DATA - Data event from stream file descriptor +STREAM_EVENT_TIMER - Timer event from thread loop +STREAM_EVENT_LOOP_DONE - Loop done event from pipeline state machine + + +Stream synchronization +The gstreamer appsink interface provides media data buffers and presentation timestamps for those buffers (absolute time +in gPTP timebase) to the application. The application uses the lower 32bit of the buffer PTS (with an 15ms offset) to build +SYNC events used by the media driver interface. These events are passed to the avtp stack and used to build avtp timestamps. +The 15ms offset is introduced to avoid having timestamps in the past due to scheduling delay at the gstreamer interface level. +Synchronization of the two generated streams (on the talker side) depends on the precision of the timestamps provided by the +gstreamer framework. + diff --git a/apps/linux/maap-ctrl-app/CMakeLists.txt b/apps/linux/maap-ctrl-app/CMakeLists.txt new file mode 100644 index 0000000..dc6a0bb --- /dev/null +++ b/apps/linux/maap-ctrl-app/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) + +project(maap-ctrl-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c +) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/maap-ctrl-app/main.c b/apps/linux/maap-ctrl-app/main.c new file mode 100644 index 0000000..d36c862 --- /dev/null +++ b/apps/linux/maap-ctrl-app/main.c @@ -0,0 +1,312 @@ +/* + * Copyright 2021-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAAP_CONTROL_TIMEOUT 1 /* 1 sec */ + +#define DEFAULT_PORT 0 +#define DEFAULT_RANGE_ID 0 +#define DEFAULT_MAC_ADDR {0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + +static const char *maap_status_str[] = { + [MAAP_STATUS_SUCCESS] = "MAAP_SUCCESS", + [MAAP_STATUS_FREE] = "MAAP_FREE", + [MAAP_STATUS_CONFLICT] = "MAAP_CONFLICT", + [MAAP_STATUS_ERROR] = "MAAP_ERROR" +}; + +static const char *maap_response_str[] = { + [MAAP_RESPONSE_SUCCESS] = "MAAP_RESPONSE_SUCCESS", + [MAAP_RESPONSE_ERROR] = "MAAP_RESPONSE_ERROR" +}; + +void usage (void) +{ + printf("\nUsage:\nmaap-ctrl-app [options]\n"); + printf("\nOptions:\n" + "\nCommon options:\n" + "\t-n Create new range, takes previously set configuration (-p -i -c -f -a) in the command line, otherwise default ones. -a and -f should be used together\n" + "\t-d Delete a range, takes previously set configuration (-p -i) in the command line, otherwise default ones\n" + "\t-i ID of the range to create or to delete (default = 0)\n" + "\t-f Flag, set it to 1 if you want to use a prefered first mac address for the new range (default = 0)\n" + "\t-a First prefered mac address of the new range, check IEEE1722-2016 for the standard ranges (default = 00:00:00:00:00:00)\n" + "\t-c Number of mac address to allocate in the range, max = 65024 (default = 1)\n" + "\t-p (Logical) Port where the range should be allocated/deleted (default = 0)\n" + "\t-h Print this help text\n" + "\t-l Listen to status messages from MAAP\n" + "\nRange creation:\n" + "\t-p 0 -i 1 -c 2 -f 1 -a 91:e0:f0:00:fd:49 -n\n" + "\t-p 0 -i 2 -c 2 -n\n" + "\t-p 0 -i 3 -n\n" + "\nMultiple range creation:\n" + "\t-p 0 -i 1 -c 2 -f 1 -a 91:e0:f0:00:fd:49 -n -i 2 -c 3 -a 91:e0:f0:00:f0:49 -n\n" + "\t-p 0 -i 3 -n -i 4 -n\n" + "\nRange deletion:\n" + "\t-p 0 -i 1 -d\n" + "\nMultiple range deletion:\n" + "\t-p 0 -i 1 -d -i 2 -d\n"); +} + +static int new_range(struct genavb_control_handle *ctrl_h, uint16_t port, uint32_t range_id, bool flag, uint8_t *addr, uint16_t count) +{ + struct genavb_msg_maap_create command_msg; + unsigned int msg_type; + struct genavb_msg_maap_create_response response_msg; + unsigned int msg_len; + int rc; + + command_msg.flag = flag; + command_msg.port_id = port; + command_msg.range_id = range_id; + command_msg.count = count; + + if (!flag) { + memset(command_msg.base_address, 0, sizeof(uint8_t) * 6); + } else { + memcpy(command_msg.base_address, addr, sizeof(uint8_t) * 6); + } + + msg_type = GENAVB_MSG_MAAP_CREATE_RANGE; + msg_len = sizeof(response_msg); + rc = genavb_control_send_sync(ctrl_h, &msg_type, &command_msg, sizeof(command_msg), &response_msg, &msg_len, MAAP_CONTROL_TIMEOUT); + + if (rc < 0) { + printf("genavb_control_send_sync() failed: %s\n", genavb_strerror(rc)); + goto err; + + } else if (msg_type != GENAVB_MSG_MAAP_CREATE_RANGE_RESPONSE) { + printf("response type error: %d\n", msg_type); + goto err; + + } else if (response_msg.status != MAAP_RESPONSE_SUCCESS) { + printf("response status error: %s\n", maap_response_str[response_msg.status]); + goto err; + } + + printf ("(%u) CREATED ID : %u, first MAC address: %02x-%02x-%02x-%02x-%02x-%02x, number of addresses: %u, port: %u, status: %u, %s\n", + getpid(), + response_msg.range_id, response_msg.base_address[0], response_msg.base_address[1], response_msg.base_address[2], + response_msg.base_address[3], response_msg.base_address[4], response_msg.base_address[5], response_msg.count, + response_msg.port_id, response_msg.status, maap_response_str[response_msg.status]); + + return 0; + +err: + return -1; +} + +static int delete_range(struct genavb_control_handle *ctrl_h, uint16_t port, uint32_t range_id) +{ + struct genavb_msg_maap_delete command_msg; + unsigned int msg_type; + struct genavb_msg_maap_delete_response response_msg; + unsigned int msg_len; + int rc; + + command_msg.port_id = port; + command_msg.range_id = range_id; + + msg_type = GENAVB_MSG_MAAP_DELETE_RANGE; + msg_len = sizeof(response_msg); + rc = genavb_control_send_sync(ctrl_h, &msg_type, &command_msg, sizeof(command_msg), &response_msg, &msg_len, MAAP_CONTROL_TIMEOUT); + + if (rc < 0) { + printf("genavb_control_send_sync() failed: %s\n", genavb_strerror(rc)); + goto err; + + } else if (msg_type != GENAVB_MSG_MAAP_DELETE_RANGE_RESPONSE) { + printf("response type error: %d\n", msg_type); + goto err; + + } else if (response_msg.status != MAAP_RESPONSE_SUCCESS) { + printf("response status error: %s\n", maap_response_str[response_msg.status]); + goto err; + } + + printf ("(%u) DELETED ID : %u, port: %u, status: %u, %s\n", + getpid(), + response_msg.range_id, response_msg.port_id, response_msg.status, maap_response_str[response_msg.status]); + + return 0; + +err: + return -1; +} + +static void maap_listen(struct genavb_control_handle *ctrl_h, int ctrl_fd) +{ + unsigned int msg_type; + struct genavb_maap_status msg; + unsigned int msg_len; + fd_set set; + int rc; + + while (1) { + FD_ZERO(&set); + FD_SET(ctrl_fd, &set); + + rc = select(ctrl_fd + 1, &set, NULL, NULL, NULL); + if (rc < 0) { + printf ("select() failed: %s\n", strerror(errno)); + break; + } + + if (!FD_ISSET(ctrl_fd, &set)) + continue; + + msg_len = sizeof(msg); + + rc = genavb_control_receive(ctrl_h, &msg_type, &msg, &msg_len); + if (rc < 0) { + printf ("genavb_control_receive() failed: %s\n", genavb_strerror(rc)); + break; + } + + switch (msg_type) { + case GENAVB_MSG_MAAP_CREATE_RANGE_RESPONSE: + case GENAVB_MSG_MAAP_DELETE_RANGE_RESPONSE: + break; + + case GENAVB_MSG_MAAP_STATUS: + printf ("(%u) ID : %u, first MAC address: %02x-%02x-%02x-%02x-%02x-%02x, number of addresses: %u, port: %u, status: %u, %s\n", + getpid(), + msg.range_id, msg.base_address[0], msg.base_address[1], msg.base_address[2], + msg.base_address[3], msg.base_address[4], msg.base_address[5], msg.count, + msg.port_id, msg.status, maap_status_str[msg.status]); + break; + + default: + printf ("Unexpected message type %d\n", msg_type); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + struct genavb_handle *avb_h; + struct genavb_control_handle *ctrl_h; + unsigned long port = DEFAULT_PORT; + unsigned long range_id = DEFAULT_RANGE_ID; + unsigned long flag = 0; + unsigned long count = 1; + uint8_t mac_addr[6] = DEFAULT_MAC_ADDR; + int ctrl_fd; + int option; + int rc = 0; + + setlinebuf(stdout); + + printf("NXP's GenAVB MAAP control application\n"); + + /* + * setup the avb stack + */ + + rc = genavb_init(&avb_h, 0); + if (rc != GENAVB_SUCCESS) { + printf("genavb_init() failed: %s\n", genavb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + rc = genavb_control_open(avb_h, &ctrl_h, GENAVB_CTRL_MAAP); + if (rc != GENAVB_SUCCESS) { + printf("genavb_control_open() failed: %s\n", genavb_strerror(rc)); + rc = -1; + goto error_control_open; + } + + ctrl_fd = genavb_control_rx_fd(ctrl_h); + + /* + * retrieve user's configuration parameters + */ + while ((option = getopt(argc, argv,"f:a:c:p:i:hlnd")) != -1) { + switch (option) { + case 'f': + if (h_strtoul(&flag, optarg, NULL, 0) < 0) { + printf("invalid -f %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'a': + if (sscanf(optarg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac_addr[0], &mac_addr[1], &mac_addr[2], &mac_addr[3], &mac_addr[4], &mac_addr[5]) < 6) { + printf("invalid -a %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'c': + if (h_strtoul(&count, optarg, NULL, 0) < 0) { + printf("invalid -c %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'p': + if (h_strtoul(&port, optarg, NULL, 0) < 0) { + printf("invalid -p %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'i': + if (h_strtoul(&range_id, optarg, NULL, 0) < 0) { + printf("invalid -i %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'l': + maap_listen(ctrl_h, ctrl_fd); + break; + + case 'n': + rc = new_range(ctrl_h, port, range_id, flag, mac_addr, count); + break; + + case 'd': + rc = delete_range(ctrl_h, port, range_id); + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + +exit: + genavb_control_close(ctrl_h); + +error_control_open: + genavb_exit(avb_h); + +error_avb_init: + return rc; +} diff --git a/apps/linux/management-app/CMakeLists.txt b/apps/linux/management-app/CMakeLists.txt new file mode 100644 index 0000000..8c9eec0 --- /dev/null +++ b/apps/linux/management-app/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.10) + +project(management-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + common.c + gptp_main.c + srp_main.c +) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) +target_link_libraries(${PROJECT_NAME} -Wl,-unresolved-symbols=ignore-in-shared-libs) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/management-app/common.c b/apps/linux/management-app/common.c new file mode 100644 index 0000000..ee68aaa --- /dev/null +++ b/apps/linux/management-app/common.c @@ -0,0 +1,149 @@ +/* + * Copyright 2020-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +//#define DEBUG + +void usage (void) +{ + printf("\nUsage:\nmanagement-app [ptp|srp] [options]\n"); + printf("\nOptions:\n" + "\nCommon options:\n" + "\t-E manages endpoint stack (default)\n" + "\t-B manages bridge stack\n" + "\t-S sets managed object\n" + "\t-G gets managed object (default)\n" + "\t-P manages port (default 0)\n" + "\t-D manages direction (default 0: talker, 1: listener)\n" + "\t-h print this help text\n" + "\nPTP options:\n" + "\t-p set/get gPTP priority1 value\n" + "\t-s set/get gPTP port state\n" + "\t-d dump port stats\n" + "\nSRP options:\n" + "\t-M set/get msrpEnabledStatus (0: disabled, 1: enabled)\n" + "\t-e set/get msrpPortEnabledStatus (0: disabled, 1: enabled)\n" + "\t-s get streams table (default wilcard if omitted)\n" + "\t-r get reservations table per (default wilcard if omitted)\n" + "\t-b get bridge base table\n" + "\t-p get bridge port table\n" + "\t-l get latency parameter table\n"); +} + +int managed_set(struct genavb_control_handle *ctrl_h, void *cmd, unsigned int cmd_len, void *response, unsigned int response_len) +{ + unsigned int msg_type; +#ifdef DEBUG + int i; +#endif + int rc; + + if (!ctrl_h) + goto err; + + msg_type = GENAVB_MSG_MANAGED_SET; + rc = genavb_control_send_sync(ctrl_h, &msg_type, cmd, cmd_len, response, &response_len, 100); + if (rc != GENAVB_SUCCESS) { + printf("genavb_control_send_sync() failed: %s\n", genavb_strerror(rc)); + goto err; + } + + if (msg_type != GENAVB_MSG_MANAGED_SET_RESPONSE) { + printf("genavb_control_send_sync() wrong response message type: %d\n", msg_type); + goto err; + } + +#ifdef DEBUG + printf("%d", response_len); + + for (i = 0; i < response_len; i++) + printf(" %1x", ((uint8_t *)response)[i]); + + printf("\n"); +#endif + + return 0; + +err: + return -1; +} + +int managed_get(struct genavb_control_handle *ctrl_h, void *cmd, unsigned int cmd_len, void *response, unsigned int response_len) +{ + unsigned int msg_type; +#ifdef DEBUG + int i; +#endif + int rc; + + if (!ctrl_h) + goto err; + + msg_type = GENAVB_MSG_MANAGED_GET; + rc = genavb_control_send_sync(ctrl_h, &msg_type, cmd, cmd_len, response, &response_len, 100); + if (rc != GENAVB_SUCCESS) { + printf("genavb_control_send_sync() failed: %s\n", genavb_strerror(rc)); + goto err; + } + + if (msg_type != GENAVB_MSG_MANAGED_GET_RESPONSE) { + printf("genavb_control_send_sync() wrong response message type: %d\n", msg_type); + goto err; + } + +#ifdef DEBUG + printf("%d", response_len); + + for (i = 0; i < response_len; i++) + printf(" %1x", ((uint8_t *)response)[i]); + + printf("\n"); +#endif + + return 0; + +err: + return -1; +} + +int check_response(uint16_t *expected, uint16_t *response, unsigned int len) +{ + int i; + + for (i = 0; i < len; i++) + if (expected[i] != response[i]) { + return -1; + } + + return 0; +} + +uint8_t *get_response_header(uint8_t *buf, uint16_t *id, uint16_t *length, uint16_t *status) +{ + *id = ((uint16_t *)buf)[0]; + *length = ((uint16_t *)buf)[1]; + *status = ((uint16_t *)buf)[2]; + + buf += 6; + + return buf; +} + +uint8_t *get_node_next(uint8_t *buf, uint16_t length) +{ + buf += length - 2; + + return buf; +} + +uint8_t *get_node_header(uint8_t *buf, uint16_t *id, uint16_t *length, uint16_t *status) +{ + buf = get_response_header(buf, id, length, status); + + return buf; +} diff --git a/apps/linux/management-app/common.h b/apps/linux/management-app/common.h new file mode 100644 index 0000000..22042f1 --- /dev/null +++ b/apps/linux/management-app/common.h @@ -0,0 +1,16 @@ +/* + * Copyright 2020-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef _MANAGEMENT_APP_COMMON_H_ +#define _MANAGEMENT_APP_COMMON_H_ +void usage (void); +int managed_set(struct genavb_control_handle *ctrl_h, void *cmd, unsigned int cmd_len, void *response, unsigned int response_len); +int managed_get(struct genavb_control_handle *ctrl_h, void *cmd, unsigned int cmd_len, void *response, unsigned int response_len); +int check_response(uint16_t *expected, uint16_t *response, unsigned int len); +uint8_t *get_response_header(uint8_t *buf, uint16_t *id, uint16_t *length, uint16_t *status); +uint8_t *get_node_next(uint8_t *buf, uint16_t length); +uint8_t *get_node_header(uint8_t *buf, uint16_t *id, uint16_t *length, uint16_t *status); + +#endif /* _MANAGEMENT_APP_COMMON_H_ */ diff --git a/apps/linux/management-app/gptp_main.c b/apps/linux/management-app/gptp_main.c new file mode 100644 index 0000000..af35b8e --- /dev/null +++ b/apps/linux/management-app/gptp_main.c @@ -0,0 +1,364 @@ +/* + * Copyright 2018-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include +#include +#include "common.h" + +static char *module_name = "ptp"; +static char *device_name; + + +static int dump_stats(struct genavb_control_handle *ctrl_h, unsigned int port) +{ + struct genavb_msg_managed_set_response get_response; + uint16_t cmd[38]; + uint16_t rep[7]; + uint8_t *data; + int i; + uint16_t id, length, status, total_length; + char name[][64] = { + "rxSyncCount", + "rxFollowUpCount", + "rxPdelayRequestCount", + "rxPdelayResponseCount", + "rxPdelayResponseFollowUpCount", + "rxAnnounceCount", + "rxPTPPacketDiscardCount", + "syncReceiptTimeoutCount", + "announceReceiptTimeoutCount", + "pdelayAllowedLostResponsesExceededCount", + "txSyncCount", + "txFollowUpCount", + "txPdelayRequestCount", + "txPdelayResponseCount", + "txPdelayResponseFollowUpCount", + "txAnnounceCount" + }; + + cmd[0] = 5; /* port_parameter_statistics */ + cmd[1] = 0; + + cmd[2] = 0; /* key index, port number */ + cmd[3] = 2; + cmd[4] = port; /* key value */ + cmd[1] += 3 * 2; + + for (i = 0; i < 16; i++) { + cmd[5 + 2 * i] = i + 1; /* stat index */ + cmd[5 + 2 * i + 1] = 0; + cmd[1] += 2 * 2; + } + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 5; /* port_parameter_statistics */ + rep[1] = 0xaa; + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* key index, port number */ + rep[4] = 4; + rep[5] = 0; /* status ok */ + rep[6] = port; + + if (check_response(rep, (u_int16_t *)&get_response, 7) < 0) + goto err; + + /* table header (id, total length and status) */ + data = get_response_header((uint8_t *)&get_response, &id, &total_length, &status); + + /* entry key header (port) */ + data = get_node_header(data, &id, &length, &status); + + printf("%s %s port: %u stats\n", module_name, device_name, ((uint16_t *)data)[0]); + + data = get_node_next(data, length); + + for (i = 0; i < 16; i++) { + data = get_node_header(data, &id, &length, &status); + + if (!status) + printf("%-40s %8u\n", name[i], ((uint32_t *)data)[0]); + + data = get_node_next(data, length); + } + + return 0; + +err: + printf("%s %s port: %u stats, error\n", module_name, device_name, port); + + return -1; +} + +static int set_port_state(struct genavb_control_handle *ctrl_h, unsigned int port, unsigned int enable) +{ + struct genavb_msg_managed_set_response set_response; + uint16_t cmd[8]; + uint16_t rep[10]; + + cmd[0] = 4; /* port_parameter_data_set */ + cmd[1] = 11; + cmd[2] = 0; /* key index, port number */ + cmd[3] = 2; + cmd[4] = port; /* key value */ + cmd[5] = 3; /* pttPortEnabled */ + cmd[6] = 1; + cmd[7] = enable; + + if (managed_set(ctrl_h, cmd, 4 + cmd[1], &set_response, sizeof(set_response)) < 0) + goto err; + + rep[0] = 4; /* port_parameter_data_set */ + rep[1] = 16; + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* key index, port number */ + rep[4] = 4; + rep[5] = 0; /* status ok */ + rep[6] = port; + rep[7] = 3; /* pttPortEnabled */ + rep[8] = 2; + rep[9] = 0; /* status ok */ + + if (check_response(rep, (u_int16_t *)&set_response, 10) < 0) + goto err; + + printf("%s %s set port: %u, state: %u, success\n", module_name, device_name, port, enable); + + return 0; + +err: + printf("%s %s set port: %u, state: %u, error\n", module_name, device_name, port, enable); + + return -1; +} + +static int get_port_state(struct genavb_control_handle *ctrl_h, unsigned int port) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[8]; + uint16_t rep[10]; + + cmd[0] = 4; /* port_parameter_data_set */ + cmd[1] = 11; + cmd[2] = 0; /* key index, port number */ + cmd[3] = 2; + cmd[4] = port; /* key value */ + cmd[5] = 3; /* pttPortEnabled */ + cmd[6] = 0; + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 4; /* port_parameter_data_set */ + rep[1] = 17; + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* key index, port number */ + rep[4] = 4; + rep[5] = 0; /* status ok */ + rep[6] = port; + rep[7] = 3; /* pttPortEnabled */ + rep[8] = 3; + rep[9] = 0; /* status ok */ + + if (check_response(rep, (u_int16_t *)&get_response, 10) < 0) + goto err; + + printf("%s %s get port: %u, state: %u\n", module_name, device_name, port, get_response.data[10 * 2]); + + return 0; + +err: + printf("%s %s get port: %u, state: error\n", module_name, device_name, port); + + return -1; +} + + +static int set_priority1(struct genavb_control_handle *ctrl_h, unsigned int priority1) +{ + struct genavb_msg_managed_set_response set_response; + uint16_t cmd[5]; + uint16_t rep[6]; + + cmd[0] = 0; /* default_parameter_data_set */ + cmd[1] = 5; + cmd[2] = 5; /* priority 1 */ + cmd[3] = 1; + cmd[4] = priority1; + + if (managed_set(ctrl_h, cmd, 4 + cmd[1], &set_response, sizeof(set_response)) < 0) + goto err; + + rep[0] = 0; /* default_parameter_data_set */ + rep[1] = 8; + rep[2] = 0; /* status Ok */ + rep[3] = 5; /* priority 1 */ + rep[4] = 2; + rep[5] = 0; /* status Ok */ + + if (check_response(rep, (u_int16_t *)&set_response, 6) < 0) + goto err; + + printf("%s %s set priority1: %u, success\n", module_name, device_name, priority1); + + return 0; + +err: + printf("%s %s set priority1: %u, error\n", module_name, device_name, priority1); + + return -1; +} + +static int get_priority1(struct genavb_control_handle *ctrl_h) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[4]; + uint16_t rep[6]; + + cmd[0] = 0; /* default_parameter_data_set */ + cmd[1] = 4; + cmd[2] = 5; /* priority 1 */ + cmd[3] = 0; + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 0; /* default_parameter_data_set */ + rep[1] = 9; + rep[2] = 0; /* status Ok */ + rep[3] = 5; /* priority 1 */ + rep[4] = 3; + rep[5] = 0; /* status Ok */ + + if (check_response(rep, (u_int16_t *)&get_response, 6) < 0) + goto err; + + printf("%s %s get priority1: %u\n", module_name, device_name, ((uint8_t *)&get_response)[12]); + + return 0; + +err: + printf("%s %s get priority1: error\n", module_name, device_name); + + return -1; +} + +int gptp_main(struct genavb_handle *avb_h, int argc, char *argv[]) +{ + struct genavb_control_handle *ctrl_h; + struct genavb_control_handle *endpoint_ctrl_h; + struct genavb_control_handle *bridge_ctrl_h; + unsigned int priority1; + unsigned int port; + unsigned int set; + unsigned int state; + int option; + int rc; + unsigned long optval_ul; + + rc = genavb_control_open(avb_h, &endpoint_ctrl_h, GENAVB_CTRL_GPTP); + if (rc != GENAVB_SUCCESS) + endpoint_ctrl_h = NULL; + + rc = genavb_control_open(avb_h, &bridge_ctrl_h, GENAVB_CTRL_GPTP_BRIDGE); + if (rc != GENAVB_SUCCESS) + bridge_ctrl_h = NULL; + + if ((endpoint_ctrl_h == NULL) && (bridge_ctrl_h == NULL)) + goto err_control_open; + + /* default options */ + ctrl_h = endpoint_ctrl_h; + device_name = "endpoint"; + port = 0; + set = 0; + + while ((option = getopt(argc, argv, "EBGSP:psdh")) != -1) { + /* common options */ + switch (option) { + case 'E': + ctrl_h = endpoint_ctrl_h; + device_name = "endpoint"; + break; + + case 'B': + ctrl_h = bridge_ctrl_h; + device_name = "bridge"; + break; + + case 'G': + set = 0; + break; + + case 'S': + set = 1; + break; + + case 'P': + if (h_strtoul(&optval_ul, optarg, NULL, 0) < 0) { + usage(); + rc = -1; + goto exit; + } + port = (unsigned int)optval_ul; + break; + + case 'p': + if (set) { + if (!argv[optind] || argv[optind][0] == '-' || (h_strtoul(&optval_ul, argv[optind], NULL, 0) < 0)) { + usage(); + rc = -1; + goto exit; + } + priority1 = (unsigned int)optval_ul; + rc = set_priority1(ctrl_h, priority1); + } else { + rc = get_priority1(ctrl_h); + } + break; + + case 's': + if (set) { + if (!argv[optind] || argv[optind][0] == '-' || (h_strtoul(&optval_ul, argv[optind], NULL, 0) < 0)) { + usage(); + rc = -1; + goto exit; + } + + state = (unsigned int)optval_ul; + rc = set_port_state(ctrl_h, port, state); + } else { + rc = get_port_state(ctrl_h, port); + } + break; + + case 'd': + rc = dump_stats(ctrl_h, port); + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + +exit: + if (endpoint_ctrl_h) + genavb_control_close(endpoint_ctrl_h); + + if (bridge_ctrl_h) + genavb_control_close(bridge_ctrl_h); + +err_control_open: + return rc; +} diff --git a/apps/linux/management-app/main.c b/apps/linux/management-app/main.c new file mode 100644 index 0000000..f6a61c4 --- /dev/null +++ b/apps/linux/management-app/main.c @@ -0,0 +1,47 @@ +/* + * Copyright 2018-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include +#include "common.h" + +extern int gptp_main(struct genavb_handle *avb_h, int argc, char *argv[]); +extern int srp_main(struct genavb_handle *avb_h, int argc, char *argv[]); + +int main(int argc, char *argv[]) +{ + struct genavb_handle *avb_h = NULL; + int rc; + + rc = genavb_init(&avb_h, 0); + if (rc != GENAVB_SUCCESS) { + printf("genavb_init() failed: %s\n", genavb_strerror(rc)); + rc = -1; + goto exit; + } + + if (argc < 2) { + usage(); + goto exit; + } + + if (!strcmp("ptp", argv[1]) ) { + rc = gptp_main(avb_h, argc - 1, argv + 1); + } else if (!strcmp("srp", argv[1])) { + rc = srp_main(avb_h, argc - 1 , argv + 1); + } else { + usage(); + goto exit; + } + +exit: + if (avb_h) + genavb_exit(avb_h); + + return rc; +} diff --git a/apps/linux/management-app/srp_main.c b/apps/linux/management-app/srp_main.c new file mode 100644 index 0000000..25186e2 --- /dev/null +++ b/apps/linux/management-app/srp_main.c @@ -0,0 +1,917 @@ +/* + * Copyright 2018-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include +#include "common.h" + +static char *module_name = "srp"; +static char *device_name; + +static int srp_bridge_base_table(struct genavb_control_handle *ctrl_h) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[10]; + uint16_t rep[6]; + uint8_t *data; + uint16_t id, length, status; + char name[][64] = { + "Bridge Base Table", + "msrpEnabledStatus", + "talkerPruning", + "msrpMaxFanInPorts", + "msrpLatencyMaxFrameSize" + }; + + cmd[0] = 0; /* SRP_BRIDGE_BASE_TABLE */ + cmd[1] = 16; + cmd[2] = 0; /* msrpEnabledStatus */ + cmd[3] = 0; + cmd[4] = 1; /* talkerPruning */ + cmd[5] = 0; + cmd[6] = 2; /* msrpMaxFanInPorts */ + cmd[7] = 0; + cmd[8] = 3; /* msrpLatencyMaxFrameSize */ + cmd[9] = 0; + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 0; /* bridge_base_table */ + rep[1] = 36; + rep[2] = 0; /* status Ok */ + + if (check_response(rep, (u_int16_t *)&get_response, 3) < 0) + goto err; + + printf("%s %s %s\n", module_name, device_name, name[0]); + + data = get_response_header((uint8_t *)&get_response, &id, &length, &status); + + /* msrpEnabledStatus */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[1], data[0]); + } + + data = get_node_next(data, length); + + /* talkerPruning */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[2], data[0]); + } + + data = get_node_next(data, length); + + /* msrpMaxFanInPorts */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[3], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* msrpLatencyMaxFrameSize */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[4], ((uint32_t *)data)[0]); + } + + return 0; +err: + printf("%s %s %s: error\n", module_name, device_name, name[0]); + + return -1; +} + +static int srp_bridge_port_table(struct genavb_control_handle *ctrl_h, unsigned int port) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[16]; + uint16_t rep[6]; + uint8_t *data; + uint16_t id, length, status, total_length; + char name[][64] = { + "Bridge Port Table", + "PortID", + "msrpPortEnabledStatus", + "FailedRegistrations", + "LastPDUOrigin", + "SR_PVID" + }; + + cmd[0] = 1; /* SRP_BRIDGE_PORT_TABLE */ + cmd[1] = 22; + cmd[2] = 0; /* 1st key leaf index, port number */ + cmd[3] = 2; /* 1st key length */ + cmd[4] = port; /* 1st key value */ + cmd[5] = 1; /* msrpPortEnabledStatus */ + cmd[6] = 0; + cmd[7] = 2; /* FailedRegistrations */ + cmd[8] = 0; + cmd[9] = 3; /* LastPDUOrigin */ + cmd[10] = 0; + cmd[11] = 4; /* SR_PVID */ + cmd[12] = 0; + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 1; /* bridge_port_table */ + rep[1] = 51; + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* key index, port number */ + rep[4] = 4; + rep[5] = 0; /* status ok */ + + if (check_response(rep, (u_int16_t *)&get_response, 6) < 0) + goto err; + + printf("%s %s %s\n", module_name, device_name, name[0]); + + /* table header (id, total length and status) */ + data = get_response_header((uint8_t *)&get_response, &id, &total_length, &status); + + /* PortID */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[1], ((uint16_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* msrpPortEnabledStatus */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[2], data[0]); + } + + data = get_node_next(data, length); + + /* FailedRegistrations */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %" PRIu64 "\n", name[3], ((uint64_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* LastPDUOrigin */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %02x:%02x:%02x:%02x:%02x:%02x\n", name[4], data[0], data[1], data[2], data[3], data[4], data[5]); + } + + data = get_node_next(data, length); + + /* SR_PVID */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[5], ((uint16_t *)data)[0]); + } + + return 0; +err: + printf("%s %s %s: error\n", module_name, device_name, name[0]); + + return -1; + +} + +static int srp_streams_table(struct genavb_control_handle *ctrl_h, uint8_t *key_value, uint16_t key_length) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[32]; + uint16_t rep[6]; + uint8_t *data; + int i, cmd_idx; + uint16_t id, length, status, total_length; + char name[][64] = { + "Streams Table", + "StreamID", + "StreamDestinationAddress", + "StreamVID", + "MaxFrameSize", + "MaxIntervalFrames", + "DataFramePriority", + "Rank", + }; + + cmd[0] = 3; /* SRP_STREAMS_TABLE */ + cmd[1] = 0; + + cmd[2] = 0; /* 1st key leaf index, StreamID */ + cmd[3] = key_length; /* 1st key length */ + cmd_idx = 4; + cmd[1] += 4; + + if (key_length) { + memcpy(&cmd[cmd_idx], key_value, key_length); /* 1st key value */ + cmd_idx += (key_length / sizeof(uint16_t)); + cmd[1] += key_length; + } + + for (i = 0; i < 6 /* SRP_STREAM_NUM_LEAVES */; i++) { + cmd[cmd_idx + 2 * i] = i + 1; /* stream parameter index */ + cmd[cmd_idx + 2 * i + 1] = 0; + cmd[1] += 4; + } + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 3; /* srp_stream_table */ + rep[1] = 0; /* variable length */ + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* streamID key */ + + if (check_response(&rep[2], &((u_int16_t *)&get_response)[2], 2) < 0) + goto err; + + printf("%s %s %s\n", module_name, device_name, name[0]); + + /* table header (id, total length and status) */ + data = get_response_header((uint8_t *)&get_response, &id, &total_length, &status); + + while (data < ((uint8_t *)&get_response + (total_length + 4))) { + /* 1st key (StreamID) */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %016" PRIx64 "\n", name[1], ((uint64_t *)data)[0]); + } else + printf("%-40s failed %u\n", name[1], status); + + data = get_node_next(data, length); + + /* StreamDestinationAddress */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %02x:%02x:%02x:%02x:%02x:%02x\n", name[2], data[0], data[1], data[2], data[3], data[4], data[5]); + } + + data = get_node_next(data, length); + + /* StreamVID */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[3], ((uint16_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* MaxFrameSize */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[4], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* MaxIntervalFrames */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[5], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* DataFramePriority */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[6], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* Rank */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[7], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + printf("\n"); + } + + return 0; +err: + printf("%s %s %s: error\n", module_name, device_name, name[0]); + + return -1; +} + + +static int srp_reservations_table(struct genavb_control_handle *ctrl_h, unsigned int port, unsigned int direction, uint8_t *key_value, uint16_t key_length) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[64]; + uint16_t rep[6]; + uint8_t *data; + int i, cmd_idx; + uint16_t id, length, status, total_length; + char name[][64] = { + "Reservations Table", + "PortID", + "StreamID", + "Direction", + "DeclarationType", + "AccumulatedLatency", + "FailedBridgeId", + "FailureCode", + "DroppedFrames", + "StreamAge", + }; + + cmd[0] = 4; /* SRP_RESERVATIONS_TABLE */ + cmd[1] = 0; + + cmd[2] = 0; /* 1st key index, port number */ + cmd[3] = 2; /* key length */ + cmd[4] = port; /* key value */ + cmd[1] += 6; + + cmd[5] = 1; /* 2nd key leaf, stream ID */ + cmd[6] = key_length; /* key length */ + cmd_idx = 7; + cmd[1] += 4; + + if (key_length) { + memcpy(&cmd[cmd_idx], key_value, key_length); + cmd_idx += (key_length / sizeof(uint16_t)); + cmd[1] += key_length; + } + + cmd[cmd_idx] = 2; /* 3rd key index, direction */ + cmd_idx++; + cmd[cmd_idx] = 2; /* key length */ + cmd_idx++; + cmd[cmd_idx] = direction; /* key value */ + cmd_idx++; + cmd[1] += 6; + + for (i = 0; i < 6 /* SRP_RESERVATIONS_NUM_LEAVES */; i++) { + cmd[cmd_idx + 2 * i] = i + 3; /* start at DeclarationType parameter index */ + cmd[cmd_idx + 2 * i + 1] = 0; + cmd[1] += 4; + } + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 4; /* srp_reservation_table */ + rep[1] = 0; /* variable length */ + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* list entry */ + rep[4] = 4; + rep[5] = 0; /* status ok */ + + if (check_response(&rep[2], &((u_int16_t *)&get_response)[2], 4) < 0) + goto err; + + printf("%s %s %s\n", module_name, device_name, name[0]); + + /* table header (id, total length and status) */ + data = get_response_header((uint8_t *)&get_response, &id, &total_length, &status); + + while (data < ((uint8_t *)&get_response + (total_length + 4))) { + /* 1st key, PortID */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[1], ((uint16_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* 2nd key, StreamID */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %016" PRIx64 "\n", name[2], ((uint64_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* 3rd key, Direction */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[3], ((uint16_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* DeclarationType */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[4], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* AccumulatedLatency */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[5], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* FailedBridgeId */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %" PRIx64 "\n", name[6], ((uint64_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* FailureCode */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[7], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* DroppedFrames */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %" PRIu64 "\n", name[8], ((uint64_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* StreamAge */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[9], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + printf("\n"); + } + + return 0; +err: + printf("%s %s %s: error\n", module_name, device_name, name[0]); + + return -1; +} + +static int srp_latency_parameter_table(struct genavb_control_handle *ctrl_h, unsigned int port, unsigned short tc_class) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[16]; + uint16_t rep[32]; + uint8_t *data; + uint16_t id, length, status, total_length; + char name[][64] = { + "Latency Parameters Table", + "PortID", + "TrafficClass", + "PortTcMaxLatency", + }; + + cmd[0] = 2; /* SRP_LATENCY_TABLE */ + cmd[1] = 0; + + cmd[2] = 0; /* key index, port number */ + cmd[3] = 2; + cmd[4] = port; /* key value */ + cmd[1] += 6; + + cmd[5] = 1; /* key index, traffic class */ + cmd[6] = 4; + cmd[7] = tc_class; + cmd[8] = 0; + cmd[1] += 8; + + cmd[9] = 2; /* latency value */ + cmd[10] = 0; + cmd[1] += 4; + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 2; /* srp_latency_table */ + rep[1] = 30;/* total length */ + rep[2] = 0; /* status ok */ + + rep[3] = 0; /* port index */ + rep[4] = 4; + rep[5] = 0; /* status ok */ + rep[6] = port; /*value */ + + rep[7] = 1; /* traffic class index */ + rep[8] = 6; + rep[9] = 0; /* status ok */ + rep[10] = tc_class; /* value */ + rep[11] = 0; + + if (check_response(rep, (u_int16_t *)&get_response, 12) < 0) + goto err; + + printf("%s %s %s\n", module_name, device_name, name[0]); + + /* table header (id, total length and status) */ + data = get_response_header((uint8_t *)&get_response, &id, &total_length, &status); + + /* 1st key, PortID */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[1], ((uint16_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* 2nd key, Traffic Class */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[2], ((uint32_t *)data)[0]); + } + + data = get_node_next(data, length); + + /* PortTcMaxLatency */ + data = get_node_header(data, &id, &length, &status); + if (!status) { + printf("%-40s %u\n", name[3], ((uint32_t *)data)[0]); + } + + return 0; +err: + printf("%s %s %s: error\n", module_name, device_name, name[0]); + + return -1; +} + +static int set_msrp_enabled_status(struct genavb_control_handle *ctrl_h, unsigned int enable) +{ + struct genavb_msg_managed_set_response set_response; + uint16_t cmd[5]; + uint16_t rep[6]; + + cmd[0] = 0; /* SRP_BRIDGE_BASE_TABLE */ + cmd[1] = 5; + cmd[2] = 0; /* msrpEnabledStatus */ + cmd[3] = 1; + ((uint8_t *)cmd)[4 * 2] = enable; + + if (managed_set(ctrl_h, cmd, 9, &set_response, sizeof(set_response)) < 0) + goto err; + + rep[0] = 0; /* SRP_BRIDGE_BASE_TABLE */ + rep[1] = 8; + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* msrpEnabledStatus */ + rep[4] = 2; + rep[5] = 0; /* status ok */ + + if (check_response(rep, (u_int16_t *)&set_response, 6) < 0) + goto err; + + printf("%s %s set state: %u, success\n", module_name, device_name, enable); + + return 0; + +err: + printf("%s %s set state: %u, error\n", module_name, device_name, enable); + + return -1; +} + +static int get_msrp_enabled_status(struct genavb_control_handle *ctrl_h) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[5]; + uint16_t rep[6]; + + cmd[0] = 0; /* SRP_BRIDGE_BASE_TABLE */ + cmd[1] = 6; + cmd[2] = 0; /* msrpEnabledStatus */ + cmd[3] = 2; + cmd[4] = 0; + + if (managed_get(ctrl_h, cmd, 5 * 2, &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 0; /* SRP_BRIDGE_BASE_TABLE */ + rep[1] = 9; + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* msrpEnabledStatus */ + rep[4] = 3; + rep[5] = 0; /* status ok */ + + if (check_response(rep, (u_int16_t *)&get_response, 6) < 0) + goto err; + + printf("%s %s get state: %u\n", module_name, device_name, get_response.data[6 * 2]); + + return 0; + +err: + printf("%s %s get state: error\n", module_name, device_name); + + return -1; +} + +static int set_msrp_port_enabled_status(struct genavb_control_handle *ctrl_h, unsigned int port, unsigned int enable) +{ + struct genavb_msg_managed_set_response set_response; + uint16_t cmd[8]; + uint16_t rep[10]; + + cmd[0] = 1; /* SRP_BRIDGE_PORT_TABLE */ + cmd[1] = 11; + cmd[2] = 0; /* 1st key leaf index, port number */ + cmd[3] = 2; /* 1st key length */ + cmd[4] = port; /* 1st key value */ + cmd[5] = 1; /* msrpPortEnabledStatus */ + cmd[6] = 1; + ((uint8_t *)cmd)[7 * 2] = enable; + + if (managed_set(ctrl_h, cmd, 4 + cmd[1], &set_response, sizeof(set_response)) < 0) + goto err; + + rep[0] = 1; /* SRP_BRIDGE_PORT_TABLE */ + rep[1] = 16; + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* 1st key leaf index, port number */ + rep[4] = 4; /* 1st key length */ + rep[5] = 0; /* status ok */ + rep[6] = port; /* 1st key value */ + rep[7] = 1; /* msrpPortEnabledStatus */ + rep[8] = 2; + rep[9] = 0; /* status ok */ + + if (check_response(rep, (u_int16_t *)&set_response, 10) < 0) + goto err; + + printf("%s %s set port %u state: %u, success\n", module_name, device_name, port, enable); + + return 0; + +err: + printf("%s %s set port %u state: %u, error\n", module_name, device_name, port, enable); + + return -1; +} + +static int get_msrp_port_enabled_status(struct genavb_control_handle *ctrl_h, unsigned int port) +{ + struct genavb_msg_managed_get_response get_response; + uint16_t cmd[8]; + uint16_t rep[10]; + + cmd[0] = 1; /* SRP_BRIDGE_PORT_TABLE */ + cmd[1] = 11; + cmd[2] = 0; /* 1st key leaf index, port number */ + cmd[3] = 2; /* 1st key length */ + cmd[4] = port; /* 1st key value */ + cmd[5] = 1; /* msrpPortEnabledStatus */ + cmd[6] = 1; + cmd[7] = 0; + + if (managed_get(ctrl_h, cmd, 4 + cmd[1], &get_response, sizeof(get_response)) < 0) + goto err; + + rep[0] = 1; /* SRP_BRIDGE_PORT_TABLE */ + rep[1] = 17; + rep[2] = 0; /* status Ok*/ + rep[3] = 0; /* 1st key leaf index, port number */ + rep[4] = 4; /* 1st key length */ + rep[5] = 0; /* status ok */ + rep[6] = port; /* 1st key value */ + rep[7] = 1; /* msrpPortEnabledStatus */ + rep[8] = 3; + rep[9] = 0; /* status ok */ + + if (check_response(rep, (u_int16_t *)&get_response, 10) < 0) + goto err; + + printf("%s %s get port %u state: %u\n", module_name, device_name, port, get_response.data[10 * 2]); + + return 0; + +err: + printf("%s %s get port %u state: error\n", module_name, device_name, port); + + return -1; +} + +int srp_main(struct genavb_handle *avb_h, int argc, char *argv[]) +{ + struct genavb_control_handle *ctrl_h; + struct genavb_control_handle *endpoint_msrp_ctrl_h; + struct genavb_control_handle *bridge_msrp_ctrl_h; + unsigned int port, direction; + unsigned int set; + unsigned int state; + unsigned long optval_ul; + unsigned long long stream_key; + unsigned long tc_key; + unsigned int key_len; + int option; + int rc = 0; + + rc = genavb_control_open(avb_h, &endpoint_msrp_ctrl_h, GENAVB_CTRL_MSRP); + if (rc != GENAVB_SUCCESS) + endpoint_msrp_ctrl_h = NULL; + + rc = genavb_control_open(avb_h, &bridge_msrp_ctrl_h, GENAVB_CTRL_MSRP_BRIDGE); + if (rc != GENAVB_SUCCESS) + bridge_msrp_ctrl_h = NULL; + + if ((endpoint_msrp_ctrl_h == NULL) && (bridge_msrp_ctrl_h == NULL)) + goto err_control_open; + + /* default options */ + ctrl_h = endpoint_msrp_ctrl_h; + device_name = "endpoint"; + port = 0; + direction = 0; + set = 0; + + while ((option = getopt(argc, argv, "EBGMSP:D:s::r::pebl:h")) != -1) { + + /* common options */ + switch (option) { + case 'E': + ctrl_h = endpoint_msrp_ctrl_h; + device_name = "endpoint"; + break; + + case 'B': + ctrl_h = bridge_msrp_ctrl_h; + device_name = "bridge"; + break; + + case 'G': + set = 0; + break; + + case 'S': + set = 1; + break; + + case 'P': + if (h_strtoul(&optval_ul, optarg, NULL, 0) < 0) { + usage(); + rc = -1; + goto exit; + } + port = (unsigned int)optval_ul; + break; + + case 'D': + if (h_strtoul(&optval_ul, optarg, NULL, 0) < 0) { + usage(); + rc = -1; + goto exit; + } + direction = (unsigned int)optval_ul; + break; + + case 's': + /* streams table */ + if (set) { + usage(); + rc = -1; + goto exit; + } else { + /* stream ID */ + if (!argv[optind]) { + key_len = 0; + } else { + key_len = sizeof(uint64_t); + if (h_strtoull(&stream_key, argv[optind], NULL, 0) < 0) { + usage(); + rc = -1; + goto exit; + } + } + rc = srp_streams_table(ctrl_h, (uint8_t *)&stream_key, key_len); + } + break; + + case 'r': + /* reservation table */ + if (set) { + usage(); + rc = -1; + goto exit; + } else { + /* Stream ID */ + if (!argv[optind]) { + key_len = 0; + } else { + key_len = sizeof(uint64_t); + if (h_strtoull(&stream_key, argv[optind], NULL, 0) < 0) { + usage(); + rc = -1; + goto exit; + } + } + rc = srp_reservations_table(ctrl_h, port, direction, (uint8_t *)&stream_key, key_len); + } + break; + + case 'b': + /* bridge base table */ + if (set) { + usage(); + rc = -1; + goto exit; + } else { + rc = srp_bridge_base_table(ctrl_h); + } + break; + + case 'p': + /* bridge ports table */ + if (set) { + usage(); + rc = -1; + goto exit; + } else { + rc = srp_bridge_port_table(ctrl_h, port); + } + break; + + case 'l': + /* latency parameters table */ + if (set) { + usage(); + rc = -1; + goto exit; + } else { + /* Traffic Class */ + if (h_strtoul(&tc_key, optarg, NULL, 0) < 0) { + usage(); + rc = -1; + goto exit; + } + rc = srp_latency_parameter_table(ctrl_h, port, tc_key); + } + break; + + case 'e': + /* msrp port enabled status */ + if (set) { + if (!argv[optind] || argv[optind][0] == '-' || (h_strtoul(&optval_ul, argv[optind], NULL, 0) < 0)) { + usage(); + rc = -1; + goto exit; + } + + state = (unsigned int)optval_ul; + rc = set_msrp_port_enabled_status(ctrl_h, port, state); + } else { + rc = get_msrp_port_enabled_status(ctrl_h, port); + } + + break; + + case 'M': + /* msrp enabled status */ + if (set) { + if (!argv[optind] || argv[optind][0] == '-' || (h_strtoul(&optval_ul, argv[optind], NULL, 0) < 0)) { + usage(); + rc = -1; + goto exit; + } + + state = (unsigned int)optval_ul; + rc = set_msrp_enabled_status(ctrl_h, state); + } else { + rc = get_msrp_enabled_status(ctrl_h); + } + + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + +exit: + if (endpoint_msrp_ctrl_h) + genavb_control_close(endpoint_msrp_ctrl_h); + + if (bridge_msrp_ctrl_h) + genavb_control_close(bridge_msrp_ctrl_h); +err_control_open: + return rc; +} diff --git a/apps/linux/msrp-ctrl-app/CMakeLists.txt b/apps/linux/msrp-ctrl-app/CMakeLists.txt new file mode 100644 index 0000000..99728be --- /dev/null +++ b/apps/linux/msrp-ctrl-app/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) + +project(msrp-ctrl-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c +) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) +target_link_libraries(${PROJECT_NAME} -Wl,-unresolved-symbols=ignore-in-shared-libs) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/msrp-ctrl-app/main.c b/apps/linux/msrp-ctrl-app/main.c new file mode 100644 index 0000000..4c1a41d --- /dev/null +++ b/apps/linux/msrp-ctrl-app/main.c @@ -0,0 +1,521 @@ +/* + * Copyright 2018, 2020-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DEFAULT_PORT 0 +#define DEFAULT_STREAM_ID 0x0011223344556677 +#define DEFAULT_SR_CLASS SR_CLASS_A +#define DEFAULT_MAC_ADDR {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff} +#define DEFAULT_VID VLAN_VID_DEFAULT +#define DEFAULT_MAX_FRAME_SIZE 128 +#define DEFAULT_MAX_INTERVAL_FRAMES 1 +#define DEFAULT_ACCUMULATED_LATENCY 10000 +#define DEFAULT_RANK NORMAL + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\nOptions:\n" + "\t-t talker stream\n" + "\t-l listener stream\n" + "\t-r register stream\n" + "\t-d deregister stream\n" + "\t-w wait and dump indications\n" + "\t-p port id: default 0\n" + "\t-s stream id: default 0x0011223344556677\n" + "\t-c sr class: default 0 (SR_CLASS_A)\n" + "\t-m mac addr: default aa:bb:cc:dd:ee:ff\n" + "\t-v vlan id\n" + "\t-f max frame size in bytes: default 128\n" + "\t-i max interval frames: default 1\n" + "\t-a accumulated latency in nanoseconds: default 10000\n" + "\t-k rank: default 1 (normal)\n" + "\t-h print this help text\n" + "\nTalker registration:\n" + "\t-t -r\n" + "\t-t -p 1 -s 0x0123456789abcdef -m 00:34:56:78:aa:55 -v 2 -f 256 -i 2 -r\n" + "\nListener registration:\n" + "\t-l -r\n" + "\t-l -p 1 -s 0x0123456789abcdef -r\n" + "\nTalker deregistration:\n" + "\t-t -d\n" + "\t-t -p 1 -s 0x0123456789abcdef -d\n" + "\nListener deregistration:\n" + "\t-l -d\n" + "\t-l -p 1 -s 0x0123456789abcdef -d\n"); + +} + +static int msrp_listener_register(struct genavb_control_handle *ctrl_h, uint16_t port, uint64_t stream_id) +{ + struct genavb_msg_listener_register listener_register; + unsigned int msg_type; + union genavb_msg_msrp msg; + unsigned int msg_len; + int rc; + + listener_register.port = port; + memcpy(listener_register.stream_id, &stream_id, 8); + + msg_type = GENAVB_MSG_LISTENER_REGISTER; + msg_len = sizeof(msg); + rc = genavb_control_send_sync(ctrl_h, &msg_type, &listener_register, sizeof(listener_register), &msg, &msg_len, 10); + if (rc < 0) { + printf("genavb_control_send_sync() failed: %s\n", genavb_strerror(rc)); + goto err; + } else if (msg_type != GENAVB_MSG_LISTENER_RESPONSE) { + printf("response type error: %d\n", msg_type); + goto err; + } else if (msg.listener_response.status != GENAVB_SUCCESS) { + printf("response status error: %s\n", genavb_strerror(msg.listener_response.status)); + goto err; + } + + printf("Listener registered\n"); + + return 0; + +err: + return -1; +} + +static int msrp_listener_deregister(struct genavb_control_handle *ctrl_h, uint16_t port, uint64_t stream_id) +{ + struct genavb_msg_listener_deregister listener_deregister; + unsigned int msg_type; + union genavb_msg_msrp msg; + unsigned int msg_len; + int rc; + + listener_deregister.port = port; + memcpy(listener_deregister.stream_id, &stream_id, 8); + + msg_type = GENAVB_MSG_LISTENER_DEREGISTER; + msg_len = sizeof(msg); + rc = genavb_control_send_sync(ctrl_h, &msg_type, &listener_deregister, sizeof(listener_deregister), &msg, &msg_len, 10); + if (rc < 0) { + printf("genavb_control_send_sync() failed: %s\n", genavb_strerror(rc)); + goto err; + } else if (msg_type != GENAVB_MSG_LISTENER_RESPONSE) { + printf("response type error: %d\n", msg_type); + goto err; + } else if (msg.listener_response.status != GENAVB_SUCCESS) { + printf("response status error: %s\n", genavb_strerror(msg.listener_response.status)); + goto err; + } + + printf("Listener deregistered\n"); + + return 0; + +err: + return -1; +} + +static int msrp_talker_register(struct genavb_control_handle *ctrl_h, uint16_t port, uint64_t stream_id, sr_class_t sr_class, + uint8_t *mac_addr, uint16_t vid, uint16_t max_frame_size, uint16_t max_interval_frames, + uint32_t accumulated_latency, msrp_rank_t rank) +{ + struct genavb_msg_talker_register talker_register; + unsigned int msg_type; + union genavb_msg_msrp msg; + unsigned int msg_len; + int rc; + + talker_register.port = port; + memcpy(talker_register.stream_id, &stream_id, 8); + talker_register.params.stream_class = sr_class; + memcpy(talker_register.params.destination_address, mac_addr, 6); + talker_register.params.vlan_id = vid; + talker_register.params.max_frame_size = max_frame_size; + talker_register.params.max_interval_frames = max_interval_frames; + talker_register.params.accumulated_latency = accumulated_latency; + talker_register.params.rank = rank; + + msg_type = GENAVB_MSG_TALKER_REGISTER; + msg_len = sizeof(msg); + rc = genavb_control_send_sync(ctrl_h, &msg_type, &talker_register, sizeof(talker_register), &msg, &msg_len, 10); + if (rc < 0) { + printf("genavb_control_send_sync() failed: %s\n", genavb_strerror(rc)); + goto err; + } else if (msg_type != GENAVB_MSG_TALKER_RESPONSE) { + printf("response type error: %d\n", msg_type); + goto err; + } else if (msg.talker_response.status != GENAVB_SUCCESS) { + printf("response status error: %s\n", genavb_strerror(msg.talker_response.status)); + goto err; + } + + printf("Talker registered\n"); + + return 0; + +err: + return -1; +} + +static int msrp_talker_deregister(struct genavb_control_handle *ctrl_h, uint16_t port, uint64_t stream_id) +{ + struct genavb_msg_talker_deregister talker_deregister; + unsigned int msg_type; + union genavb_msg_msrp msg; + unsigned int msg_len; + int rc; + + talker_deregister.port = port; + memcpy(talker_deregister.stream_id, &stream_id, 8); + + msg_type = GENAVB_MSG_TALKER_DEREGISTER; + msg_len = sizeof(msg); + rc = genavb_control_send_sync(ctrl_h, &msg_type, &talker_deregister, sizeof(talker_deregister), &msg, &msg_len, 10); + if (rc < 0) { + printf("genavb_control_send_sync() failed: %s\n", genavb_strerror(rc)); + goto err; + } else if (msg_type != GENAVB_MSG_TALKER_RESPONSE) { + printf("response type error: %d\n", msg_type); + goto err; + } else if (msg.talker_response.status != GENAVB_SUCCESS) { + printf("response status error: %s\n", genavb_strerror(msg.talker_response.status)); + goto err; + } + + printf("Talker deregistered\n"); + + return 0; + +err: + return -1; +} + +static const char *talker_status_str[] = { + [NO_LISTENER] = "NO_LISTENER", + [FAILED_LISTENER] = "FAILED_LISTENER", + [ACTIVE_AND_FAILED_LISTENERS] = "ACTIVE_AND_FAILED_LISTENERS", + [ACTIVE_LISTENER] = "ACTIVE_LISTENER" +}; + +static const char *failure_code_str[] = { + [INSUFFICIENT_BANDWIDTH] = "INSUFFICIENT_BANDWIDTH", + [INSUFFICIENT_BRIDGE_RESOURCES] = "INSUFFICIENT_BRIDGE_RESOURCES", + [INSUFFICIENT_BANDWIDTH_FOR_TRAFFIC_CLASS] = "INSUFFICIENT_BANDWIDTH_FOR_TRAFFIC_CLASS", + [STREAM_ID_ALREADY_IN_USE] = "STREAM_ID_ALREADY_IN_USE", + [STREAM_DESTINATION_ADDRESS_ALREADY_IN_USE] = "STREAM_DESTINATION_ADDRESS_ALREADY_IN_USE", + [STREAM_PREEMPTED_BY_HIGHER_RANK] = "STREAM_PREEMPTED_BY_HIGHER_RANK", + [REPORTED_LATENCY_HAS_CHANGED] = "REPORTED_LATENCY_HAS_CHANGED", + [EGRESS_PORT_IS_NOT_AVB_CAPABLE] = "EGRESS_PORT_IS_NOT_AVB_CAPABLE", + [USE_DIFFERENT_DESTINATION_ADDRESS] = "USE_DIFFERENT_DESTINATION_ADDRESS", + [OUT_OF_MSRP_RESOURCES] = "OUT_OF_MSRP_RESOURCES", + [OUT_OF_MMRP_RESOURCES] = "OUT_OF_MMRP_RESOURCES", + [CANNOT_STORE_DESTINATION_ADDRESS] = "CANNOT_STORE_DESTINATION_ADDRESS", + [REQUESTED_PRIORITY_IS_NOT_AN_SR_CLASS_PRIORITY] = "REQUESTED_PRIORITY_IS_NOT_AN_SR_CLASS_PRIORITY", + [MAX_FRAME_SIZE_TOO_LARGE_FOR_MEDIA] = "MAX_FRAME_SIZE_TOO_LARGE_FOR_MEDIA", + [FAN_IN_PORT_LIMIT_REACHED] = "FAN_IN_PORT_LIMIT_REACHED", + [CHANGE_IN_FIRST_VALUE_FOR_REGISTED_STREAM_ID] = "CHANGE_IN_FIRST_VALUE_FOR_REGISTED_STREAM_ID", + [VLAN_BLOCKED_ON_EGRESS_PORT] = "VLAN_BLOCKED_ON_EGRESS_PORT", + [VLAN_TAGGING_DISABLED_ON_EGRESS_PORT] = "VLAN_TAGGING_DISABLED_ON_EGRESS_PORT", + [SR_CLASS_PRIORITY_MISMATCH] = "SR_CLASS_PRIORITY_MISMATCH" +}; + + +static const char *listener_status_str[] = { + [NO_TALKER] = "NO_TALKER", + [ACTIVE] = "ACTIVE", + [FAILED] = "FAILED" +}; + +static void msrp_listen(struct genavb_control_handle *ctrl_h, int ctrl_fd) +{ + unsigned int msg_type; + union genavb_msg_msrp msg; + unsigned int msg_len; + fd_set set; + int rc; + + while (1) { + FD_ZERO(&set); + FD_SET(ctrl_fd, &set); + + rc = select(ctrl_fd + 1, &set, NULL, NULL, NULL); + if (rc < 0) { + printf ("select() failed: %s\n", strerror(errno)); + break; + } + + if (!FD_ISSET(ctrl_fd, &set)) + continue; + + msg_len = sizeof(msg); + + rc = genavb_control_receive(ctrl_h, &msg_type, &msg, &msg_len); + if (rc < 0) { + printf ("genavb_control_receive() failed: %s\n", genavb_strerror(rc)); + break; + } + + switch (msg_type) { + case GENAVB_MSG_TALKER_RESPONSE: + break; + + case GENAVB_MSG_LISTENER_RESPONSE: + break; + + case GENAVB_MSG_TALKER_DECLARATION_STATUS: + break; + + case GENAVB_MSG_LISTENER_DECLARATION_STATUS: + break; + + case GENAVB_MSG_LISTENER_STATUS: + printf ("(%u) Talker stream: %02x%02x%02x%02x%02x%02x%02x%02x, port: %u, status: %u, %s\n", + getpid(), + msg.listener_status.stream_id[0], msg.listener_status.stream_id[1], msg.listener_status.stream_id[2], msg.listener_status.stream_id[3], + msg.listener_status.stream_id[4], msg.listener_status.stream_id[5], msg.listener_status.stream_id[6], msg.listener_status.stream_id[7], + msg.listener_status.port, msg.listener_status.status, listener_status_str[msg.listener_status.status]); + + if (msg.listener_status.status == ACTIVE) + printf(" class: %u, mac: %02x%02x%02x%02x%02x%02x, vid: %u, frame size: %u, interval frames: %u, latency: %u, rank: %u\n", + msg.listener_status.params.stream_class, + msg.listener_status.params.destination_address[0], msg.listener_status.params.destination_address[1], msg.listener_status.params.destination_address[2], + msg.listener_status.params.destination_address[3], msg.listener_status.params.destination_address[4], msg.listener_status.params.destination_address[5], + msg.listener_status.params.vlan_id, + msg.listener_status.params.max_frame_size, msg.listener_status.params.max_interval_frames, + msg.listener_status.params.accumulated_latency, + msg.listener_status.params.rank); + else if (msg.listener_status.status == FAILED) + printf(" bridge id: %02x%02x%02x%02x%02x%02x, failure code: %u, %s\n", + msg.listener_status.failure.bridge_id[0], msg.listener_status.failure.bridge_id[1], msg.listener_status.failure.bridge_id[2], + msg.listener_status.failure.bridge_id[3], msg.listener_status.failure.bridge_id[4], msg.listener_status.failure.bridge_id[5], + msg.listener_status.failure.failure_code, failure_code_str[msg.listener_status.failure.failure_code]); + + break; + + case GENAVB_MSG_TALKER_STATUS: + printf ("(%u) Listener stream: %02x%02x%02x%02x%02x%02x%02x%02x, port: %u, status: %u, %s\n", + getpid(), + msg.talker_status.stream_id[0], msg.talker_status.stream_id[1], msg.talker_status.stream_id[2], msg.talker_status.stream_id[3], + msg.talker_status.stream_id[4], msg.talker_status.stream_id[5], msg.talker_status.stream_id[6], msg.talker_status.stream_id[7], + msg.talker_status.port, msg.talker_status.status, talker_status_str[msg.talker_status.status]); + break; + + default: + printf ("Unexpected message type %d\n", msg_type); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + struct genavb_handle *avb_h; + struct genavb_control_handle *ctrl_h; + unsigned long port = DEFAULT_PORT; + unsigned long long stream_id = DEFAULT_STREAM_ID; + unsigned long sr_class = DEFAULT_SR_CLASS; + uint8_t mac_addr[6] = DEFAULT_MAC_ADDR; + unsigned long vid = DEFAULT_VID; + unsigned long max_frame_size = DEFAULT_MAX_FRAME_SIZE; + unsigned long max_interval_frames = DEFAULT_MAX_INTERVAL_FRAMES; + unsigned long accumulated_latency = DEFAULT_ACCUMULATED_LATENCY; + unsigned long rank = DEFAULT_RANK; + bool talker = false; + int ctrl_fd; + int option; + int rc = 0; + + setlinebuf(stdout); + + printf("NXP's GenAVB MRP control application\n"); + + /* + * setup the avb stack + */ + + rc = genavb_init(&avb_h, 0); + if (rc != GENAVB_SUCCESS) { + printf("genavb_init() failed: %s\n", genavb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + rc = genavb_control_open(avb_h, &ctrl_h, GENAVB_CTRL_MSRP); + if (rc != GENAVB_SUCCESS) { + printf("genavb_control_open() failed: %s\n", genavb_strerror(rc)); + rc = -1; + goto error_control_open; + } + + ctrl_fd = genavb_control_rx_fd(ctrl_h); + + /* + * retrieve user's configuration parameters + */ + + while ((option = getopt(argc, argv,"p:s:c:m:v:f:i:a:k:tlrdw")) != -1) { + switch (option) { + case 'p': + if (h_strtoul(&port, optarg, NULL, 0) < 0) { + printf("invalid -p %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 's': + if (h_strtoull(&stream_id, optarg, NULL, 0) < 0) { + printf("invalid -s %s option\n", optarg); + rc = -1; + goto exit; + } + + stream_id = htonll(stream_id); + break; + + case 'c': + if (h_strtoul(&sr_class, optarg, NULL, 0) < 0) { + printf("invalid -c %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'm': + if (sscanf(optarg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac_addr[0], &mac_addr[1], &mac_addr[2], &mac_addr[3], &mac_addr[4], &mac_addr[5]) < 6) { + printf("invalid -m %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'v': + if (h_strtoul(&vid, optarg, NULL, 0) < 0) { + printf("invalid -v %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'f': + if (h_strtoul(&max_frame_size, optarg, NULL, 0) < 0) { + printf("invalid -f %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'i': + if (h_strtoul(&max_interval_frames, optarg, NULL, 0) < 0) { + printf("invalid -i %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'a': + if (h_strtoul(&accumulated_latency, optarg, NULL, 0) < 0) { + printf("invalid -a %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 'k': + if (h_strtoul(&rank, optarg, NULL, 0) < 0) { + printf("invalid -k %s option\n", optarg); + rc = -1; + goto exit; + } + + break; + + case 't': + talker = true; + break; + + case 'l': + talker = false; + break; + + case 'r': + if (talker) + rc = msrp_talker_register(ctrl_h, port, stream_id, sr_class, mac_addr, vid, max_frame_size, max_interval_frames, accumulated_latency, rank); + else + rc = msrp_listener_register(ctrl_h, port, stream_id); + + break; + + case 'd': + if (talker) + rc = msrp_talker_deregister(ctrl_h, port, stream_id); + else + rc = msrp_listener_deregister(ctrl_h, port, stream_id); + + break; +#if 0 + case 'c': { + struct genavb_control_handle *handle; + + while (1) { + rc = genavb_control_open(avb_h, &handle, GENAVB_CTRL_MSRP); + if (rc < 0) + printf("genavb_control_open() failed: %s\n", genavb_strerror(rc)); + + msrp_listener_register(handle, port, stream_id); + + rc = genavb_control_close(handle); + if (rc < 0) + printf("genavb_control_close() failed: %s\n", genavb_strerror(rc)); + + } + } + case 'e': + + while (1) { + msrp_listener_register(ctrl_h, port, stream_id); + msrp_listener_deregister(ctrl_h, port, stream_id); + } + + break; +#endif + case 'w': + msrp_listen(ctrl_h, ctrl_fd); + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + +exit: + genavb_control_close(ctrl_h); + +error_control_open: + genavb_exit(avb_h); + +error_avb_init: + return rc; +} diff --git a/apps/linux/salsacamctrl/CMakeLists.txt b/apps/linux/salsacamctrl/CMakeLists.txt new file mode 100644 index 0000000..27782c0 --- /dev/null +++ b/apps/linux/salsacamctrl/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.10) + +project(salsacamctrl) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + udpcc2.c +) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) +install(PROGRAMS salsacam-cmd.sh DESTINATION usr/bin) +install(PROGRAMS salsacam-setup.sh DESTINATION usr/bin) +install(FILES salsacam-configs.inc DESTINATION etc/genavb) diff --git a/apps/linux/salsacamctrl/main.c b/apps/linux/salsacamctrl/main.c new file mode 100644 index 0000000..f4214e3 --- /dev/null +++ b/apps/linux/salsacamctrl/main.c @@ -0,0 +1,349 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020, 2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include "udpcc2.h" + +#include "genavb/helpers.h" + +static void usage (void) +{ + printf("\nUsage:\nsalsacamctrl [options]\n"); + printf("\nOptions:\n" + "\t-i IP address of the camera to control\n" + "\t-p UDP control port to connect to (default port will be used if none is specified)\n" + "\n" + "\t-R Reboot camera\n" + "\t-S Start camera\n" + "\t-K Stop camera\n" + "\n" + "\t-I Set camera IPv4 address, in the form a.b.c.d. DANGEROUS (an incorrect value may render the camera unreachable). Only RFC1918 unicast addresses are allowed.\n" + "\t-M Set camera MAC address, in the form uu:vv:ww:xx:yy:zz. DANGEROUS. \n" + "\t-B Set camera stream bandwidth\n" + "\t-A Set camera AVB stream ID\n" + "\t-T Set stream type:\n" + "\t -T 1 for AVB\n" + "\t -T 0 for UDP\n" + "\t-Y Set PHY:\n" + "\t -Y 0 for 100BaseT\n" + "\t -Y 1 for BroadR-Reach\n" + "\t-V E:xxxx Set camera VLAN configuration for AVB streams:\n" + "\t E = 0 to disable VLANS, E = 1 to enable VLANs\n" + "\t xxxx: VLAN ID to configure\n" + "\t-Z Set auto-start:\n" + "\t -Z 1 to start streaming on boot\n" + "\t -Z 0 to wait for start command\n" + "\n" + "\t-s Get current camera state\n" + "\t-m Get current camera MAC address\n" + "\t-b Get current camera bandwidth\n" + "\t-a Get current camera AVB stream ID\n" + "\t-t Get current stream type\n" + "\t-y Get current camera interface\n" + "\t-v Get current VLAN ID\n" + "\t-z Get auto-start setting\n" + "\t-h This help message\n" + "Note: All changes resulting from Set commands are effective after reboot.\n"); +} + + +int main(int argc, char *argv[]) +{ + int option; + int rc = 0; + char ipaddr[32] = UDPCC2_DEFAULT_IPADDR; + unsigned int port = UDPCC2_DEFAULT_PORT; + unsigned long optval; + + setlinebuf(stdout); + + printf("NXP's Salsa camera control application\n"); + + + while ((option = getopt(argc, argv,"i:p:RSKB:A:I:M:T:Y:V:Z:sbamtyvzh")) != -1) { + + switch (option) { + case 'i': + h_strncpy(ipaddr, optarg, 32); + break; + + case 'p': + if (h_strtoul(&optval, optarg, NULL, 0) < 0) { + printf("port(%s) is not a valid integer\n", optarg); + goto exit; + } + port = (unsigned int)optval; + if (port > 65535) { + printf("Invalid port(%s)\n", optarg); + port = UDPCC2_DEFAULT_PORT; + } + break; + + case 'R': + rc = udpcc2_camera_reboot(ipaddr, port); + if (rc == 1) + printf("Camera %s rebooting\n", ipaddr); + else + printf("Configuration error\n"); + break; + + case 'S': + rc = udpcc2_camera_start(ipaddr, port); + if (rc == 1) + printf("Camera %s started\n", ipaddr); + else + printf("Configuration error\n"); + break; + + case 'K': + rc = udpcc2_camera_stop(ipaddr, port); + if (rc == 1) + printf("Camera %s stopped\n", ipaddr); + else + printf("Configuration error\n"); + break; + + case 'B': + { + int rate; + if (h_strtoul(&optval, optarg, NULL, 0) < 0) { + printf("bandwidth(%s) is not a valid integer\n", optarg); + goto exit; + } + rate = (int)optval; + if (rate > 100000) { + printf("Invalid bandwidth(%s)\n", optarg); + } else { + rc = udpcc2_camera_set_rate(ipaddr, port, rate); + if (rc == 1) + printf("Camera %s rate configured to %d\n", ipaddr, rate); + else + printf("Configuration error\n"); + } + break; + } + + case 'A': + { + unsigned long long stream_id; + if (h_strtoull(&stream_id, optarg, NULL, 0) < 0) { + printf("stream ID(%s) is not a valid integer\n", optarg); + goto exit; + } + rc = udpcc2_camera_set_avb_stream_id(ipaddr, port, stream_id); + if (rc == 1) + printf("Camera %s AVB stream ID configured to 0x%llx\n", ipaddr, stream_id); + else + printf("Configuration error\n"); + + break; + } + + case 'I': + { + unsigned int control_ipaddr; + unsigned char *control_ipaddr_p = (unsigned char *)&control_ipaddr; + rc = sscanf(optarg, "%hhu.%hhu.%hhu.%hhu", control_ipaddr_p, control_ipaddr_p + 1, control_ipaddr_p + 2, control_ipaddr_p + 3); + if (rc < 4) + printf("Invalid control IP address(%s)\n", optarg); + else { + rc = udpcc2_camera_set_control_ipaddr(ipaddr, port, control_ipaddr); + if (rc == 1) + printf("Camera %s IP address configured to %s\n", ipaddr, optarg); + else + printf("Configuration error\n"); + } + break; + } + + case 'M': + { + unsigned long long mac_addr; + unsigned char *mac_addr_p = (unsigned char *)&mac_addr; + rc = sscanf(optarg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", mac_addr_p + 2, mac_addr_p + 3, mac_addr_p + 4, mac_addr_p + 5, mac_addr_p + 6, mac_addr_p + 7); + if (rc < 6) + printf("Invalid MAC address(%s)\n", optarg); + else { + rc = udpcc2_camera_set_mac_addr(ipaddr, port, mac_addr); + if (rc == 1) + printf("Camera %s MAC address configured to %s\n", ipaddr, optarg); + else + printf("Configuration error\n"); + } + break; + } + + case 'T': + { + int type; + if (h_strtoul(&optval, optarg, NULL, 0) < 0) { + printf("stream type(%s) is not a valid integer\n", optarg); + goto exit; + } + type = (int)optval; + if ((type != 0) && (type != 1)) { + printf("Invalid stream type(%s)\n", optarg); + } else { + rc = udpcc2_camera_set_stream_type(ipaddr, port, type); + if (rc == 1) + printf("Camera %s stream type configured to %s\n", ipaddr, (type == 1) ? "AVB" : "UDP"); + else + printf("Configuration error\n"); + } + break; + } + + case 'Y': + { + int phy; + if (h_strtoul(&optval, optarg, NULL, 0) < 0) { + printf("PHY(%s) is not a valid integer\n", optarg); + goto exit; + } + phy = (int)optval; + if ((phy != 0) && (phy != 1)) { + printf("Invalid PHY(%s)\n", optarg); + } else { + rc = udpcc2_camera_set_phy(ipaddr, port, phy); + if (rc == 1) + printf("Camera %s PHY configured to %s\n", ipaddr, (phy == 1) ? "BroadRReach" : "100BaseT"); + else + printf("Configuration error\n"); + } + break; + } + + case 'V': + { + unsigned char enable; + unsigned short vlan_id; + rc = sscanf(optarg, "%hhu:%hu", &enable, &vlan_id); + if (rc < 2) + printf("Invalid VLAN configuration(%s)\n", optarg); + else { + rc = udpcc2_camera_set_avb_vlan(ipaddr, port, enable, vlan_id); + if (rc == 1) + printf("Camera %s VLAN configured to: enable: %d id: %u\n", ipaddr, enable, vlan_id); + else + printf("Configuration error\n"); + } + break; + } + + case 'Z': + { + int autostart; + if (h_strtoul(&optval, optarg, NULL, 0) < 0) { + printf("auto-start(%s) is not a valid integer\n", optarg); + goto exit; + } + autostart = (int)optval; + if ((autostart != 0) && (autostart != 1)) { + printf("Invalid auto-start value(%s)\n", optarg); + } else { + rc = udpcc2_camera_set_autostart(ipaddr, port, autostart); + if (rc == 1) + printf("Camera %s auto-start configured to %d\n", ipaddr, autostart); + else + printf("Configuration error\n"); + } + break; + } + + + case 's': + rc = udpcc2_camera_get_state(ipaddr, port); + if (rc >= 0) + printf("Camera %s state: %d\n", ipaddr, rc); + else + printf("Request error\n"); + break; + + case 'b': + rc = udpcc2_camera_get_rate(ipaddr, port); + if (rc >= 0) + printf("Camera %s rate: %d\n", ipaddr, rc); + else + printf("Request error\n"); + break; + + case 'a': + { + unsigned long long stream_id = udpcc2_camera_get_avb_stream_id(ipaddr, port); + if (stream_id) + printf("Camera %s AVB stream ID: 0x%llx\n", ipaddr, stream_id); + else + printf("Request error\n"); + break; + } + + case 'm': + { + unsigned long long mac_addr = udpcc2_camera_get_mac_addr(ipaddr, port); + unsigned char *mac_addr_p = (unsigned char *)&mac_addr; + if (mac_addr) + printf("Camera %s MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", ipaddr, + mac_addr_p[2], mac_addr_p[3], mac_addr_p[4], mac_addr_p[5], mac_addr_p[6], mac_addr_p[7]); + else + printf("Request error\n"); + break; + } + + case 't': + rc = udpcc2_camera_get_stream_type(ipaddr, port); + if (rc >= 0) + printf("Camera %s stream type: %s\n", ipaddr, (rc == 1) ? "AVB" : "UDP"); + else + printf("Request error\n"); + break; + + case 'y': + rc = udpcc2_camera_get_phy(ipaddr, port); + if (rc >= 0) + printf("Camera %s PHY: %s\n", ipaddr, (rc == 1) ? "BroadRReach" : "100BaseT"); + else + printf("Request error\n"); + break; + + case 'v': + { + unsigned short vlan_id; + rc = udpcc2_camera_get_avb_vlan(ipaddr, port, &vlan_id); + if (rc >= 0) + printf("Camera %s VLAN enabled: %d id: %d\n", ipaddr, rc, vlan_id); + else + printf("Request error\n"); + + break; + } + case 'z': + rc = udpcc2_camera_get_autostart(ipaddr, port); + if (rc >= 0) + printf("Camera %s auto-start: %d\n", ipaddr, rc); + else + printf("Request error\n"); + break; + + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + + +exit: + return rc; + +} diff --git a/apps/linux/salsacamctrl/salsacam-cmd.sh b/apps/linux/salsacamctrl/salsacam-cmd.sh new file mode 100755 index 0000000..c5e5da2 --- /dev/null +++ b/apps/linux/salsacamctrl/salsacam-cmd.sh @@ -0,0 +1,64 @@ +#! /bin/bash + +source /etc/genavb/salsacam-configs.inc + +usage () { + echo "Usage:" + echo " `basename $0` to send to camera N" + echo " `basename $0` to send to all cameras" + echo "" + echo "Allowed commands:" + echo " start : start streaming" + echo " stop : stop streaming" + echo " reboot : reboot camera" + echo " brr : configure camera for BroadR-Reach" + echo " baset : configure camera for 100BaseT" + exit 1 +} + +if [ $# -eq 0 ]; then + usage +fi + +if [ $# -gt 2 ]; then + usage +fi + + +case "$1" in +start) + CMD="-S" + ;; +stop) + CMD="-K" + ;; +reboot) + CMD="-R" + ;; +brr) + CMD="-Y 1" + ;; +baset) + CMD="-Y 0" + ;; +*) + usage + ;; +esac + +case "$#" in +1) + salsacamctrl -i ${IP[1]} $CMD -i ${IP[2]} $CMD -i ${IP[3]} $CMD -i ${IP[4]} $CMD + ;; +2) + CAM=$2 + + if [ "${IP[$CAM]}" = "" ]; then + echo "Unknown camera number $CAM" + exit 1 + fi + + salsacamctrl -i ${IP[$CAM]} $CMD + ;; +esac + diff --git a/apps/linux/salsacamctrl/salsacam-configs.inc b/apps/linux/salsacamctrl/salsacam-configs.inc new file mode 100644 index 0000000..8495fbb --- /dev/null +++ b/apps/linux/salsacamctrl/salsacam-configs.inc @@ -0,0 +1,20 @@ +RATE=20000 + +DEFAULT_IP="192.168.1.2" + +IP[1]="192.168.1.10" +IP[2]="192.168.1.20" +IP[3]="192.168.1.30" +IP[4]="192.168.1.40" + +MAC[1]="00:04:9F:00:4A:55" +MAC[2]="00:04:9F:00:4A:65" +MAC[3]="00:04:9F:00:4A:75" +MAC[4]="00:04:9F:00:4A:85" + +SID[1]="0x000000049f004a50" +SID[2]="0x000000049f004a60" +SID[3]="0x000000049f004a70" +SID[4]="0x000000049f004a80" + + diff --git a/apps/linux/salsacamctrl/salsacam-setup.sh b/apps/linux/salsacamctrl/salsacam-setup.sh new file mode 100755 index 0000000..91b2bb9 --- /dev/null +++ b/apps/linux/salsacamctrl/salsacam-setup.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +source /etc/genavb/salsacam-configs.inc + +CAMCTRL=salsacamctrl + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi +CAM=$1 + +if [ "${IP[$CAM]}" = "" ]; then + echo "Unknown camera number $CAM" + exit 1 +fi + + +echo "Configuring camera $CAM ..." +$CAMCTRL -i $DEFAULT_IP -I ${IP[$CAM]} -M ${MAC[$CAM]} -A ${SID[$CAM]} -B $RATE -Z 1 -T 1 + +echo +echo "Rebooting camera $CAM ..." +$CAMCTRL -i $DEFAULT_IP -R +echo "Waiting for camera to reboot..." +sleep 4 + +echo +echo "Checking camera $CAM configuration:" +$CAMCTRL -i ${IP[$CAM]} -m -a -b -z -t -y diff --git a/apps/linux/salsacamctrl/udpcc2.c b/apps/linux/salsacamctrl/udpcc2.c new file mode 100644 index 0000000..ba7e720 --- /dev/null +++ b/apps/linux/salsacamctrl/udpcc2.c @@ -0,0 +1,388 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "udpcc2.h" + +#define RESPONSE_TIMEOUT 1 + +static unsigned char sequence = 0; + +#define htonll(x) (((unsigned long long)htonl((unsigned int)x))<<32 | htonl((unsigned int)((unsigned long long)x>>32))) +#define ntohll(x) (((unsigned long long)ntohl((unsigned int)x))<<32 | ntohl((unsigned int)((unsigned long long)x>>32))) + + + +int udpcc2_send_msg(const char *ipaddr, unsigned int port, struct udpcc2_message *cmd, struct udpcc2_message *resp, unsigned int resplen) +{ + int sock, rc, i, msglen; + struct sockaddr_in camera_addr; + unsigned char csum; + fd_set readfs; + struct timeval timeout; + + sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + printf("%s: Could not create socket: errno %d(%s)\n", __func__, errno, strerror(errno)); + return -1; + } + + memset(&camera_addr, 0, sizeof(camera_addr)); + camera_addr.sin_family = AF_INET; + camera_addr.sin_port = htons(port); + rc = inet_pton(AF_INET, ipaddr, &camera_addr.sin_addr.s_addr); + if (rc <= 0) { + printf("%s: Invalid IP address (%s)\n", __func__, ipaddr); + goto err; + } + + csum = 0; + msglen = sizeof(struct udpcc2_message) + cmd->payload_length; + for (i = 0; i < msglen; i++) { + csum += ((unsigned char *)cmd)[i]; + } + csum = ~csum + 1; + + cmd->configuration_option = htons(cmd->configuration_option); + cmd->payload_length = htons(cmd->payload_length); + ((unsigned char *)cmd)[msglen] = csum; + msglen++; + + rc = sendto(sock, cmd, msglen, 0, (struct sockaddr *)&camera_addr, sizeof(camera_addr)); + if (rc <= 0) { + printf("%s: Coudln't send UDPCC2 message: errno %d(%s)\n", __func__, errno, strerror(errno)); + goto err; + } + + if (resp) { + FD_ZERO(&readfs); + FD_SET(sock, &readfs); + timeout.tv_sec = RESPONSE_TIMEOUT; + timeout.tv_usec = 0; + rc = select(sock + 1, &readfs, NULL, NULL, &timeout); + if (rc <= 0) { + printf("%s: Timeout waiting for UDPCC2 response: errno %d(%s)\n", __func__, errno, strerror(errno)); + goto err; + } + + rc = recvfrom(sock, resp, resplen, 0, NULL, NULL); + + if (rc <= 0) { + printf("%s: Couldn't receive UDPCC2 message: errno %d(%s)\n", __func__, errno, strerror(errno)); + goto err; + } + + if (resp->request_number != cmd->request_number) { + printf("%s: Request number mismatch (cmd = %d, resp = %d)\n", __func__, cmd->request_number, resp->request_number); + goto err; + } + + //TODO verify checksum? + } + + if (rc > 0) + rc = 1; + + close(sock); + return rc; + +err: + rc = -1; + close(sock); + return rc; +} + +#define MAX_BUFFER_SIZE (sizeof(struct udpcc2_message) + 32) +int udpcc2_send_set_msg(const char *ipaddr, unsigned int port, udpcc2_configuration_option_t config_option, udpcc2_change_type_t change_type, void *payload, unsigned short payload_length) +{ + unsigned int buffer_size = sizeof(struct udpcc2_message) + payload_length + 1; + unsigned char cmd_buffer[buffer_size]; + unsigned char resp_buffer[buffer_size]; + struct udpcc2_message *cmd = (struct udpcc2_message *)&cmd_buffer; + struct udpcc2_message *resp = (struct udpcc2_message *)&resp_buffer; + int rc = 0; + + cmd->type = UDPCC2_MSG_TYPE_SetMessage; + cmd->code = UDPCC2_CODE_UNSET; + cmd->configuration_option = config_option; + cmd->change_type = change_type; + cmd->request_number = sequence; + cmd->payload_length = payload_length; + if (payload) + memcpy(cmd->payload, payload, payload_length); + + rc = udpcc2_send_msg(ipaddr, port, cmd, resp, buffer_size); + sequence++; + + if (rc > 0) { + if (resp->code != UDPCC2_CODE_ACK) { + printf("%s: Received Error code %d\n", __func__, resp->code); + rc = -1; + } + else + rc = 1; + } + return rc; +} + +int udpcc2_send_get_msg(const char *ipaddr, unsigned int port, udpcc2_configuration_option_t config_option, void *payload, unsigned short payload_length) +{ + unsigned char cmd_buffer[sizeof(struct udpcc2_message) + 1]; + unsigned char resp_buffer[sizeof(struct udpcc2_message) + payload_length + 1]; + struct udpcc2_message *cmd = (struct udpcc2_message *)&cmd_buffer; + struct udpcc2_message *resp = (struct udpcc2_message *)&resp_buffer; + int rc = 0; + + cmd->type = UDPCC2_MSG_TYPE_GetMessage; + cmd->code = UDPCC2_CODE_UNSET; + cmd->configuration_option = config_option; + cmd->change_type = UDPCC2_CHANGE_IMMEDIATE; + cmd->request_number = sequence; + cmd->payload_length = 0; + + rc = udpcc2_send_msg(ipaddr, port, cmd, resp, sizeof(resp_buffer)); + sequence++; + + if (rc > 0) { + if (resp->code != UDPCC2_CODE_ACK) { + printf("%s: Received Error code %d\n", __func__, resp->code); + rc = -1; + } + else { + rc = 1; + memcpy(payload, resp->payload, payload_length); + } + } + return rc; +} + + +int udpcc2_camera_reboot(const char *ipaddr, unsigned int port) +{ + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_REBOOT, UDPCC2_CHANGE_IMMEDIATE, NULL, 0); +} + +int udpcc2_camera_start(const char *ipaddr, unsigned int port) +{ + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_CAMERA_START, UDPCC2_CHANGE_IMMEDIATE, NULL, 0); +} + +int udpcc2_camera_stop(const char *ipaddr, unsigned int port) +{ + unsigned char payload[4] = { 0, 0, 0, 1}; + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_CAMERA_STOP, UDPCC2_CHANGE_IMMEDIATE, payload, 4); + +} + +int udpcc2_camera_get_state(const char *ipaddr, unsigned int port) +{ + int state; + int rc = udpcc2_send_get_msg(ipaddr, port, UDPCC2_OPT_CAMERA_START, &state, 4); + + if (rc == 1) + rc = ntohl(state); + + return rc; +} + + +int udpcc2_camera_set_rate(const char *ipaddr, unsigned int port, unsigned int rate) +{ + unsigned int payload = htonl(rate); + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_DATA_RATE, UDPCC2_CHANGE_PERSISTENT, &payload, 4); +} + + +int udpcc2_camera_get_rate(const char *ipaddr, unsigned int port) +{ + int rate; + int rc = udpcc2_send_get_msg(ipaddr, port, UDPCC2_OPT_DATA_RATE, &rate, 4); + + if (rc == 1) + rc = ntohl(rate); + + return rc; +} + + +int udpcc2_camera_set_avb_stream_id(const char *ipaddr, unsigned int port, unsigned long long stream_id) +{ + unsigned long long payload = htonll(stream_id); + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_AVB_STREAM_ID, UDPCC2_CHANGE_PERSISTENT, &payload, 8); +} + +unsigned long long udpcc2_camera_get_avb_stream_id(const char *ipaddr, unsigned int port) +{ + unsigned long long stream_id; + int rc = udpcc2_send_get_msg(ipaddr, port, UDPCC2_OPT_AVB_STREAM_ID, &stream_id, 8); + + if (rc == 1) + return ntohll(stream_id); + else + return 0; +} + + +// Returns 1 if IP address belongs to a private network (RFC1918) and is a valid unicast address, 0 otherwise +static int is_ip_usable(const unsigned int ipaddr) +{ + unsigned char *a = (unsigned char *)&ipaddr; + unsigned char *b = a + 1; +// unsigned char *c = a + 2; + unsigned char *d = a + 3; + + // Broadcast address + if ((*d & 0xff) == 0xff) + return 0; + + // Network address + if ((*d & 0xff) == 0) + return 0; + + // 10.0.0.0/8 + if (*a == 0x0a) + return 1; + + // 172.16.0.0/12 + if ((*a == 0xac) && ((*b & 0xf0) == 0x10)) + return 1; + + // 192.168.0.0/16 + if ((*a == 0xc0) && (*b == 0xa8)) + return 1; + + return 0; +} + + +int udpcc2_camera_set_control_ipaddr(const char *ipaddr, unsigned int port, unsigned int control_ipaddr) +{ + if (is_ip_usable(control_ipaddr)) + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_IP_ADDR, UDPCC2_CHANGE_PERSISTENT, &control_ipaddr, 4); + else { + printf("Invalid IP address\n"); + return -1; + } +} + + +int udpcc2_camera_set_mac_addr(const char *ipaddr, unsigned int port, unsigned long long mac_addr) +{ + unsigned char *mac_addr_p = (unsigned char *)&mac_addr; + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_MAC_ADDR, UDPCC2_CHANGE_PERSISTENT, mac_addr_p + 2, 6); +} + +unsigned long long udpcc2_camera_get_mac_addr(const char *ipaddr, unsigned int port) +{ + unsigned long long mac_addr; + unsigned char *mac_addr_p = (unsigned char *)&mac_addr; + int rc = udpcc2_send_get_msg(ipaddr, port, UDPCC2_OPT_MAC_ADDR, mac_addr_p + 2, 6); + + if (rc == 1) + return mac_addr; + else + return 0; + +} + + +int udpcc2_camera_set_stream_type(const char *ipaddr, unsigned int port, udpcc2_stream_type_t avb) +{ + unsigned char payload = (avb != UDPCC2_STREAM_UDP) ? 1 : 2; + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_STREAM_TYPE, UDPCC2_CHANGE_PERSISTENT, &payload, 1); +} + +udpcc2_stream_type_t udpcc2_camera_get_stream_type(const char *ipaddr, unsigned int port) +{ + unsigned char type; + + int rc = udpcc2_send_get_msg(ipaddr, port, UDPCC2_OPT_STREAM_TYPE, &type, 1); + + if (rc == 1) + rc = (type == 1) ? UDPCC2_STREAM_AVB : UDPCC2_STREAM_UDP; + else + rc = UDPCC2_STREAM_ERROR; + + + return rc; +} + + +int udpcc2_camera_set_autostart(const char *ipaddr, unsigned int port, unsigned int autostart) +{ + unsigned char payload = (autostart != 0) ? 1 : 0; + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_AUTOSTART, UDPCC2_CHANGE_PERSISTENT, &payload, 1); +} + +int udpcc2_camera_get_autostart(const char *ipaddr, unsigned int port) +{ + unsigned char autostart; + + int rc = udpcc2_send_get_msg(ipaddr, port, UDPCC2_OPT_AUTOSTART, &autostart, 1); + + if (rc == 1) + rc = autostart; + + return rc; +} + + +int udpcc2_camera_set_avb_vlan(const char *ipaddr, unsigned int port, unsigned int enable, unsigned short vlan_id) +{ + unsigned int payload = 0; + unsigned char *payload_p = (unsigned char *)&payload; + + *(unsigned short *)(payload_p + 2) = htons(vlan_id); + if (enable != 0) + *(payload_p + 1) = 0x01; + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_AVB_VLAN, UDPCC2_CHANGE_PERSISTENT, payload_p + 1, 3); +} + + + +int udpcc2_camera_get_avb_vlan(const char *ipaddr, unsigned int port, unsigned short *vlan_id) +{ + int payload; + int rc = udpcc2_send_get_msg(ipaddr, port, UDPCC2_OPT_AVB_VLAN, &payload, 3); + + if (rc == 1) { + rc = payload & 0x1; + if (vlan_id) + *vlan_id = ntohs(payload >> 8); + } + + return rc; +} + + +int udpcc2_camera_set_phy(const char *ipaddr, unsigned int port, udpcc2_phy_type_t phy) +{ + unsigned char payload = (phy != 0) ? 1 : 0; + return udpcc2_send_set_msg(ipaddr, port, UDPCC2_OPT_PHY, UDPCC2_CHANGE_PERSISTENT, &payload, 1); +} + +udpcc2_phy_type_t udpcc2_camera_get_phy(const char *ipaddr, unsigned int port) +{ + unsigned char phy; + + int rc = udpcc2_send_get_msg(ipaddr, port, UDPCC2_OPT_PHY, &phy, 1); + + if (rc == 1) + rc = (phy == 1) ? UDPCC2_PHY_BRR : UDPCC2_PHY_100BASET; + else + rc = UDPCC2_PHY_ERROR; + + return rc; +} diff --git a/apps/linux/salsacamctrl/udpcc2.h b/apps/linux/salsacamctrl/udpcc2.h new file mode 100644 index 0000000..8b5a5b6 --- /dev/null +++ b/apps/linux/salsacamctrl/udpcc2.h @@ -0,0 +1,233 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +typedef enum { + UDPCC2_MSG_TYPE_FORBIDDEN = 0, + UDPCC2_MSG_TYPE_SetMessage = 1, + UDPCC2_MSG_TYPE_GetMessage = 2, + UDPCC2_MSG_TYPE_RespMessage = 3, + UDPCC2_MSG_TYPE_NotifyMessage = 4 +} udpcc2_message_type_t; + +typedef enum { + UDPCC2_CODE_UNSET = 0, + UDPCC2_CODE_ACK = 1, + UDPCC2_CODE_E_FAILED = 2, + UDPCC2_CODE_E_BUSY = 3, + UDPCC2_CODE_E_UNSUPPORTED = 4, + UDPCC2_CODE_E_WRONG_MSG = 5, + UDPCC2_CODE_E_WRONG_DATA = 6, + UDPCC2_CODE_E_NO_MEMORY = 7 +} udpcc2_code_t; + +typedef enum { + UDPCC2_CHANGE_FORBIDDEN = 0, + UDPCC2_CHANGE_IMMEDIATE = 1, + UDPCC2_CHANGE_PERSISTENT_IMMEDIATE = 2, + UDPCC2_CHANGE_PERSISTENT = 3 +} udpcc2_change_type_t; + +typedef enum { + UDPCC2_OPT_UNUSED = 0x0, + UDPCC2_OPT_REBOOT = 0x1, + UDPCC2_OPT_MAC_ADDR = 0x2, + UDPCC2_OPT_IP_ADDR = 0x3, + UDPCC2_OPT_NETMASK = 0x4, + UDPCC2_OPT_GATEWAY = 0x5, + UDPCC2_OPT_PORT = 0x6, + UDPCC2_OPT_UDP_STREAM_PORT = 0x7, + UDPCC2_OPT_UDP_STREAM_IP_ADDR = 0x8, + UDPCC2_OPT_AVB_STREAM_MAC_ADDR = 0x9, + UDPCC2_OPT_AVB_STREAM_ID = 0xa, + UDPCC2_OPT_STREAM_TYPE = 0x10, + UDPCC2_OPT_DATA_RATE = 0x12, + UDPCC2_OPT_CAMERA_STOP = 0x14, + UDPCC2_OPT_CAMERA_START = 0x15, + UDPCC2_OPT_AUTOSTART = 0x19, + UDPCC2_OPT_AVB_VLAN = 0x1a, + UDPCC2_OPT_PHY = 0x1b, +} udpcc2_configuration_option_t; + +typedef enum { + UDPCC2_STREAM_ERROR = -1, + UDPCC2_STREAM_UDP = 0, + UDPCC2_STREAM_AVB = 1, +} udpcc2_stream_type_t; + +typedef enum { + UDPCC2_PHY_ERROR = -1, + UDPCC2_PHY_100BASET = 0, + UDPCC2_PHY_BRR = 1, +} udpcc2_phy_type_t; + +/** UDPCC2 message description + */ +struct udpcc2_message { + unsigned char type; /**< Message type */ + unsigned char code; /**< Response code */ + unsigned short configuration_option; /**< Configuration option to be read/written or command to be executed */ + unsigned char change_type; /**< Change type */ + unsigned char request_number; /**< Sequence number to match commands and reponses */ + unsigned short payload_length; /**< Length of the following payload, in bytes */ + unsigned char payload[]; /**< Message payload */ +}; + +#define UDPCC2_DEFAULT_PORT 25002 +#define UDPCC2_DEFAULT_IPADDR "192.168.1.2" + +/** Send an UDPCC2 message to a Salsa camera + * Send an UDPCC2 message, and optionnally receive a response. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param cmd UDPCC2 message to send. + * \param resp If non-NULL, will contain the response message on return. + * \param resplen Length of the response buffer, including the checksum (which is computed by udpcc2_send_msg). + */ +int udpcc2_send_msg(const char *ipaddr, unsigned int port, struct udpcc2_message *cmd, struct udpcc2_message *resp, unsigned int resplen); + +int udpcc2_camera_reboot(const char *ipaddr, unsigned int port); +int udpcc2_camera_start(const char *ipaddr, unsigned int port); +int udpcc2_camera_stop(const char *ipaddr, unsigned int port); + + +int udpcc2_camera_get_state(const char *ipaddr, unsigned int port); + +/** Set camera data rate + * Configure the target bandwidth for the stream. Will become effective after the next camera reboot. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param rate Data rate to configure, in kbps and host byte order. + */ +int udpcc2_camera_set_rate(const char *ipaddr, unsigned int port, unsigned int rate); + +/** Get camera data rate + * Retrieve the current target bandwidth for the stream. + * \return Current data rate in kbps or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + */ +int udpcc2_camera_get_rate(const char *ipaddr, unsigned int port); + +/** Set camera AVB stream ID + * Configure the stream ID for the AVB stream. Will become effective after the next camera reboot. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param stream_id stream_id to configure (host byte order). + */ +int udpcc2_camera_set_avb_stream_id(const char *ipaddr, unsigned int port, unsigned long long stream_id); + +/** Get camera AVB stream ID + * Retrieve the current stream ID for the AVB stream. + * \return Current AVB stream ID or 0 on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + */ +unsigned long long udpcc2_camera_get_avb_stream_id(const char *ipaddr, unsigned int port); + +/** Set camera control IP address + * Configure the IPv4 address used to control the camera. Will become effective after the next camera reboot. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param control_ipaddr IPv4 address to configure (network byte order). + */ +int udpcc2_camera_set_control_ipaddr(const char *ipaddr, unsigned int port, unsigned int control_ipaddr); + + +/** Set camera MAC address + * Configure the MAC address of the camera. Will become effective after the next camera reboot. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param mac_addr MAC address to configure (network byte order). + */ +int udpcc2_camera_set_mac_addr(const char *ipaddr, unsigned int port, unsigned long long mac_addr); + +/** Get camera MAC address + * Retrieve the current MAC address of the camera. + * \return Current MAC address or 0 on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + */ +unsigned long long udpcc2_camera_get_mac_addr(const char *ipaddr, unsigned int port); + + +/** Set camera stream type (UDP or AVB) + * Configure the stream type of the camera. Will become effective after the next camera reboot. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param avb Set to 1 to enable AVB streaming, 0 for UDP. + */ +int udpcc2_camera_set_stream_type(const char *ipaddr, unsigned int port, udpcc2_stream_type_t avb); + +/** Get camera stream type + * Retrieve the current stream type of the camera. + * \return 1 if the camera is setup for AVB streaming, 0 for UDP, or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + */ +udpcc2_stream_type_t udpcc2_camera_get_stream_type(const char *ipaddr, unsigned int port); + + +/** Set camera auto-start + * Configure stream auto-start. Will become effective after the next camera reboot. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param autostart Set to 1 to enable stream auto-start. + */ +int udpcc2_camera_set_autostart(const char *ipaddr, unsigned int port, unsigned int autostart); + +/** Get camera auto-start + * Retrieve the current auto-start setting of the camera. + * \return 1 if the camera is setup for auto-start, 0 if not, or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + */ + int udpcc2_camera_get_autostart(const char *ipaddr, unsigned int port); + + + /** Configure camera AVB VLAN tagging + * Configure VLAN id to use with AVB streams and enable/disable VLAN tagging. Will become effective after the next camera reboot. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param enable Set to 1 to add a VLAN tag on AVB streams. + * \param vlan_id VLAN id to use on AVB streams (when VLAN tagging is enabled). + */ +int udpcc2_camera_set_avb_vlan(const char *ipaddr, unsigned int port, unsigned int enable, unsigned short vlan_id); + +/** Get camera AVB VLAN configuration + * Retrieve the current AVB VLAN setting of the camera. + * \return 1 if VLAN tagging is enabled, 0 if disabled, or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param vlan_id On successful return, will contain the vlan ID currently configured on the camera. + */ + int udpcc2_camera_get_avb_vlan(const char *ipaddr, unsigned int port, unsigned short *vlan_id); + + + /** Set camera preferred PHY + * Configure default PHY to use. Will become effective after the next camera reboot. + * \return 1 on success or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + * \param phy Set to 0 for 100BaseT Ethernet, 1 for BroadRReach. + */ +int udpcc2_camera_set_phy(const char *ipaddr, unsigned int port, udpcc2_phy_type_t phy); + +/** Get camera preferred PHY + * Retrieve the default PHY of the camera. + * \return 1 if the camera is setup for BroadRReach, 0 for 100BaseT, or negative value on error. + * \param ipaddr Pointer to a character string containing the IPv4 address of the camera in dotted-decimal format, "ddd.ddd.ddd.ddd". + * \param port UDP port of the control interface of the camera. + */ + udpcc2_phy_type_t udpcc2_camera_get_phy(const char *ipaddr, unsigned int port); diff --git a/apps/linux/simple-acf-app/CMakeLists.txt b/apps/linux/simple-acf-app/CMakeLists.txt new file mode 100644 index 0000000..0973136 --- /dev/null +++ b/apps/linux/simple-acf-app/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) + +project(simple-acf-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + ../common/common.c + ../common/stats.c + ../common/time.c + ../common/msrp.c + ../common/log.c +) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/simple-acf-app/main.c b/apps/linux/simple-acf-app/main.c new file mode 100644 index 0000000..a92623a --- /dev/null +++ b/apps/linux/simple-acf-app/main.c @@ -0,0 +1,1024 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../common/common.h" +#include "../common/log.h" +#include "../common/time.h" +#include "../common/msrp.h" + +/* Application main modes */ +#define MODE_LISTENER 0 /* acting as media files server if avdecc is not used*/ +#define MODE_TALKER 1 /* acting as media files server if avdecc is not used*/ + +/* GenAVB stack cofiguration */ +#define FLAG_IOV (1 << 3) /* use iov array for data and event */ + +#define PROCESS_PRIORITY 60 /* RT_FIFO priority to be used for the process */ + +/* default file name used for media */ +#define DEFAULT_MEDIA_FILE_NAME "media.raw" + +#define DEFAULT_LOG_FILE_NAME "/var/log/avb_media_app" + +#define MAX_DATA_BUF_SZ (4*K) +#define MAX_EVENT_BUF_SZ (512) + +#define MAX_FRAME_SIZE 256 +#define MAX_INTERVAL_FRAMES 1 +#define ACF_PAYLOAD_SZ 128 +#define ACF_TX_INTERVAL_US 1000000 + + +#define BATCH_SIZE 1 + +avb_u8 default_stream_id[8] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 }; +avb_u8 default_dst_mac[6] = { 0x91, 0xE0, 0xF0, 0x00, 0xeb, 0x15 }; + +static int signal_terminate = 0; + +struct can_stats { + unsigned long long num_rx_count; + unsigned long long num_rx_lost; + unsigned long long num_rx_error; + unsigned int rx_size; + unsigned char rx_bus_id; + unsigned long long num_tx_count; +}; + +struct avtp_stats { + unsigned long long num_rx_count; + unsigned long long num_rx_lost; + unsigned long long num_rx_ts_invalid; + unsigned long long num_tx_count; +}; + +struct app_stats { + struct avtp_stats avtp; + struct can_stats can; + struct stats latency_stats; + struct stats sync_stats; +}; + +/* application main context */ +struct acf_app { + unsigned int mode; + unsigned int config; + unsigned int avtp_subtype; + unsigned int sr_class; + unsigned int vlan_id; + int media_fd; + char *media_file_name; + char *log_file_name; + struct genavb_stream_params stream_params; + unsigned int stream_batch_size; + unsigned int stream_flags; + unsigned int max_frame_size; + unsigned int max_interval_frames; + unsigned long tx_interval; + unsigned int tx_burst; + unsigned int acf_payload_size; + unsigned long stats_interval_ms; + struct app_stats stats; + int timer_stats_fd; + int timer_process_fd; +}; + +struct acf_app app; + +static void listener_stats_print(void); + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\nOptions:\n" + "Talker or Listener:\n" + "\t-m application mode: (default) listener, talker\n" + "\t-f media file name (default media.raw)\n" + "\t-t avtp sub type: ntscf (default), tscf\n" + "\t-s stream reservation class: none , a (default), b\n" + "\t-L vlan id: 0 (default)\n" + "\t-p ACF payload size in bytes: 128 (default)\n" + "\t-i transmit interval in us: 1000000 (default)\n" + "\t-b number of packet to transmit per interval: 1 (default)\n" + "\t-S statistics interval in msesc: 1000 (default)\n"); +} + +static void set_avb_config(unsigned int *avb_flags) +{ + *avb_flags = 0; +} + +static void set_stream_params(struct genavb_stream_params *stream_params) +{ + if (app.mode == MODE_TALKER) { + stream_params->direction = AVTP_DIRECTION_TALKER; + stream_params->talker.vlan_id = htons(app.vlan_id); + stream_params->talker.priority = 0; + stream_params->clock_domain = GENAVB_MEDIA_CLOCK_DOMAIN_PTP; + stream_params->talker.latency = max(CFG_TALKER_LATENCY_NS, sr_class_interval_p(stream_params->stream_class) / sr_class_interval_q(stream_params->stream_class)); + stream_params->talker.max_frame_size = (avb_u16)app.max_frame_size; + stream_params->talker.max_interval_frames = (avb_u16)app.max_interval_frames; + } else { + stream_params->direction = AVTP_DIRECTION_LISTENER; + stream_params->clock_domain = GENAVB_MEDIA_CLOCK_DOMAIN_STREAM; + } + + stream_params->stream_class = (sr_class_t)app.sr_class; + + stream_params->subtype = app.avtp_subtype; + if (stream_params->subtype == AVTP_SUBTYPE_NTSCF) { + memset(&stream_params->format.u.raw, 0, sizeof(struct avdecc_format)); + } else { + stream_params->format.u.s.v = 0, + stream_params->format.u.s.subtype = AVTP_SUBTYPE_TSCF, + stream_params->format.u.s.subtype_u.tscf.m = 0; + stream_params->format.u.s.subtype_u.tscf.t3v = 0; + stream_params->format.u.s.subtype_u.tscf.type_3 = 0; + stream_params->format.u.s.subtype_u.tscf.t2v = 0; + stream_params->format.u.s.subtype_u.tscf.type_2 = 0; + stream_params->format.u.s.subtype_u.tscf.t1v = 0; + stream_params->format.u.s.subtype_u.tscf.type_1 = 0; + stream_params->format.u.s.subtype_u.tscf.t0v = 1; + stream_params->format.u.s.subtype_u.tscf.type_0 = 0x78; + } + + stream_params->flags = GENAVB_STREAM_FLAGS_CUSTOM_TSPEC; + + stream_params->port = 0; + memcpy(stream_params->stream_id, default_stream_id, 8); + print_stream_id(stream_params->stream_id); + memcpy(stream_params->dst_mac, default_dst_mac, 6); +} + +static int apply_config(void) +{ + avb_u64 stream_id; + + app.stream_batch_size = BATCH_SIZE; + + app.stream_flags = AVTP_NONBLOCK |AVTP_DGRAM; + + if (app.media_file_name == NULL) + app.media_file_name = DEFAULT_MEDIA_FILE_NAME; + + if (app.mode == MODE_LISTENER) + app.media_fd = open(app.media_file_name, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + else + app.media_fd = open(app.media_file_name, O_RDONLY); + + if (app.media_fd < 0) { + printf("open(%s) failed: %s\n", app.media_file_name, strerror(errno)); + goto error_media_open; + } + + app.max_frame_size = (sizeof(struct avtp_data_hdr) + sizeof(struct acf_msg) + sizeof(struct acf_can_hdr) + app.acf_payload_size); + + /* + * display the whole configuration + */ + + printf("mode: "); + if (app.mode == MODE_LISTENER) + printf("LISTENER\n"); + else + printf("TALKER\n"); + + printf("media file name: %s (fd %d)\n", app.media_file_name, app.media_fd); + printf("log_file_name: %s\n", app.log_file_name); + + printf("flags: "); + if (app.config & FLAG_IOV) + printf("IOV\n"); + else + printf("none\n"); + + printf("subtype: "); + if (app.avtp_subtype == AVTP_SUBTYPE_TSCF) + printf("TSCF\n"); + else + printf("NTSCF\n"); + + printf("SR class: "); + if (app.sr_class == SR_CLASS_A) + printf("CLASS A\n"); + else if (app.sr_class == SR_CLASS_B) + printf("CLASS B\n"); + else + printf("NONE\n"); + + printf("vlan ID: %u\n", app.vlan_id); + + memcpy(&stream_id, default_stream_id, 8); + printf("Stream ID: %016"PRIx64"\n", stream_id); + + if (app.mode == MODE_TALKER) { + printf("acf payload size: %u\n", app.acf_payload_size); + printf("tx interval: %lu\n", app.tx_interval); + printf("tx burst: %u\n", app.tx_burst); + } + + printf("\n\n"); + + return 0; + +error_media_open: + return -1; +} + +static void acf_stats_print(struct stats *s) +{ + INF("%s min %d mean %d max %d rms^2 %llu stddev^2 %llu", (char*)s->priv, s->min, s->mean, s->max, s->ms, s->variance); +} + +static void listener_stats_print(void) +{ + stats_compute(&app.stats.latency_stats); + acf_stats_print(&app.stats.latency_stats); + stats_reset(&app.stats.latency_stats); + + if (app.avtp_subtype == AVTP_SUBTYPE_TSCF) { + stats_compute(&app.stats.sync_stats); + acf_stats_print(&app.stats.sync_stats); + stats_reset(&app.stats.sync_stats); + } + + INF("CAN: size %u, bus_id 0x%x, rx %llu, lost %llu", app.stats.can.rx_size, app.stats.can.rx_bus_id, app.stats.can.num_rx_count, app.stats.can.num_rx_lost); + + INF("AVTP: rx count %llu, rx lost %llu, ts invalid %llu\n", app.stats.avtp.num_rx_count, app.stats.avtp.num_rx_lost, app.stats.avtp.num_rx_ts_invalid); +} + +#define LISTENER_STAT_FD_IDX 0 +#define LISTENER_STREAM_FD_IDX 1 +static int listener_nonblocking(struct genavb_stream_handle *stream_h, int stream_fd, unsigned int batch_size, int file_dst, int stats_fd) +{ + unsigned char acf_msg_buf[MAX_DATA_BUF_SZ]; + struct genavb_event event[MAX_EVENT_BUF_SZ] = {0}; + unsigned int event_len = MAX_EVENT_BUF_SZ; + struct pollfd poll_fds[3]; + uint64_t now, message_origin_timestamp; + int ready, i, n, nfds, nbytes; + int rc = 0; + + struct acf_msg *acf_hdr; + unsigned int acf_payload_length, acf_payload_offset; + + struct acf_can_hdr *can_hdr; + unsigned char can_previous_seq = 0; + unsigned long long can_lost_packets = 0; + + INF("Starting listener loop, non-blocking mode"); + + /* + * listen to read event from the stack + */ + + nfds = 0; + + poll_fds[LISTENER_STAT_FD_IDX].fd = stats_fd; + poll_fds[LISTENER_STAT_FD_IDX].events = POLLIN; + nfds++; + + poll_fds[LISTENER_STREAM_FD_IDX].fd = stream_fd; + poll_fds[LISTENER_STREAM_FD_IDX].events = POLLIN; + nfds++; + + poll_fds[nfds].fd = -1; + poll_fds[nfds].events = 0; + + stats_init(&app.stats.latency_stats, 31, "Latency (ns)", NULL); + stats_init(&app.stats.sync_stats, 31, "Synchro (ns)", NULL); + + while (1) { + if (signal_terminate) { + INF("processing terminate signal"); + rc = -1; + goto exit; + } + + if ((ready = poll(poll_fds, nfds, -1)) == -1) { + if (errno == EINTR) + continue; + else { + ERR("poll(%d) failed while processing listener errno %d: %s", stream_fd, errno, strerror(errno)); + rc = -1; + goto exit; + } + } + + if (ready > 0) { + for (n = 0, i = 0; i < nfds && n < ready; i++) { + if (poll_fds[LISTENER_STREAM_FD_IDX].revents && POLLIN) { + n++; + + if (gettime_ns(&now) < 0) { + rc = -1; + goto exit; + } + + /* + * read data from stack... + */ + event_len = MAX_EVENT_BUF_SZ; + nbytes = genavb_stream_receive(stream_h, acf_msg_buf, MAX_DATA_BUF_SZ, event, &event_len); + if (nbytes <= 0) { + if (nbytes < 0) + ERR("genavb_stream_receive() failed: %s", genavb_strerror(nbytes)); + else + ERR("genavb_stream_receive() incomplete"); + + rc = nbytes; + goto exit; + } + + if (event_len != 0) { + if (event[0].event_mask & AVTP_MEDIA_CLOCK_RESTART) + INF("AVTP media clock restarted"); + + if (event[0].event_mask & AVTP_PACKET_LOST) { + INF("AVTP packet lost"); + app.stats.avtp.num_rx_lost++; + } + + if (event[0].event_mask & AVTP_END_OF_FRAME) + INF("AVTP end of frame (size %d)", event[0].index); + + if (event[0].event_mask & (AVTP_TIMESTAMP_INVALID | AVTP_TIMESTAMP_UNCERTAIN)) { + INF("AVTP timestamp not valid"); + app.stats.avtp.num_rx_ts_invalid++; + } else { + /* log synchronization accuracy between now and presentation time */ + stats_update(&app.stats.sync_stats, (unsigned int)now - event[0].ts); + } + } + + acf_hdr = (struct acf_msg *)acf_msg_buf; + acf_payload_offset = sizeof(struct acf_msg); + acf_payload_length = (ACF_MSG_LENGTH(acf_hdr) << 2) - sizeof(struct acf_msg); + DBG("msg_type: 0x%x msg_length: %u nbytes: %u", acf_hdr->acf_msg_type, (ACF_MSG_LENGTH(acf_hdr) << 2), nbytes); + + switch (acf_hdr->acf_msg_type) { + case ACF_MSG_TYPE_CAN : + can_hdr = (struct acf_can_hdr*)(&acf_msg_buf[acf_payload_offset]); + acf_payload_offset += sizeof(struct acf_can_hdr); + acf_payload_length -= sizeof(struct acf_can_hdr); + message_origin_timestamp = ntohll(can_hdr->message_timestamp); + + /* compute average end to end latency */ + stats_update(&app.stats.latency_stats, (unsigned long)(now - message_origin_timestamp)); + + /* compute cumulative packet loss */ + if (app.stats.can.num_rx_count) { + if (acf_msg_buf[acf_payload_offset] >= (unsigned char)(can_previous_seq + 1)) + can_lost_packets = acf_msg_buf[acf_payload_offset] -(unsigned char)(can_previous_seq + 1); + else + can_lost_packets = (unsigned char)(255 -can_previous_seq) + acf_msg_buf[acf_payload_offset]; + } + can_previous_seq = acf_msg_buf[acf_payload_offset]; + + app.stats.can.num_rx_count++; + app.stats.can.num_rx_lost += can_lost_packets; + app.stats.can.rx_bus_id = can_hdr->can_bus_id; + app.stats.can.rx_size = nbytes; + + DBG("CAN: size %u bus_id 0x%x, rx %llu, lost %llu", app.stats.can.rx_size, app.stats.can.rx_bus_id, app.stats.can.num_rx_count, app.stats.can.rx_lost); + break; + default: + ERR("Unsupported message type received: 0x%x", acf_hdr->acf_msg_type); + app.stats.can.num_rx_error++; + goto exit; + } + + app.stats.avtp.num_rx_count++; + + /* + * ...and write to local file + */ + rc = write(file_dst, &acf_msg_buf[acf_payload_offset], acf_payload_length); + if (rc < acf_payload_length) { + if (rc < 0) + ERR("write() failed: %s", strerror(errno)); + else + ERR("write() incomplete"); + + goto exit; + } + + DBG("rx count %llu, rx lost: %llu; ts invalid: %llu\n", app.stats.avtp.num_rx_count, app.stats.avtp.num_rx_lost, app.avtp.stats.num_rx_ts_invalid); + } + + if (poll_fds[LISTENER_STAT_FD_IDX].revents && POLLIN) { + char tmp[8]; + n++; + if ((nbytes = read(poll_fds[i].fd, tmp, 8)) < 8) { + if (nbytes < 0) { + printf("stats_fd read() failed: %s\n", strerror(errno)); + goto exit; + } + } else + listener_stats_print(); + } + } + } + } +exit: + return rc; +} + + +static void talker_stats_print(void) +{ + INF("%s: tx count %llu", (app.avtp_subtype == AVTP_SUBTYPE_TSCF)?"TSCF":"NTSCF", app.stats.avtp.num_tx_count); +} + +#define TALKER_PROCESS_FD_IDX 0 +#define TALKER_STAT_FD_IDX 1 +#define TALKER_STREAM_FD_IDX 2 +static int talker_nonblocking(struct genavb_stream_handle *stream_h, int stream_fd, unsigned int batch_size, int file_src, int process_fd, int stats_fd) +{ + unsigned char acf_msg_buf[MAX_DATA_BUF_SZ]; + struct genavb_event event[MAX_EVENT_BUF_SZ]; + struct pollfd poll_fds[4]; + int nbytes, ready, i, n, nfds; + uint64_t start_time; + unsigned int event_n; + struct acf_msg *acf_hdr; + struct acf_can_hdr *can_hdr; + unsigned int nburst = 0; + int rc = 0; + + INF("Starting talker loop, non-blocking mode (fds: %d - %d - %d)", stream_fd, process_fd, stats_fd); + + lseek(file_src, 0, SEEK_SET); + + nfds = 0; + + /* + * listen to timer event for talker transmit + */ + poll_fds[TALKER_PROCESS_FD_IDX].fd = process_fd; + poll_fds[TALKER_PROCESS_FD_IDX].events = POLLIN; + poll_fds[TALKER_PROCESS_FD_IDX].revents = 0; + nfds++; + + /* + * listen to timer event for stats output + */ + poll_fds[TALKER_STAT_FD_IDX].fd = stats_fd; + poll_fds[TALKER_STAT_FD_IDX].events = POLLIN; + poll_fds[TALKER_STAT_FD_IDX].revents = 0; + nfds++; + + /* + * listen to write event from the stack + */ + poll_fds[TALKER_STREAM_FD_IDX].fd = stream_fd; + poll_fds[TALKER_STREAM_FD_IDX].events = POLLOUT; + poll_fds[TALKER_STREAM_FD_IDX].revents = 0; + nfds++; + + poll_fds[nfds].fd = -1; + poll_fds[nfds].events = 0; + poll_fds[nfds].revents = 0; + + app.stats.avtp.num_tx_count = 0; + + while (1) { + if (signal_terminate) { + INF("processing terminate signal"); + rc = -1; + goto exit; + } + + if ((ready = poll(poll_fds, nfds, -1)) == -1) { + if (errno == EINTR) + continue; + else { + INF("poll(%d) failed while processing talker errno %d: %s", stream_fd, errno, strerror(errno)); + rc = -1; + goto exit; + } + } + + if (ready > 0) { + for (n = 0, i = 0; i < nfds && n < ready; i++) { + if (poll_fds[TALKER_STAT_FD_IDX].revents & POLLIN) { + n++; + char tmp[8]; + if ((nbytes = read(poll_fds[TALKER_STAT_FD_IDX].fd, tmp, 8)) < 8) { + if (nbytes < 0) { + printf("stats timer_fd read() failed: %s\n", strerror(errno)); + goto exit; + } + } else + talker_stats_print(); + } + + if (poll_fds[TALKER_PROCESS_FD_IDX].revents & POLLIN) { + n++; + char tmp[8]; + if ((nbytes = read(poll_fds[TALKER_PROCESS_FD_IDX].fd, tmp, 8)) < 8) { + if (nbytes < 0) { + printf("process timer_fd read() failed: %s\n", strerror(errno)); + goto exit; + } + } else + /*add stream_fd */ + poll_fds[TALKER_STREAM_FD_IDX].fd = stream_fd; + } + + if (poll_fds[TALKER_STREAM_FD_IDX].revents & POLLOUT) { + n++; +read_again: + /* reading dummy data from file (emulates data from a CAN bus) */ + nbytes = read(app.media_fd, &acf_msg_buf[sizeof(struct acf_msg) + sizeof(struct acf_can_hdr)], app.acf_payload_size); + if (nbytes < app.acf_payload_size ) { + if (nbytes < 0) { + rc = nbytes; + goto exit; + } else { + lseek(app.media_fd, 0, SEEK_SET); + goto read_again; + } + } + + acf_hdr = (struct acf_msg *)acf_msg_buf; + acf_hdr->acf_msg_type = ACF_MSG_TYPE_CAN; + ACF_MSG_LENGTH_SET(acf_hdr, (sizeof(struct acf_msg) + sizeof(struct acf_can_hdr) + app.acf_payload_size ) >> 2); /* number of quadlets */ + + can_hdr = (struct acf_can_hdr *)(&acf_msg_buf[sizeof(struct acf_msg)]); + memset(can_hdr, 0, sizeof(struct acf_can_hdr)); + can_hdr->mtv = 1; + + if (gettime_ns(&start_time) < 0) { + rc = -1; + goto exit; + } + + can_hdr->message_timestamp = htonll(start_time); + can_hdr->can_bus_id = 0x17; /* arbitrary value */ + + /* for debug purpose (sequence number) */ + acf_msg_buf[sizeof(struct acf_msg) + sizeof(struct acf_can_hdr)] = (unsigned char)++app.stats.avtp.num_tx_count; + + /* add presentation timestamp depending on subtype */ + if (app.avtp_subtype == AVTP_SUBTYPE_TSCF) { + event[0].index = (ACF_MSG_LENGTH(acf_hdr) << 2) - 1; + event[0].event_mask = AVTP_SYNC; + event[0].ts = start_time + genavb_stream_presentation_offset(stream_h); + event_n = 1; + rc = genavb_stream_send(stream_h, acf_msg_buf, (ACF_MSG_LENGTH(acf_hdr) << 2), event, event_n); + } else { + rc = genavb_stream_send(stream_h, acf_msg_buf, (ACF_MSG_LENGTH(acf_hdr) << 2), NULL, 0); + } + + if (rc != (ACF_MSG_LENGTH(acf_hdr) << 2)) { + if (rc < 0) + ERR("genavb_stream_send() failed, rc = %s", genavb_strerror(rc)); + else + ERR("genavb_stream_send() incomplete"); + + goto exit; + } + + if (app.avtp_subtype == AVTP_SUBTYPE_TSCF) + DBG("TSCF packet sent: %d bytes, num_tx_count %llu, ts %x\n", (ACF_MSG_LENGTH(acf_hdr) << 2), app.stats.avtp.num_tx_count, event[0].ts); + else + DBG("NTSCF packet sent: %d bytes, num_tx_count %llu\n", (ACF_MSG_LENGTH(acf_hdr) << 2), app.stats.avtp.num_tx_count); + + nburst++; + + if (nburst >= app.tx_burst) { + /* remove stream_fd until next transmit interval */ + if (app.tx_interval) + poll_fds[TALKER_STREAM_FD_IDX].fd = -1; + + nburst = 0; + } else + goto read_again; + } + } + } + } + +exit: + return rc; +} + + +static int run_listener(struct genavb_stream_handle *stream_h, int stream_fd, unsigned int batch_size) +{ + int rc; + + rc = listener_nonblocking(stream_h, stream_fd, batch_size, app.media_fd, app.timer_stats_fd); + + return rc; +} + + +static int run_talker(struct genavb_stream_handle *stream_h, int stream_fd, unsigned int batch_size) +{ + int rc; + + rc = talker_nonblocking(stream_h, stream_fd, batch_size, app.media_fd, app.timer_process_fd, app.timer_stats_fd); + + return rc; +} + + +void signal_terminate_handler (int signal_num) +{ + signal_terminate = 1; +} + + +static void set_signal_handlers(void) +{ + struct sigaction action; + + action.sa_handler = signal_terminate_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGTERM, &action, NULL) < 0) /* Termination signal */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGQUIT, &action, NULL) < 0) /* Quit from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGINT, &action, NULL) < 0) /* Interrupt from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); +} + +static int set_timers_fd(void) +{ + unsigned long timer_msecs, timer_usecs; + struct itimerspec its; + + app.timer_process_fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (app.timer_process_fd < 0) { + printf("%s timerfd_create() failed %s\n", __func__, strerror(errno)); + goto err_process_timer_create; + } + + #define NSECS_PER_USEC 1000 + timer_usecs = app.tx_interval; + its.it_value.tv_sec = timer_usecs / USECS_PER_SEC; + its.it_value.tv_nsec = (timer_usecs % USECS_PER_SEC) * NSECS_PER_USEC; + its.it_interval.tv_sec = its.it_value.tv_sec; + its.it_interval.tv_nsec = its.it_value.tv_nsec; + if (timerfd_settime(app.timer_process_fd, 0, &its, NULL) < 0) { + printf("%s timerfd_settime() failed %s\n", __func__, strerror(errno)); + goto err_process_timer_set; + } + + app.timer_stats_fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (app.timer_stats_fd < 0) { + printf("%s timerfd_create() failed %s\n", __func__, strerror(errno)); + goto err_stats_timer_create; + } + + timer_msecs = app.stats_interval_ms; + its.it_value.tv_sec = timer_msecs / MSECS_PER_SEC; + its.it_value.tv_nsec = (timer_msecs % MSECS_PER_SEC) * NSECS_PER_MSEC; + its.it_interval.tv_sec = its.it_value.tv_sec; + its.it_interval.tv_nsec = its.it_value.tv_nsec; + if (timerfd_settime(app.timer_stats_fd, 0, &its, NULL) < 0) { + printf("%s timerfd_settime() failed %s\n", __func__, strerror(errno)); + goto err_stats_timer_set; + } + + return 0; + +err_stats_timer_set: + close(app.timer_stats_fd); + +err_stats_timer_create: +err_process_timer_set: + close(app.timer_process_fd); + +err_process_timer_create: + return -1; + +} + +int main(int argc, char *argv[]) +{ + struct genavb_handle *avb_h; + struct genavb_stream_handle* stream_h = NULL; + struct sched_param param = { + .sched_priority = PROCESS_PRIORITY, + }; + unsigned long optval; + unsigned int avb_flags; + int stream_fd, option; + int rc = 0; + + /* + * Increase process priority to match the AVTP thread priority + */ + + if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) { + printf("sched_setscheduler(), %s\n", strerror(errno)); + rc = -1; + goto exit; + } + + setlinebuf(stdout); + + printf("NXP's GenAVB reference ACF application\n"); + + /* + * retrieve user's configuration parameters + */ + + /* default settings */ + app.mode = MODE_LISTENER; app.config = 0; /*LISTENER, NON-BLOCKING, SINGLE BUFFER */ + app.avtp_subtype = AVTP_SUBTYPE_NTSCF ; /* NTSCF */ + app.vlan_id = 0; app.sr_class = SR_CLASS_A ; /* no stream reservation */ + app.tx_interval = ACF_TX_INTERVAL_US; app.tx_burst = 1; /* 1 packet every ACF_TX_INTERVAL_US interval */ + app.acf_payload_size = ACF_PAYLOAD_SZ; + app.max_frame_size = (sizeof(struct avtp_data_hdr) + sizeof(struct acf_msg) + sizeof(struct acf_can_hdr) + app.acf_payload_size); + app.max_interval_frames = MAX_INTERVAL_FRAMES; + app.stats_interval_ms = MSECS_PER_SEC; + app.log_file_name = DEFAULT_LOG_FILE_NAME; + + while ((option = getopt(argc, argv,"m:f:L:t:s:v:p:i:b:S:I:h")) != -1) { + switch (option) { + case 'm': + if (!strcasecmp(optarg, "listener")) + app.mode = MODE_LISTENER; + else if (!strcasecmp(optarg, "talker")) + app.mode = MODE_TALKER; + else { + usage(); + goto exit; + } + break; + + case 'f': + app.media_file_name = optarg; + break; + + case 'L': + app.log_file_name = optarg; + break; + + case 't': + if (!strcasecmp(optarg, "tscf")) + app.avtp_subtype = AVTP_SUBTYPE_TSCF; + else if (!strcasecmp(optarg, "ntscf")) + app.avtp_subtype = AVTP_SUBTYPE_NTSCF; + else { + usage(); + goto exit; + } + break; + + case 's': + if (!strcasecmp(optarg, "a")) + app.sr_class = SR_CLASS_A; + else if (!strcasecmp(optarg, "b")) + app.sr_class = SR_CLASS_B; + else if (!strcasecmp(optarg, "none")) + app.sr_class = SR_CLASS_NONE; + else { + usage(); + goto exit; + } + break; + + case 'v': + if(h_strtoul(&optval, optarg, NULL, 0) < 0) { + printf("vlan_id not a valid unsigned integer\n"); + rc = -1; + goto exit; + } + app.vlan_id = (unsigned int)optval; + break; + + case 'p': + if ((h_strtoul(&optval, optarg, NULL, 0) < 0) || (!optval)) { + printf("acf_payload_size not a valid unsigned integer\n"); + rc = -1; + goto exit; + } + app.acf_payload_size = (unsigned int)optval; + break; + + case 'i': + if (h_strtoul(&app.tx_interval, optarg, NULL, 0) < 0) { + printf("tx_interval not a valid unsigned integer\n"); + rc = -1; + goto exit; + } + break; + + case 'b': + if (h_strtoul(&optval, optarg, NULL, 0) < 0) { + printf("tx_burst not a valid unsigned integer\n"); + rc = -1; + goto exit; + } + app.tx_burst = (unsigned int)optval; + break; + + case 'S': + if (h_strtoul(&app.stats_interval_ms, optarg, NULL, 0) < 0) { + printf("stats_interval_ms not a valid unsigned integer\n"); + rc = -1; + goto exit; + } + break; + + case 'I': + + if (h_strtoul(&optval, optarg, NULL, 16) < 0) { + printf("stream ID modifier not a valid unsigned integer\n"); + rc = -1; + goto exit; + } + default_stream_id[0] = (avb_u8)optval; + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + + /* + * set signals handler + */ + set_signal_handlers(); + + /* + * timers file descriptors for stats and talker processing interval + */ + if (set_timers_fd() < 0) + goto err_timerfd; + + rc = aar_log_init(app.log_file_name); + if (rc < 0) + goto err_log_init; + + /* + * setup the avb stack + */ + + set_avb_config(&avb_flags); + + rc = genavb_init(&avb_h, avb_flags); + if (rc != GENAVB_SUCCESS) { + ERR("genavb_init() failed, rc = %s", genavb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + rc = msrp_init(avb_h); + if (rc < 0) { + ERR("msrp_init() failed"); + goto err_msrp_init; + } + +wait_new_stream: + printf("\nwait for new stream...\n"); + + if (apply_config() < 0) { + rc = -1; + goto error_apply_config; + } + + set_stream_params(&app.stream_params); + + /* + * setup the stream + */ + + rc = genavb_stream_create(avb_h, &stream_h, &app.stream_params, &app.stream_batch_size, app.stream_flags); + if (rc != GENAVB_SUCCESS) { + ERR("genavb_stream_create() failed, rc = %s", genavb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + + /* + * setup the stream reservation + */ + if (app.sr_class != SR_CLASS_NONE) { + if (app.stream_params.direction & AVTP_DIRECTION_TALKER) { + rc = msrp_talker_register(&app.stream_params); + if (rc != GENAVB_SUCCESS) { + ERR("msrp_talker_register error, rc = %s", genavb_strerror(rc)); + goto err_msrp_register; + } + } else { + rc = msrp_listener_register(&app.stream_params); + if (rc != GENAVB_SUCCESS) { + ERR("msrp_listener_register error, rc = %s", genavb_strerror(rc)); + goto err_msrp_register; + } + } + } + + /* additional delay for SRP protocol establisment */ + sleep(4); + + /* + * retrieve the file descriptor associated to the stream + */ + + stream_fd = genavb_stream_fd(stream_h); + if (stream_fd < 0) { + ERR("genavb_stream_fd() failed, rc = %s\n", genavb_strerror(stream_fd)); + rc = -1; + goto error_stream_fd; + } + + /* + * run listener/talker main processing function + */ + + if (app.stream_params.direction & AVTP_DIRECTION_TALKER) + rc = run_talker(stream_h, stream_fd, app.stream_batch_size); + else + rc = run_listener(stream_h, stream_fd, app.stream_batch_size); + + /* + * main processing loop exited. + */ + if (rc < 0) { + ERR("Loop function exited with error code %d\n", rc); + } else { + printf("Loop function exited\n"); + genavb_stream_destroy(stream_h); + goto wait_new_stream; + } + + /* + * destroy the stream, disconnect for avb stack and close media files + */ + +error_stream_fd: + if (app.sr_class != SR_CLASS_NONE) { + if (app.stream_params.direction & AVTP_DIRECTION_TALKER) + msrp_talker_deregister(&app.stream_params); + else + msrp_listener_deregister(&app.stream_params); + } + +err_msrp_register: + genavb_stream_destroy(stream_h); + +error_stream_create: + close(app.media_fd); + +error_apply_config: + msrp_exit(); + +err_msrp_init: + genavb_exit(avb_h); + +error_avb_init: + aar_log_exit(); + +err_log_init: +err_timerfd: + if (app.timer_stats_fd > 0) + close(app.timer_stats_fd); + if (app.timer_process_fd > 0) + close(app.timer_process_fd); + +exit: + return rc; + +} diff --git a/apps/linux/simple-audio-app/CMakeLists.txt b/apps/linux/simple-audio-app/CMakeLists.txt new file mode 100644 index 0000000..a41cc4e --- /dev/null +++ b/apps/linux/simple-audio-app/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.10) + +project(simple-audio-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + ../common/ts_parser.c + ../common/file_buffer.c + ../common/common.c + ../common/stats.c + ../common/time.c + ../common/msrp.c + ../common/log.c +) + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/simple-audio-app/main.c b/apps/linux/simple-audio-app/main.c new file mode 100644 index 0000000..6eb9236 --- /dev/null +++ b/apps/linux/simple-audio-app/main.c @@ -0,0 +1,947 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../common/ts_parser.h" +#include "../common/file_buffer.h" +#include "../common/common.h" +#include "../common/msrp.h" + +#define CFG_CAPTURE_LATENCY_NS 1500000 // Additional fixed playback latency in ns + +/* Application main modes */ +#define MODE_AVDECC 0 /* the application relies on avdecc indication from avb stack*/ +#define MODE_LISTENER 1 /* acting as media files server if avdecc is not used*/ +#define MODE_TALKER 2 /* acting as media files server if avdecc is not used*/ + +/* GenAVB stack cofiguration */ +#define FLAG_BLOCKING (1 << 2) /* use blocking call to the genavb library */ +#define FLAG_IOV (1 << 3) /* use iov array for data and event */ + +#define PROCESS_PRIORITY 60 /* RT_FIFO priority to be used for the process */ + +/* default file name used for media */ +#define DEFAULT_MEDIA_FILE_NAME "media.raw" + + +#define K 1024 +#define DATA_BUF_SZ (16*K) +#define EVENT_BUF_SZ (K) + + +#define BATCH_SIZE 4096 + +#define EVENT_MAX (BATCH_SIZE / PES_SIZE) + +static int signal_terminate = 0; +static int signal_pause = 0; + +/* application main context */ +struct avb_app { + unsigned int mode; + unsigned int config; + int media_fd; + char *media_file_name; + struct avb_stream_params stream_params; + unsigned int stream_batch_size; + unsigned int stream_flags; + struct avb_control_handle *ctrl_h; + int connected_stream_index; +}; + +struct avb_app app; + +static unsigned int ts_parser_enabled = 0; + +struct avb_stream_params default_stream_params = { + .subtype = AVTP_SUBTYPE_61883_IIDC, + .stream_class = SR_CLASS_A, + .flags = 0, + .format.u.s = { + .v = 0, + .subtype = AVTP_SUBTYPE_61883_IIDC, + .subtype_u.iec61883 = { + .sf = IEC_61883_SF_61883, + .fmt = IEC_61883_CIP_FMT_6, + .r = 0, + .format_u.iec61883_6 = { + .fdf_u.fdf = { + .evt = IEC_61883_6_FDF_EVT_AM824, + .sfc = IEC_61883_6_FDF_SFC_48000, + }, + .dbs = 2, + .b = 0, + .nb = 1, + .rsvd = 0, + .label_iec_60958_cnt = 0, + .label_mbla_cnt = 2, + .label_midi_cnt = 0, + .label_smptecnt = 0, + }, + }, + }, + .port = 0, + .stream_id = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 }, + .dst_mac = { 0x91, 0xE0, 0xF0, 0x00, 0xeb, 0x15 }, +}; + +static void usage (void) +{ + printf("\nUsage:\napp [options]\n"); + printf("\nOptions:\n" + "\t-m application mode: avdecc(default), listener, talker\n" + "\t-b use blocking API calls\n" + "\t-i use iov\n" + "\t-f media file name (default media.raw)\n" + "\t-t parse the media file as mpegts and apply PCR based flow control\n" + "\t-h print this help text\n"); +} + + +static void set_avb_config(unsigned int *avb_flags) +{ + *avb_flags = 0; +} + + +static int apply_config(struct avb_stream_params *stream_params) +{ + memcpy(&app.stream_params, stream_params, sizeof(struct avb_stream_params)); + + if (app.stream_params.direction == AVTP_DIRECTION_TALKER) { + app.stream_params.clock_domain = AVB_MEDIA_CLOCK_DOMAIN_PTP; + app.stream_params.talker.latency = max(CFG_CAPTURE_LATENCY_NS, sr_class_interval_p(app.stream_params.stream_class) / sr_class_interval_q(app.stream_params.stream_class)); + } else { + app.stream_params.clock_domain = AVB_MEDIA_CLOCK_DOMAIN_STREAM; + } + + app.stream_batch_size = BATCH_SIZE; + + if (!(app.config & FLAG_BLOCKING)) + app.stream_flags = AVTP_NONBLOCK; + else + app.stream_flags = 0; + + if (app.media_file_name == NULL) + app.media_file_name = DEFAULT_MEDIA_FILE_NAME; + + if (app.stream_params.direction == AVTP_DIRECTION_LISTENER) + app.media_fd = open(app.media_file_name, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + else + app.media_fd = open(app.media_file_name, O_RDONLY); + + if (app.media_fd < 0) { + printf("open(%s) failed: %s\n", app.media_file_name, strerror(errno)); + goto error_media_open; + } + + /* + * display the whole configuration + */ + + print_stream_id(stream_params->stream_id); + + printf("mode: "); + if (app.mode == MODE_LISTENER) + printf("LISTENER\n"); + else if (app.mode == MODE_TALKER) + printf("TALKER\n"); + else + printf("AVDECC %s\n", (app.stream_params.direction == AVTP_DIRECTION_LISTENER) ? "LISTENER":"TALKER"); + + printf("media file name: %s (fd %d)\n", app.media_file_name, app.media_fd); + + printf("flags: "); + if (app.config & FLAG_BLOCKING) + printf("BLOCKING "); + if (app.config & FLAG_IOV) + printf("IOV\n"); + + printf("\n\n"); + + return 0; + +error_media_open: + return -1; +} + +static int handle_avdecc_event(struct avb_control_handle *ctrl_h, unsigned int *msg_type, union avb_media_stack_msg *msg, bool *is_audio_stream) +{ + unsigned int msg_len = sizeof(union avb_media_stack_msg); + int rc; + + *is_audio_stream = false; + + rc = avb_control_receive(ctrl_h, msg_type, msg, &msg_len); + if (rc != AVB_SUCCESS) + goto receive_error; + + switch (*msg_type) { + case AVB_MSG_MEDIA_STACK_CONNECT: + if (!(avdecc_format_is_61883_6(&msg->media_stack_connect.stream_params.format) || avdecc_format_is_aaf_pcm(&msg->media_stack_connect.stream_params.format))) { + printf("\nIgnoring stream formats other than 61883_6 or AAF\n"); + goto exit; + } + + *is_audio_stream = true; + + printf("\nevent: AVB_MSG_MEDIA_STACK_CONNECT\n"); + app.connected_stream_index = msg->media_stack_connect.stream_index; + break; + + case AVB_MSG_MEDIA_STACK_DISCONNECT: + if (msg->media_stack_disconnect.stream_index != app.connected_stream_index) { + printf("\nIgnoring stream with a different id than the current connected stream\n"); + goto exit; + } + + *is_audio_stream = true; + + printf("\nevent: AVB_MSG_MEDIA_STACK_DISCONNECT\n"); + app.connected_stream_index = -1; + break; + + default: + break; + } + +exit: +receive_error: + return rc; +} + +static int listener_blocking(struct avb_stream_handle *stream_h, unsigned int batch_size, int file_dst) +{ + unsigned int event_len = EVENT_BUF_SZ; + unsigned char data_buf[DATA_BUF_SZ]; + struct avb_event event[EVENT_BUF_SZ]; + int nbytes; + int rc = 0; + + printf("Starting listener loop, blocking mode\n"); + + while (1) { + /* + * read data from stack... + */ + nbytes = avb_stream_receive(stream_h, data_buf, batch_size, event, &event_len); + if (nbytes <= 0) { + if (nbytes < 0) + printf("avb_stream_receive() failed: %s\n", avb_strerror(nbytes)); + else + printf("avb_stream_receive() incomplete\n"); + + goto exit; + } + + if (event_len != 0) { + if (event[0].event_mask & AVTP_MEDIA_CLOCK_RESTART) + printf ("AVTP media clock restarted\n"); + + if (event[0].event_mask & AVTP_PACKET_LOST) + printf ("AVTP packet lost\n"); + } + + /* + * ...and write to local file + */ + rc = write(file_dst, data_buf, nbytes); + if (rc < nbytes) { + if (rc < 0) + printf("write() failed: %s\n", strerror(errno)); + else + printf("write() incomplete\n"); + + goto exit; + } + } + +exit: + return rc; + +} + + +static int listener_nonblocking(struct avb_stream_handle *stream_h, int stream_fd, unsigned int batch_size, int file_dst) +{ + unsigned int event_len = EVENT_BUF_SZ; + struct pollfd poll_fds[2]; + unsigned char data_buf[DATA_BUF_SZ]; + struct avb_event event[EVENT_BUF_SZ]; + int nbytes; + int rc = 0; + int ctrl_rx_fd = -1; + int ready, i, n, nfds; + unsigned int event_type; + union avb_media_stack_msg msg; + bool is_audio_stream; + + printf("Starting listener loop, non-blocking mode\n"); + + /* + * listen to read event from the stack + */ + + nfds = 0; + + poll_fds[0].fd = stream_fd; + poll_fds[0].events = POLLIN; + poll_fds[0].revents = 0; + nfds++; + + /* control fd required only for avdecc mode */ + if (app.ctrl_h) { + ctrl_rx_fd = avb_control_rx_fd(app.ctrl_h); + poll_fds[1].fd = ctrl_rx_fd; + poll_fds[1].events = POLLIN; + poll_fds[1].revents = 0; + nfds++; + } else { + poll_fds[1].fd = -1; + poll_fds[1].events = 0; + poll_fds[1].revents = 0; + } + + while (1) { + if (signal_terminate) { + printf("processing terminate signal\n"); + rc = -1; + goto exit; + } + + if ((ready = poll(poll_fds, nfds, -1)) == -1) { + if (errno == EINTR) { + continue; + } else { + printf("poll(%d) failed while processing listener errno %d: %s\n", stream_fd, errno, strerror(errno)); + rc = -1; + goto exit; + } + } + + if (ready > 0) { + for (n = 0, i = 0; i < nfds && n < ready; i++) { + if (poll_fds[i].revents & POLLIN) { + if (poll_fds[i].fd == ctrl_rx_fd) { + n++; + + /* + * read control event from avdecc + */ + if (handle_avdecc_event(app.ctrl_h, &event_type, &msg, &is_audio_stream) == AVB_SUCCESS) { + if (event_type == AVB_MSG_MEDIA_STACK_DISCONNECT && is_audio_stream) { + rc = 0; + goto exit; /* disconnected, stop processing on this stream */ + } + } + } else if (poll_fds[i].fd == stream_fd) { + n++; + + /* + * read data from stack... + */ + nbytes = avb_stream_receive(stream_h, data_buf, batch_size, event, &event_len); + if (nbytes <= 0) { + if (nbytes < 0) + printf("avb_stream_receive() failed: %s\n", avb_strerror(nbytes)); + else + printf("avb_stream_receive() incomplete\n"); + + rc = nbytes; + goto exit; + } + + if (event_len != 0) { + if (event[0].event_mask & AVTP_MEDIA_CLOCK_RESTART) + printf ("AVTP media clock restarted\n"); + + if (event[0].event_mask & AVTP_PACKET_LOST) + printf ("AVTP packet lost\n"); + } + + /* + * ...and write to local file + */ + rc = write(file_dst, data_buf, nbytes); + if (rc < nbytes) { + if (rc < 0) + printf("write() failed: %s\n", strerror(errno)); + else + printf("write() incomplete\n"); + + goto exit; + } + } + } + } + } + } +exit: + return rc; +} + + +static int talker_blocking(struct avb_stream_handle *stream_h, unsigned int batch_size, int file_src) +{ + unsigned char data_buf[DATA_BUF_SZ] = {0}; + int nbytes; + int rc = 0; + + printf("Starting talker loop, blocking mode\n"); + + while (1) { + /* + * read data from local file... + */ + nbytes = read(file_src, data_buf, batch_size); + + /* no more data to read, we are done*/ + if (nbytes <= 0) { + if (nbytes < 0) + printf("read() failed: %s\n", strerror(errno)); + else + printf("read() incomplete\n"); + + goto exit; + } + + /* + * ...and write to avb stack + */ + rc = avb_stream_send(stream_h, data_buf, nbytes, NULL, 0); + if (rc != nbytes) { + if (rc < 0) + printf("avb_stream_send() failed: %s\n", avb_strerror(rc)); + else + printf("avb_stream_send() incomplete\n"); + + goto exit; + } + } + +exit: + return rc; + +} + + +static int talker_nonblocking(struct avb_stream_handle *stream_h, int stream_fd, unsigned int batch_size, int file_src) +{ + struct pollfd poll_fds[2]; + int nbytes; + int rc = 0; + int ctrl_rx_fd = -1; + int ready, i, n, nfds; + unsigned int event_type; + union avb_media_stack_msg msg; + struct avb_event event[EVENT_MAX]; + unsigned int event_n; + unsigned long long byte_count; + struct ts_parser p; + struct file_buffer *b; + bool is_audio_stream; + + printf("Starting talker loop, non-blocking mode\n"); + + b = malloc(sizeof(struct file_buffer)); + if (!b) { + printf("%s() cannot allocate file_buffer\n", __func__); + rc = -1; + goto err_malloc; + } + +loop: + lseek(file_src, 0, SEEK_SET); + + if (ts_parser_enabled) { + sleep(4); + + ts_parser_init(&p); + + file_buffer_init(b, 2); + } else + file_buffer_init(b, 1); + + byte_count = 0; + + /* + * listen to write event from the stack + */ + nfds = 0; + + poll_fds[0].fd = stream_fd; + poll_fds[0].events = POLLOUT; + poll_fds[0].revents = 0; + nfds++; + + /* control fd required only for avdecc mode */ + if (app.ctrl_h) { + ctrl_rx_fd = avb_control_rx_fd(app.ctrl_h); + poll_fds[1].fd = ctrl_rx_fd; + poll_fds[1].events = POLLIN; + poll_fds[0].revents = 0; + nfds++; + } else { + poll_fds[1].fd = -1; + poll_fds[1].events = 0; + poll_fds[0].revents = 0; + } + + while (1) { + if (signal_terminate) { + printf("processing terminate signal\n"); + rc = -1; + goto exit; + } + + if ((ready = poll(poll_fds, nfds, -1)) == -1) { + if (errno == EINTR) { + if (signal_pause) { + printf("processing pause signal\n"); + poll_fds[0].fd = -1; + } else { + printf("processing play signal\n"); + poll_fds[0].fd = stream_fd; + } + + continue; + } else { + printf("poll(%d) failed while processing listener errno %d: %s\n", stream_fd, errno, strerror(errno)); + rc = -1; + goto exit; + } + } + + if (ready > 0) { + for (n = 0, i = 0; i < nfds && n < ready; i++) { + if (poll_fds[i].revents & POLLIN) { + if (poll_fds[i].fd == ctrl_rx_fd) { + n++; + + /* + * read control event from avdecc + */ + if (handle_avdecc_event(app.ctrl_h, &event_type, &msg, &is_audio_stream) == AVB_SUCCESS) { + if (event_type == AVB_MSG_MEDIA_STACK_DISCONNECT && is_audio_stream) { + rc = 0; + goto exit; /* disconnected, stop processing on this stream */ + } + } + } + } else if (poll_fds[i].revents & POLLOUT) { + if (poll_fds[i].fd == stream_fd) { + n++; + + read: + if (file_buffer_empty(b, 0)) { + rc = file_buffer_write(b, file_src, 1000000); + if (rc <= 0) { + if (!rc) { + printf("loop\n"); + + talker_stream_flush(stream_h, NULL); + + goto loop; + } + + printf("file_buffer_write() failed\n"); + + goto exit; + } + } else { + rc = file_buffer_write(b, file_src, 0); + if (rc < 0) { + printf("file_buffer_write() failed\n"); + goto exit; + } + } + + nbytes = batch_size; + if (nbytes > file_buffer_available_wrap(b, 0)) + nbytes = file_buffer_available_wrap(b, 0); + + if (ts_parser_enabled) { + event_n = EVENT_MAX; + nbytes = ts_parser_timestamp_range(b, &p, event, &event_n, byte_count, nbytes, avb_stream_presentation_offset(stream_h)); + + if (nbytes <= 0) { + if (!nbytes) + goto read; + + rc = -1; + goto exit; + } + } else { + event_n = 0; + } + + /* + * ...and write to avb stack + */ + rc = avb_stream_send(stream_h, file_buffer_buf(b, 0), nbytes, event, event_n); + if (rc != nbytes) { + if (rc < 0) + printf("avb_stream_send() failed: %s\n", avb_strerror(rc)); + else + printf("avb_stream_send() incomplete\n"); + + goto exit; + } + + file_buffer_read(b, 0, nbytes); + + byte_count += nbytes; + } + } + } + } + } + +exit: + free(b); + +err_malloc: + return rc; +} + + +static int run_listener(struct avb_stream_handle *stream_h, int stream_fd, unsigned int batch_size) +{ + int rc; + + if (app.config & FLAG_BLOCKING) + rc = listener_blocking(stream_h, batch_size, app.media_fd); + else + rc = listener_nonblocking(stream_h, stream_fd, batch_size, app.media_fd); + + return rc; +} + + +static int run_talker(struct avb_stream_handle *stream_h, int stream_fd, unsigned int batch_size) +{ + int rc; + + if (app.config & FLAG_BLOCKING) + rc = talker_blocking(stream_h, batch_size, app.media_fd); + else + rc = talker_nonblocking(stream_h, stream_fd, batch_size, app.media_fd); + + return rc; +} + + +void signal_terminate_handler (int signal_num) +{ + signal_terminate = 1; +} + + +void signal_pause_handler (int signal_num) +{ + if (signal_num == SIGUSR1) + signal_pause = 1; + else if(signal_num == SIGUSR2) + signal_pause = 0; +} + +int main(int argc, char *argv[]) +{ + struct avb_handle *avb_h; + struct avb_stream_handle* stream_h; + union avb_media_stack_msg msg; + unsigned int avb_flags; + int stream_fd, option; + unsigned int event_type; + int ctrl_rx_fd; + struct pollfd ctrl_poll; + int rc = 0; + struct sched_param param = { + .sched_priority = PROCESS_PRIORITY, + }; + struct sigaction action; + bool is_audio_stream; + + + /* + * Increase process priority to match the AVTP thread priority + */ + + if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) { + printf("sched_setscheduler(), %s\n", strerror(errno)); + rc = -1; + goto exit; + } + + setlinebuf(stdout); + + printf("NXP's GenAVB reference audio application\n"); + + + /* + * retrieve user's configuration parameters + */ + + app.mode = MODE_AVDECC; app.config = 0; /* default is AVDECC, NON-BLOCKING, SINGLE BUFFER */ + app.connected_stream_index = -1; + + while ((option = getopt(argc, argv,"m:bif:ht")) != -1) { + switch (option) { + case 'm': + if (!strcasecmp(optarg, "listener")) + app.mode = MODE_LISTENER; + else if (!strcasecmp(optarg, "talker")) + app.mode = MODE_TALKER; + else if(!strcasecmp(optarg, "avdecc")) + app.mode = MODE_AVDECC; + else { + usage(); + goto exit; + } + break; + + case 'b': + app.config |= FLAG_BLOCKING; + printf("blocking mode not supported\n"); + rc = -1; + goto exit; + + case 'i': + app.config |= FLAG_IOV; + printf("multiple buffers mode not supported\n"); + rc = -1; + goto exit; + + case 'f': + app.media_file_name = optarg; + break; + + case 't': + ts_parser_enabled = 1; + break; + + case 'h': + default: + usage(); + rc = -1; + goto exit; + } + } + + + /* + * set signals handler + */ + action.sa_handler = signal_terminate_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGTERM, &action, NULL) < 0) /* Termination signal */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGQUIT, &action, NULL) < 0) /* Quit from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGINT, &action, NULL) < 0) /* Interrupt from keyboard */ + printf("sigaction(): %s\n", strerror(errno)); + + action.sa_handler = signal_pause_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + printf("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGUSR1, &action, NULL) < 0) /* User signal to pause talker streaming */ + printf("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGUSR2, &action, NULL) < 0) /* Resume talker streaming */ + printf("sigaction(): %s\n", strerror(errno)); + + /* + * setup the avb stack + */ + + set_avb_config(&avb_flags); + + rc = avb_init(&avb_h, avb_flags); + if (rc != AVB_SUCCESS) { + printf("avb_init() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_avb_init; + } + + if (app.mode != MODE_AVDECC) { + rc = msrp_init(avb_h); + if (rc < 0) + goto err_msrp_init; + } + +wait_new_stream: + printf("\nwait for new stream...\n"); + + if (app.mode == MODE_AVDECC) { + /* + * listen to avdecc events to get stream parameters + */ + + rc = avb_control_open(avb_h, &app.ctrl_h, AVB_CTRL_AVDECC_MEDIA_STACK); + if (rc != AVB_SUCCESS) { + printf("avb_control_open() failed: %s\n", avb_strerror(rc)); + goto error_control_open; + } + + ctrl_rx_fd = avb_control_rx_fd(app.ctrl_h); + ctrl_poll.fd = ctrl_rx_fd; + ctrl_poll.events = POLLIN; + ctrl_poll.revents = 0; + + while (1) { + if (poll(&ctrl_poll, 1, -1) == -1) { + printf("poll(%d) failed on waiting for connect\n", ctrl_poll.fd); + rc = -1; + goto error_ctrl_poll; + } + + if (ctrl_poll.revents & POLLIN) { + /* + * read control event from avdecc + */ + if (handle_avdecc_event(app.ctrl_h, &event_type, &msg, &is_audio_stream) == AVB_SUCCESS) { + if (event_type == AVB_MSG_MEDIA_STACK_CONNECT && is_audio_stream) { + apply_config(&msg.media_stack_connect.stream_params); + break; /* connected, start stream processing */ + } + } + } + } + }else { + /* + * no avdecc, static configuration used + */ + + if (app.mode == MODE_TALKER) + default_stream_params.direction = AVTP_DIRECTION_TALKER; + else + default_stream_params.direction = AVTP_DIRECTION_LISTENER; + + apply_config(&default_stream_params); + + if (app.mode == MODE_TALKER) { + rc = msrp_talker_register(&default_stream_params); + if (rc != AVB_SUCCESS) { + printf("msrp_talker_register error, rc = %d\n", rc); + goto err_msrp; + } + } else if (app.mode == MODE_LISTENER) { + rc = msrp_listener_register(&default_stream_params); + if (rc != AVB_SUCCESS) { + printf("msrp_listener_register error, rc = %d\n", rc); + goto err_msrp; + } + } + + } + + + /* + * setup the stream + */ + sleep(3); + + rc = avb_stream_create(avb_h, &stream_h, &app.stream_params, &app.stream_batch_size, app.stream_flags); + if (rc != AVB_SUCCESS) { + printf("avb_stream_create() failed: %s\n", avb_strerror(rc)); + rc = -1; + goto error_stream_create; + } + printf("Configured AVB batch size (bytes): %d\n", app.stream_batch_size); + + /* + * retrieve the file descriptor associated to the stream + */ + + stream_fd = avb_stream_fd(stream_h); + if (stream_fd < 0) { + printf("avb_stream_fd() failed: %s\n", avb_strerror(stream_fd)); + rc = -1; + goto error_stream_fd; + } + + /* + * run listener/talker main processing function + */ + + if (app.stream_params.direction & AVTP_DIRECTION_TALKER) + rc = run_talker(stream_h, stream_fd, app.stream_batch_size); + else + rc = run_listener(stream_h, stream_fd, app.stream_batch_size); + + /* + * main processing loop exited. could be due to error or avdecc disconnect + */ + if (rc < 0) { + printf("Loop function exited with error code %d\n", rc); + } else { + printf("Loop function exited upon avdecc disconnect\n"); + + avb_stream_destroy(stream_h); + + if (app.ctrl_h) { + avb_control_close(app.ctrl_h); + app.ctrl_h = NULL; + } + + goto wait_new_stream; + } + + /* + * destroy the stream, disconnect for avb stack and close media files + */ + +error_stream_fd: + avb_stream_destroy(stream_h); + +error_stream_create: + if (app.mode == MODE_TALKER) + msrp_talker_deregister(&default_stream_params); + else if (app.mode == MODE_LISTENER) + msrp_listener_deregister(&default_stream_params); + +err_msrp: + close(app.media_fd); + +error_ctrl_poll: + if (app.ctrl_h) + avb_control_close(app.ctrl_h); + +error_control_open: + if (app.mode != MODE_AVDECC) + msrp_exit(); +err_msrp_init: + avb_exit(avb_h); + +error_avb_init: +exit: + return rc; + +} diff --git a/apps/linux/tsn-app/CMakeLists.txt b/apps/linux/tsn-app/CMakeLists.txt new file mode 100644 index 0000000..2424b89 --- /dev/null +++ b/apps/linux/tsn-app/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.10) + +project(tsn-app) + +include_directories(${GENAVB_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} + main.c + tsn_task.c + tsn_tasks_config.c + thread_config.c + cyclic_task.c + serial_controller.c + network_only.c + tsn_timer.c + ../common/stats.c + ../common/stats.c + ../common/thread.c + ../common/log.c + ../common/time.c + ../common/timer.c +) + +set(OPCUA_SUPPORT $ENV{OPCUA_SUPPORT}) + +if(OPCUA_SUPPORT) + target_compile_definitions(${PROJECT_NAME} PUBLIC OPCUA_SUPPORT) + target_sources(${PROJECT_NAME} PRIVATE opcua/opcua_server.c opcua/model/tsn_app_model.c) + target_link_libraries(${PROJECT_NAME} open62541) +endif() + +target_compile_options(${PROJECT_NAME} PUBLIC -O2 -Wall -Werror -g -DSTATS_LOG) +target_link_libraries(${PROJECT_NAME} -Wl,-unresolved-symbols=ignore-in-shared-libs) + +if(DEFINED GENAVB_LIB_DIR) + add_library(genavb SHARED IMPORTED) + set_target_properties(genavb PROPERTIES IMPORTED_LOCATION "${GENAVB_LIB_DIR}/libgenavb.so") +endif() + +target_link_libraries(${PROJECT_NAME} genavb) +target_link_libraries(${PROJECT_NAME} pthread) +target_link_libraries(${PROJECT_NAME} dl) + +install(TARGETS ${PROJECT_NAME} DESTINATION usr/bin) diff --git a/apps/linux/tsn-app/cyclic_task.c b/apps/linux/tsn-app/cyclic_task.c new file mode 100644 index 0000000..21765f1 --- /dev/null +++ b/apps/linux/tsn-app/cyclic_task.c @@ -0,0 +1,423 @@ +/* + * Copyright 2019-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "cyclic_task.h" +#include "tsn_tasks_config.h" +#include "opcua/opcua_server.h" + +#include "../common/log.h" +#include "../common/time.h" + +#ifdef TRACE_SNAPSHOT +#include +#define TRACE_SNAPSHOT_FILE "/sys/kernel/debug/tracing/snapshot" +#define TRACE_SNAPSHOT_TAG "1" +#define TRACE_SNAPSHOT_THRESHOLD 50000 +#endif + +static bool reset_stats; + +void cyclic_stats_reset_handler(void) +{ + reset_stats = true; +} + +void socket_stats_print(struct socket *sock) +{ + if (sock->stats_snap.pending) { + stats_compute(&sock->stats_snap.traffic_latency); + + INF("cyclic rx socket(%p) net_sock(%p) peer id: %d", sock, sock->net_sock, sock->peer_id); + INF("valid frames : %u", sock->stats_snap.valid_frames); + INF("err id : %u", sock->stats_snap.err_id); + INF("err ts : %u", sock->stats_snap.err_ts); + INF("err underflow : %u", sock->stats_snap.err_underflow); + INF("link %s", sock->stats_snap.link_status ? "up" : "down"); + + stats_print(&sock->stats_snap.traffic_latency); + hist_print(&sock->stats_snap.traffic_latency_hist); + + opcua_update_cyclic_socket(sock); + + sock->stats_snap.pending = false; + } +} + +static void socket_stats_dump(struct socket *sock) +{ + if (sock->stats_snap.pending) + return; + + memcpy(&sock->stats_snap, &sock->stats, sizeof(struct socket_stats)); + sock->stats_snap.pending = true; + + stats_reset(&sock->stats.traffic_latency); +} + +void cyclic_stats_print(struct cyclic_task *c_task) +{ + struct socket *sock; + struct tsn_task *task = c_task->task; + int i; + + tsn_task_stats_print(task); + + /* RX sockets */ + for (i = 0; i < c_task->num_peers; i++) { + sock = &c_task->rx_socket[i]; + socket_stats_print(sock); + net_socket_stats_print(sock->net_sock); + } + + /* TX socket */ + sock = &c_task->tx_socket; + socket_stats_print(sock); + net_socket_stats_print(sock->net_sock); +} + +static void cyclic_stats_dump(struct cyclic_task *c_task) +{ + int i; + + for (i = 0; i < c_task->num_peers; i++) + socket_stats_dump(&c_task->rx_socket[i]); +} + +static void cyclic_task_stats_reset(struct cyclic_task *c_task) +{ + int i; + + tsn_task_stats_reset(c_task->task); + + for (i = 0; i < c_task->num_peers; i++) { + c_task->rx_socket[i].stats.traffic_latency_min = 0xffffffff; + c_task->rx_socket[i].stats.traffic_latency_max = 0; + c_task->rx_socket[i].stats.err_id = 0; + c_task->rx_socket[i].stats.err_ts = 0; + c_task->rx_socket[i].stats.err_underflow = 0; + c_task->rx_socket[i].stats.valid_frames = 0; + + stats_init(&c_task->rx_socket[i].stats.traffic_latency, 31, "traffic latency", NULL); + hist_reset(&c_task->rx_socket[i].stats.traffic_latency_hist); + } +} + +#ifdef TRACE_SNAPSHOT +void cyclic_task_init_trace_snapshot(struct cyclic_task *c_task) +{ + c_task->trace_snapshot_fd = open(TRACE_SNAPSHOT_FILE, O_WRONLY); +} + +static void cyclic_task_take_trace_snapshot(struct cyclic_task *c_task) +{ + if (c_task->trace_snapshot_fd > 0) + if (write(c_task->trace_snapshot_fd, TRACE_SNAPSHOT_TAG, sizeof(TRACE_SNAPSHOT_TAG)) < 0) + ERR("Could not write to trace snapshot file %s\n", TRACE_SNAPSHOT_FILE); +} + +#endif + +static void cyclic_net_receive(struct cyclic_task *c_task) +{ + int i; + int status; + struct socket *sock; + struct tsn_task *task = c_task->task; + struct tsn_common_hdr *hdr; + int rx_frame; + uint32_t traffic_latency; + + for (i = 0; i < c_task->num_peers; i++) { + sock = &c_task->rx_socket[i]; + rx_frame = 0; + retry: + status = tsn_net_receive_sock(sock->net_sock); + if (status == NET_NO_FRAME && !rx_frame) { + sock->stats.err_underflow++; +#ifdef TRACE_SNAPSHOT + cyclic_task_take_trace_snapshot(c_task); +#endif + } + + if (status != NET_OK) { + sock->stats.link_status = 0; + continue; + } + + rx_frame = 1; + + hdr = tsn_net_sock_buf(sock->net_sock); + if (hdr->sched_time != (tsn_task_get_time(task) - task->params->transfer_time_ns)) { + sock->stats.err_ts++; + goto retry; + } + + if (hdr->src_id != sock->peer_id) { + sock->stats.err_id++; + goto retry; + } + + traffic_latency = sock->net_sock->ts - hdr->sched_time; + + stats_update(&sock->stats.traffic_latency, traffic_latency); + hist_update(&sock->stats.traffic_latency_hist, traffic_latency); + + if (traffic_latency > sock->stats.traffic_latency_max) + sock->stats.traffic_latency_max = traffic_latency; + + if (traffic_latency < sock->stats.traffic_latency_min) + sock->stats.traffic_latency_min = traffic_latency; + + sock->stats.valid_frames++; + sock->stats.link_status = 1; + + if (c_task->net_rx_func) + c_task->net_rx_func(c_task->ctx, hdr->msg_id, hdr->src_id, + hdr + 1, hdr->len); + } +} + +int cyclic_net_transmit(struct cyclic_task *c_task, int msg_id, void *buf, int len) +{ + struct tsn_task *task = c_task->task; + struct socket *sock = &c_task->tx_socket; + struct tsn_common_hdr *hdr = tsn_net_sock_buf(sock->net_sock); + int total_len = (len + sizeof(*hdr)); + int status; + + if (total_len >= task->params->tx_buf_size) + goto err; + + hdr->msg_id = msg_id; + hdr->src_id = c_task->id; + hdr->sched_time = tsn_task_get_time(task); + hdr->len = len; + + if (len) + memcpy(hdr + 1, buf, len); + + sock->net_sock->len = total_len; + + status = tsn_net_transmit_sock(sock->net_sock); + if (status != NET_OK) + goto err; + + return 0; + +err: + return -1; +} + +static int main_cyclic(void *data, unsigned int events) +{ + struct cyclic_task *c_task = data; + struct tsn_task *task = c_task->task; + unsigned int num_sched_stats = CYCLIC_STAT_PERIOD_SEC * (NSECS_PER_SEC / task->params->task_period_ns); + uint64_t n_time; + uint64_t now; + int rc = -1; + + if (genavb_clock_gettime64(task->params->clk_id, &now) < 0) { + ERR("genavb_clock_gettime64() error\n"); + goto timer_err; + } + + if (reset_stats) { + cyclic_task_stats_reset(c_task); + reset_stats = false; + } + + rc = tsn_timer_check(task->timer, now, &n_time); + if (rc < 0) + goto timer_err; + + tsn_task_stats_start(task, (int)n_time, now); + +#ifdef TRACE_SNAPSHOT + if ((now - task->sched_time) > TRACE_SNAPSHOT_THRESHOLD) { + cyclic_task_take_trace_snapshot(c_task); + } +#endif + + /* + * Receive, frames should be available + */ + cyclic_net_receive(c_task); + + /* + * Main loop + */ + if (c_task->loop_func) + c_task->loop_func(c_task->ctx, 0); + + tsn_task_stats_end(task); + + if (!(task->stats.sched % num_sched_stats)) { + tsn_stats_dump(task); + cyclic_stats_dump(c_task); + } + + return 0; + +timer_err: + if (c_task->loop_func) + c_task->loop_func(c_task->ctx, -1); + + cyclic_task_stop(c_task); + cyclic_task_start(c_task); + +#ifdef ECANCELED + if (rc == ECANCELED) + task->stats.clock_discont++; + else +#endif + task->stats.clock_err++; + + return rc; +} + +void cyclic_task_set_period(struct cyclic_task *c_task, unsigned int period_ns) +{ + struct tsn_task_params *params = &c_task->params; + + /* Use default config */ + if (!period_ns) + return; + + params->task_period_ns = period_ns; + params->transfer_time_ns = period_ns / 2; + + if (c_task->type == CYCLIC_CONTROLLER) + params->task_period_offset_ns = 0; + else + params->task_period_offset_ns = period_ns / 2; +} + +int cyclic_task_set_num_peers(struct cyclic_task *c_task, unsigned int num_peers) +{ + /* Use default config */ + if (!num_peers) + return 0; + + if (num_peers > MAX_PEERS) + return -1; + + c_task->num_peers = num_peers; + + return 0; +} + +void cyclic_task_get_monitoring(struct cyclic_task *task, uint32_t *sched_err_max, uint32_t *traffic_latency_max, + uint32_t *traffic_latency_min, uint32_t num_socket_monitored) +{ + uint32_t i; + + *sched_err_max = task->task->stats.sched_err_max; + task->task->stats.sched_err_max = 0; + for (i = 0; i < task->num_peers; i++) { + traffic_latency_max[i] = task->rx_socket[i].stats.traffic_latency_max; + traffic_latency_min[i] = task->rx_socket[i].stats.traffic_latency_min; + + task->rx_socket[i].stats.traffic_latency_max = 0; + task->rx_socket[i].stats.traffic_latency_min = 0xffffffff; + + if (i >= num_socket_monitored) + return; + } +} + +int cyclic_task_start(struct cyclic_task *c_task) +{ + unsigned int period_ns = c_task->params.task_period_ns; + + if (!period_ns || ((NSECS_PER_SEC / period_ns) * period_ns != NSECS_PER_SEC)) { + ERR("invalid task period(%u ns), needs to be an integer divider of 1 second\n", period_ns); + return -1; + } + + return tsn_task_start(c_task->task); +} + +void cyclic_task_stop(struct cyclic_task *c_task) +{ + tsn_task_stop(c_task->task); +} + +int cyclic_task_init(struct cyclic_task *c_task, + void (*net_rx_func)(void *ctx, int msg_id, int src_id, void *buf, int len), + void (*loop_func)(void *ctx, int timer_status), void *ctx) +{ + struct tsn_task_params *params = &c_task->params; + struct tsn_stream *rx_stream, *tx_stream; + int i; + int rc; + + INF("cyclic task type: %d, id: %d, num peers: %d\n", + c_task->type, c_task->id, c_task->num_peers); + INF("task params"); + INF("clk_id : %u", params->clk_id); + INF("task_period_ns : %u", params->task_period_ns); + INF("task_period_offset_ns : %u", params->task_period_offset_ns); + INF("transfer_time_ns : %u", params->transfer_time_ns); + + tx_stream = tsn_conf_get_stream(c_task->tx_socket.stream_id); + if (!tx_stream) + goto err; + + memcpy(¶ms->tx_params[0].addr, &tx_stream->address, + sizeof(struct net_address)); + params->num_tx_socket = 1; + params->tx_params[0].addr.port = 0; + + for (i = 0; i < c_task->num_peers; i++) { + c_task->rx_socket[i].id = i; + rx_stream = tsn_conf_get_stream(c_task->rx_socket[i].stream_id); + if (!rx_stream) + goto err; + + memcpy(¶ms->rx_params[i].addr, &rx_stream->address, + sizeof(struct net_address)); + params->rx_params[i].addr.port = 0; + params->num_rx_socket++; + } + + c_task->net_rx_func = net_rx_func; + c_task->loop_func = loop_func; + c_task->ctx = ctx; + + rc = tsn_task_register(&c_task->task, params, c_task->id, main_cyclic, c_task); + if (rc < 0) { + ERR("tsn_task_register() failed rc = %d\n", rc); + goto err; + } + + for (i = 0; i < c_task->num_peers; i++) { + c_task->rx_socket[i].net_sock = tsn_net_sock_rx(c_task->task, i); + c_task->rx_socket[i].stats.traffic_latency_min = 0xffffffff; + + stats_init(&c_task->rx_socket[i].stats.traffic_latency, 31, "traffic latency", NULL); + hist_init(&c_task->rx_socket[i].stats.traffic_latency_hist, 100, 1000); + } + + c_task->tx_socket.net_sock = tsn_net_sock_tx(c_task->task, 0); + + INF("success\n"); + + opcua_init_params(c_task); + + return 0; + +err: + return -1; +} + +void cyclic_task_exit(struct cyclic_task *c_task) +{ + tsn_task_deregister(c_task->task); +} diff --git a/apps/linux/tsn-app/cyclic_task.h b/apps/linux/tsn-app/cyclic_task.h new file mode 100644 index 0000000..e6c96d9 --- /dev/null +++ b/apps/linux/tsn-app/cyclic_task.h @@ -0,0 +1,70 @@ +/* + * Copyright 2019-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _CYCLIC_TASK_H_ +#define _CYCLIC_TASK_H_ + +#include "tsn_task.h" +#include "tsn_tasks_config.h" + +//#define TRACE_SNAPSHOT 1 // Uncomment to trigger a kernel ftrace snapshot on some latency errors (see cyclic_task.c) + +#define CYCLIC_STAT_PERIOD_SEC 5 + +struct socket_stats { + unsigned int valid_frames; + unsigned int err_id; + unsigned int err_ts; + unsigned int err_underflow; + int link_status; + unsigned int traffic_latency_max; + unsigned int traffic_latency_min; + struct stats traffic_latency; + struct hist traffic_latency_hist; + bool pending; +}; + +struct socket { + int id; + int peer_id; + int stream_id; + struct socket_stats stats; + struct socket_stats stats_snap; + struct net_socket *net_sock; +}; + +struct cyclic_task { + struct tsn_task *task; + struct tsn_task_params params; + int type; + int id; + int num_peers; + struct socket rx_socket[MAX_PEERS]; + struct socket tx_socket; + void (*net_rx_func)(void *ctx, int msg_id, int src_id, void *buf, int len); + void (*loop_func)(void *ctx, int timer_status); + void *ctx; +#ifdef TRACE_SNAPSHOT + int trace_snapshot_fd; +#endif +}; + +int cyclic_task_init(struct cyclic_task *c_task, + void (*net_rx_func)(void *ctx, int msg_id, int src_id, void *buf, int len), + void (*loop_func)(void *ctx, int timer_status), void *ctx); +void cyclic_task_exit(struct cyclic_task *c_task); +int cyclic_task_start(struct cyclic_task *); +void cyclic_task_stop(struct cyclic_task *); +int cyclic_net_transmit(struct cyclic_task *c_task, int msg_id, void *buf, int len); +void cyclic_task_get_monitoring(struct cyclic_task *task, uint32_t *sched_err_max, uint32_t *traffic_latency_max, + uint32_t *traffic_latency_min, uint32_t num_socket_monitored); +void cyclic_stats_print(struct cyclic_task *c_task); +void cyclic_stats_reset_handler(void); +void cyclic_task_init_trace_snapshot(struct cyclic_task *c_task); +void cyclic_task_set_period(struct cyclic_task *c_task, unsigned int period_ns); +int cyclic_task_set_num_peers(struct cyclic_task *c_task, unsigned int num_peers); + +#endif /* _CYCLIC_TASK_H_ */ diff --git a/apps/linux/tsn-app/main.c b/apps/linux/tsn-app/main.c new file mode 100644 index 0000000..5c6e209 --- /dev/null +++ b/apps/linux/tsn-app/main.c @@ -0,0 +1,429 @@ +/* + * Copyright 2020-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _XOPEN_SOURCE 600 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../common/log.h" +#include "../common/timer.h" +#include "../common/thread.h" + +#include "tsn_tasks_config.h" +#include "cyclic_task.h" +#include "serial_controller.h" +#include "network_only.h" +#include "opcua/opcua_server.h" + +#define STATS_THREAD_PERIOD_SEC 1 +#define NUM_CONTROL_FDS 2 +#define PTS_NAME_STR_LEN 30 +#define TSN_APP_LOG "/var/log/tsn_app" + +static int signal_terminate = 0; + +enum app_mode { + NETWORK_ONLY, + SERIAL +}; + +struct stats_ctx { + int fd; + thr_thread_slot_t *thread_slot; + void (*handler)(void *data); + void *data; +}; + +static int stats_handler(void *data, unsigned int events) +{ + int rc; + uint64_t n_exp; + struct stats_ctx *ctx = data; + + rc = read(ctx->fd, &n_exp, sizeof(n_exp)); + if (rc < 0) { + ERR("read error: %s", strerror(errno)); + goto err; + } + + if (n_exp > 0) { + aar_log_update_time(GENAVB_CLOCK_GPTP_0_0); + + if (ctx->handler) + ctx->handler(ctx->data); + + thread_print_stats(); + } + + return 0; + +err: + return -1; +} + +static int stats_thread_init(struct stats_ctx *ctx, void (*handler)(void *), void *data) +{ + ctx->fd = create_timerfd_periodic(CLOCK_MONOTONIC); + if (ctx->fd < 0) { + ERR("create_timer_fd_periodic() failed"); + goto err; + } + + INF("%s: Add the stats thread slot\n", __func__); + + ctx->handler = handler; + ctx->data = data; + + if (thread_slot_add(THR_CAP_STATS, ctx->fd, EPOLLIN, ctx, stats_handler, NULL, 0, &ctx->thread_slot) < 0) { + ERR("thread_slot_add() failed"); + goto err_close; + } + + if (start_timerfd_periodic(ctx->fd, STATS_THREAD_PERIOD_SEC, 0) < 0) { + ERR("start_timerfd_periodic() failed"); + goto err_close; + } + + return 0; + +err_close: + close(ctx->fd); + +err: + return -1; +} + +static void stats_thread_exit(struct stats_ctx *ctx) +{ + stop_timerfd(ctx->fd); + close(ctx->fd); +} + +static int pseudo_tty_init(char *slave_file) +{ + int fd, file_fd, rc; + char *pts_name; + + fd = posix_openpt(O_RDWR | O_NOCTTY); + if (fd < 0) { + ERR("posix_openpt(): %s\n", strerror(errno)); + goto err; + } + + rc = grantpt(fd); + if (rc < 0) { + ERR("grantpt(): %s\n", strerror(errno)); + goto err_close; + } + + rc = unlockpt(fd); + if (rc < 0) { + ERR("unlockpt(): %s\n", strerror(errno)); + goto err_close; + } + + pts_name = ptsname(fd); + if (!pts_name) { + ERR("ptsname() error\n"); + goto err_close; + } + + if (slave_file) { + ssize_t written; + + file_fd = open(slave_file, O_CREAT | O_WRONLY, S_IROTH); + if (file_fd < 0) { + ERR("open(): %s\n", strerror(errno)); + goto err_close; + } + + written = write(file_fd, pts_name, strlen(pts_name)); + if (written < 0) { + ERR("write(): %s\n", strerror(errno)); + goto err_close_file; + } + if (written < strlen(pts_name)) { + ERR("write() incomplete\n"); + goto err_close_file; + } + + close(file_fd); + } + + INF("pts device: %s\n", pts_name); + + return fd; + +err_close_file: + close(file_fd); + +err_close: + close(fd); + +err: + return -1; +} + +void signal_terminate_handler(int signal_num) +{ + signal_terminate = 1; +} + +void sigusr1_handler(int sig) +{ + cyclic_stats_reset_handler(); +} + + +static void usage(void) +{ + printf("\nUsage:\ntsn-app [options]\n"); + printf("\nOptions:\n" + "\t-m supported mode: \"network_only\" or \"serial\" (default: \"network_only\")\n" + "\t-r supported role: \"controller\", \"io_device_0\" or \"io_device_1\" (default: \"controller\")\n" + "\t-s supported sleep handlers: \"epoll\" or \"nanosleep\" (default: \"nanosleep\")\n" + "\t-p task period in nanoseconds (default: 2000000 ns)\n" + "\t-n number of IO devices (default: 1, only used if role is set to \"controller\"\n" + "\t-f pts file name (default: don't write pts file name, only used if mode is set to \"serial\")\n" + "\t-x use AF_XDP sockets instead of standard raw sockets\n"); +}; + +int main(int argc, char *argv[]) +{ + unsigned int mode = NETWORK_ONLY; + unsigned int role = CONTROLLER_0; + unsigned int timer_type = TSN_TIMER_NANOSLEEP; + unsigned long period_ns = 0; + unsigned long num_peers = 0; + unsigned int flags = 0; + struct genavb_handle *genavb_handle; + struct sched_param param = { + .sched_priority = 1, + }; + struct sigaction action; + int option; + int rc = 0; + int pt_fd = -1; + char *slave_file = NULL; + struct stats_ctx stats_ctx; + void (*stats_handler)(void *); + void (*exit_fn)(void *) = NULL; + void *ctx; + + //setlinebuf(stdout); + + while ((option = getopt(argc, argv, "hf:m:p:r:n:s:x")) != -1) { + switch (option) { + + case 'f': + slave_file = optarg; + break; + + case 'm': + if (!strcasecmp(optarg, "serial")) { + mode = SERIAL; + } else if (!strcasecmp(optarg, "network_only")) { + mode = NETWORK_ONLY; + } else { + printf("invalid -m %s option\n", optarg); + usage(); + goto err; + } + break; + + case 'p': + if ((h_strtoul(&period_ns, optarg, NULL, 0) < 0) || !period_ns) { + printf("invalid -p %s option\n", optarg); + usage(); + goto err; + } + break; + + case 'n': + if ((h_strtoul(&num_peers, optarg, NULL, 0) < 0) || !num_peers) { + printf("invalid -n %s option\n", optarg); + usage(); + goto err; + } + break; + + case 'r': + if (!strcasecmp(optarg, "controller")) { + role = CONTROLLER_0; + } else if (!strcasecmp(optarg, "io_device_0")) { + role = IO_DEVICE_0; + } else if (!strcasecmp(optarg, "io_device_1")) { + role = IO_DEVICE_1; + } else { + printf("invalid -r %s option\n", optarg); + usage(); + goto err; + } + break; + + case 's': + if (!strcasecmp(optarg, "epoll")) { + timer_type = TSN_TIMER_EPOLL; + } else if (!strcasecmp(optarg, "nanosleep")) { + timer_type = TSN_TIMER_NANOSLEEP; + } else { + printf("invalid -s %s option\n", optarg); + usage(); + goto err; + } + break; + + case 'x': + flags = GENAVB_FLAGS_NET_XDP; + break; + case 'h': + usage(); + goto err; + default: + rc = -1; + usage(); + goto err; + } + } + + rc = aar_log_init(TSN_APP_LOG); + if (rc < 0) + goto err; + + INF("NXP's GenAVB/TSN stack reference TSN application\n"); + + rc = genavb_init(&genavb_handle, flags); + if (rc != GENAVB_SUCCESS) { + ERR("genavb_init() failed: %s", avb_strerror(rc)); + rc = -1; + goto err; + } + + /* + * set signals handler + */ + action.sa_handler = signal_terminate_handler; + action.sa_flags = 0; + + if (sigemptyset(&action.sa_mask) < 0) + ERR("sigemptyset(): %s\n", strerror(errno)); + + if (sigaction(SIGTERM, &action, NULL) < 0) /* Termination signal */ + ERR("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGQUIT, &action, NULL) < 0) /* Quit from keyboard */ + ERR("sigaction(): %s\n", strerror(errno)); + + if (sigaction(SIGINT, &action, NULL) < 0) /* Interrupt from keyboard */ + ERR("sigaction(): %s\n", strerror(errno)); + + action.sa_handler = sigusr1_handler; + if (sigaction(SIGUSR1, &action, NULL) < 0) /* SIGUSR1 */ + ERR("sigaction(): %s\n", strerror(errno));; + + rc = sched_setscheduler(0, SCHED_FIFO, ¶m); + if (rc < 0) { + ERR("sched_setscheduler() failed: %s\n", strerror(errno)); + goto err_avb_exit; + } + + rc = thread_init(); + if (rc < 0) + goto err_avb_exit; + + rc = opcua_server_init(); + if (rc < 0) { + ERR("opcua_server_init() failed: %s\n", strerror(errno)); + goto err_thread_exit; + } + + if (mode == SERIAL) { + exit_fn = serial_controller_exit; + stats_handler = serial_controller_stats_handler; + + if (role != CONTROLLER_0) { + ERR("Only controller role is supported\n"); + rc = -1; + goto err_opcua_exit; + } + + pt_fd = pseudo_tty_init(slave_file); + if (pt_fd < 0) { + ERR("pseudo_tty_init() failed\n"); + rc = -1; + goto err_opcua_exit; + } + + ctx = serial_controller_init(period_ns, num_peers, pt_fd, timer_type); + if (!ctx) { + ERR("serial_controller_init() failed\n"); + goto err_close_pt; + } + } else if (mode == NETWORK_ONLY) { + exit_fn = network_only_exit; + stats_handler = network_only_stats_handler; + + ctx = network_only_init(role, period_ns, num_peers, timer_type); + if (!ctx) { + ERR("network_only_init() failed\n"); + goto err_opcua_exit; + } + } else { + ERR("Invalid mode %d\n", mode); + rc = -1; + goto err_opcua_exit; + } + + if (stats_thread_init(&stats_ctx, stats_handler, ctx) < 0) { + ERR("stats_thread_init() failed: %s\n", strerror(errno)); + rc = -1; + goto err_exit_fn; + } + + while (1) { + pause(); + if ((errno == EINTR) && signal_terminate) { + INF("processing terminate signal\n"); + rc = -1; + goto err_stats_exit; + } + } + +err_stats_exit: + stats_thread_exit(&stats_ctx); + +err_exit_fn: + if (exit_fn) + exit_fn(ctx); + +err_close_pt: + if (pt_fd > 0) + close(pt_fd); + +err_opcua_exit: + opcua_server_exit(); + +err_thread_exit: + thread_exit(); + +err_avb_exit: + genavb_exit(genavb_handle); + +err: + return rc; +} diff --git a/apps/linux/tsn-app/network_only.c b/apps/linux/tsn-app/network_only.c new file mode 100644 index 0000000..a7b370e --- /dev/null +++ b/apps/linux/tsn-app/network_only.c @@ -0,0 +1,95 @@ +/* + * Copyright 2020-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include "../common/log.h" +#include "../common/thread.h" + +#include "network_only.h" +#include "cyclic_task.h" + +void network_only_stats_handler(void *data) +{ + struct network_only_ctx *ctx = data; + struct cyclic_task *c_task = ctx->c_task; + + cyclic_stats_print(c_task); +} + +static void null_loop(void *data, int timer_status) +{ + struct network_only_ctx *ctx = data; + struct cyclic_task *c_task = ctx->c_task; + uint64_t sched_time = tsn_task_get_time(c_task->task); + uint64_t sched_now = tsn_task_get_now(c_task->task); + + if ((sched_now - sched_time) < SCHEDULE_LATENCY_THRESHOLD) + cyclic_net_transmit(c_task, 0, NULL, 0); +} + +struct network_only_ctx *network_only_init(unsigned int role, unsigned int period_ns, unsigned int num_peers, unsigned int timer_type) +{ + struct cyclic_task *c_task = NULL; + struct network_only_ctx *ctx; + + ctx = malloc(sizeof(struct network_only_ctx)); + if (!ctx) { + ERR("malloc() failed\n"); + goto err; + } + + memset(ctx, 0, sizeof(*ctx)); + + c_task = tsn_conf_get_cyclic_task(role); + if (!c_task) { + ERR("tsn_conf_get_cyclic_task() failed\n"); + goto err_free; + } + + cyclic_task_set_period(c_task, period_ns); + +#ifdef TRACE_SNAPSHOT + cyclic_task_init_trace_snapshot(c_task); +#endif + + if (c_task->type == CYCLIC_CONTROLLER) { + if (cyclic_task_set_num_peers(c_task, num_peers) < 0) { + ERR("cyclic_task_set_period() failed\n"); + goto err_free; + } + } + + c_task->params.timer_type = timer_type; + + if (cyclic_task_init(c_task, NULL, null_loop, ctx) < 0) { + ERR("cyclic_task_init() failed\n"); + goto err_free; + } + + ctx->c_task = c_task; + + cyclic_task_start(c_task); + + return ctx; + +err_free: + free(ctx); +err: + return NULL; +} + +void network_only_exit(void *data) +{ + struct network_only_ctx *ctx = data; + + cyclic_task_exit(ctx->c_task); + free(ctx); +} diff --git a/apps/linux/tsn-app/network_only.h b/apps/linux/tsn-app/network_only.h new file mode 100644 index 0000000..ee51a21 --- /dev/null +++ b/apps/linux/tsn-app/network_only.h @@ -0,0 +1,21 @@ +/* + * Copyright 2020-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _NETWORK_ONLY_H_ +#define _NETWORK_ONLY_H_ + +#include +#include + +struct network_only_ctx { + struct cyclic_task *c_task; +}; + +struct network_only_ctx *network_only_init(unsigned int role, unsigned int period_ns, unsigned int num_peers, unsigned int timer_type); +void network_only_exit(void *data); +void network_only_stats_handler(void *data); + +#endif /* _NETWORK_ONLY_H_ */ diff --git a/apps/linux/tsn-app/opcua/model/README.md b/apps/linux/tsn-app/opcua/model/README.md new file mode 100755 index 0000000..d4911dc --- /dev/null +++ b/apps/linux/tsn-app/opcua/model/README.md @@ -0,0 +1,47 @@ +Building Node Sets from Model Design +------------------------------------ +It is recommended to use the precompiled docker container. +Information to use the docker: [build docker](https://github.com/OPCFoundation/UA-ModelCompiler#docker-build) +Copy your model design file inside a new folder. (You can use the example tsn_app_model_design.xml) +Run: +``` +sudo docker run --mount type=bind,source=$(pwd),target=/model/src --entrypoint "/app/PublishModel.sh" sailavid/ua-modelcompiler /model/src/tsn_app_model_design TsnApp /model/src/Published +``` +This command generates several files in your folder and especially: + - Published/TsnApp/TsnApp.NodeSet2.xml + - Published/TsnApp/tsn_app_model_design.csv + +Checking Information Model (optional step) +-------------------------- +When you are coding the model design file, it's useful to check the Information Model. +Without this step, you can only check the Information Model when you connect the Client to the Server. +1) Make sure python 3.6+ and python-pip are installed. +2) Install the FreeOpcUa Modeler. +``` +pip3 install opcua-modeler +``` +3) Run it: +``` +opcua-modeler +``` +4) Click on Actions->open and open TsnApp.NodeSet2.xml (generated in the previous step). + +Generating NodeId Header File +----------------------------- +Clone the git repository for open62541: [open62541](https://github.com/open62541/open62541) +In open62541 folder, run this command with your path to tsn_app_model_design.csv (generated in the first step). +``` +tools/generate_nodeid_header.py /tsn_app_model_design.csv ./tsn_app_ua_nodeid_header 1 +``` + +Generating C Model (which can be used by open62541) +------------------ +In open62541 folder, run the command below with your own path to TsnApp.NodeSet2.xml. +``` +tools/nodeset_compiler/nodeset_compiler.py -e tools/schema/Opc.Ua.NodeSet2.Reduced.xml --xml /TsnApp.NodeSet2.xml tsn_app_model +``` + +Finally, three files are generated in src_generated : + - tsn_app_model.c + - tsn_app_model.h + - tsn_app_ua_nodeid_header.h diff --git a/apps/linux/tsn-app/opcua/model/TsnApp.NodeSet2.xml b/apps/linux/tsn-app/opcua/model/TsnApp.NodeSet2.xml new file mode 100644 index 0000000..bd2a1e0 --- /dev/null +++ b/apps/linux/tsn-app/opcua/model/TsnApp.NodeSet2.xml @@ -0,0 +1,3331 @@ + + + + https://opcua/UA/Tsn/ + + + + + + + + i=1 + i=2 + i=3 + i=4 + i=5 + i=6 + i=7 + i=8 + i=9 + i=10 + i=11 + i=13 + i=12 + i=15 + i=14 + i=16 + i=17 + i=18 + i=20 + i=21 + i=19 + i=22 + i=26 + i=27 + i=28 + i=47 + i=46 + i=35 + i=36 + i=48 + i=45 + i=40 + i=37 + i=38 + i=39 + + + StatsType + Base type for all statistics + + ns=1;i=15002 + ns=1;i=15003 + ns=1;i=15004 + ns=1;i=15005 + ns=1;i=15006 + ns=1;i=15007 + ns=1;i=15008 + i=58 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15001 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15001 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15001 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15001 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15001 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15001 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15001 + + + + HistogramType + Base type for all histograms + + ns=1;i=15010 + ns=1;i=15011 + ns=1;i=15012 + i=58 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15009 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15009 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15009 + + + + ConfigurationType + Base type for the app configuration + + ns=1;i=15014 + ns=1;i=15015 + i=58 + + + + Role + Role of the endpoint : controller or io_device + + i=68 + i=78 + ns=1;i=15013 + + + + NumPeers + Number of peers + + i=68 + i=78 + ns=1;i=15013 + + + + TaskStatsType + Statistics of the task + + ns=1;i=15017 + ns=1;i=15018 + ns=1;i=15019 + ns=1;i=15020 + ns=1;i=15021 + ns=1;i=15022 + ns=1;i=15023 + ns=1;i=15024 + ns=1;i=15032 + ns=1;i=15036 + ns=1;i=15044 + ns=1;i=15048 + ns=1;i=15056 + i=58 + + + + Sched + Sched + + i=63 + i=78 + ns=1;i=15016 + + + + SchedEarly + SchedEarly + + i=63 + i=78 + ns=1;i=15016 + + + + SchedLate + SchedLate + + i=63 + i=78 + ns=1;i=15016 + + + + SchedMissed + SchedMissed + + i=63 + i=78 + ns=1;i=15016 + + + + SchedTimeout + SchedTimeout + + i=63 + i=78 + ns=1;i=15016 + + + + ClockDiscount + ClockDiscount + + i=63 + i=78 + ns=1;i=15016 + + + + ClockErr + ClockErr + + i=63 + i=78 + ns=1;i=15016 + + + + SchedErrStats + SchedErrStats + + ns=1;i=15025 + ns=1;i=15026 + ns=1;i=15027 + ns=1;i=15028 + ns=1;i=15029 + ns=1;i=15030 + ns=1;i=15031 + ns=1;i=15001 + i=78 + ns=1;i=15016 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15024 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15024 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15024 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15024 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15024 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15024 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15024 + + + + SchedErrHisto + SchedErrHisto + + ns=1;i=15033 + ns=1;i=15034 + ns=1;i=15035 + ns=1;i=15009 + i=78 + ns=1;i=15016 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15032 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15032 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15032 + + + + ProcTimeStats + Processing time statistics + + ns=1;i=15037 + ns=1;i=15038 + ns=1;i=15039 + ns=1;i=15040 + ns=1;i=15041 + ns=1;i=15042 + ns=1;i=15043 + ns=1;i=15001 + i=78 + ns=1;i=15016 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15036 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15036 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15036 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15036 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15036 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15036 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15036 + + + + ProcTimeHisto + Processing time histogram + + ns=1;i=15045 + ns=1;i=15046 + ns=1;i=15047 + ns=1;i=15009 + i=78 + ns=1;i=15016 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15044 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15044 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15044 + + + + TotalTimeStats + Total time statistics + + ns=1;i=15049 + ns=1;i=15050 + ns=1;i=15051 + ns=1;i=15052 + ns=1;i=15053 + ns=1;i=15054 + ns=1;i=15055 + ns=1;i=15001 + i=78 + ns=1;i=15016 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15048 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15048 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15048 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15048 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15048 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15048 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15048 + + + + TotalTimeHisto + Total time histogram + + ns=1;i=15057 + ns=1;i=15058 + ns=1;i=15059 + ns=1;i=15009 + i=78 + ns=1;i=15016 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15056 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15056 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15056 + + + + CyclicRxSocketType + Application level socket + + ns=1;i=15061 + ns=1;i=15062 + ns=1;i=15063 + ns=1;i=15064 + ns=1;i=15065 + ns=1;i=15066 + ns=1;i=15067 + ns=1;i=15075 + i=58 + + + + PeerId + Peer identifier + + i=63 + i=78 + ns=1;i=15060 + + + + ValidFrames + Number of valid frames + + i=63 + i=78 + ns=1;i=15060 + + + + ErrId + Error parameter + + i=63 + i=78 + ns=1;i=15060 + + + + ErrTs + Error parameter + + i=63 + i=78 + ns=1;i=15060 + + + + ErrUnderflow + Error parameter + + i=63 + i=78 + ns=1;i=15060 + + + + Link + Link up or down + + i=63 + i=78 + ns=1;i=15060 + + + + TrafficLatencyStats + Traffic latency statistics + + ns=1;i=15068 + ns=1;i=15069 + ns=1;i=15070 + ns=1;i=15071 + ns=1;i=15072 + ns=1;i=15073 + ns=1;i=15074 + ns=1;i=15001 + i=78 + ns=1;i=15060 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15067 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15067 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15067 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15067 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15067 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15067 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15067 + + + + TrafficLatencyHisto + Traffic latency histogram + + ns=1;i=15076 + ns=1;i=15077 + ns=1;i=15078 + ns=1;i=15009 + i=78 + ns=1;i=15060 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15075 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15075 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15075 + + + + NetworkSocketType + Frames from low-level network socket + + ns=1;i=15080 + ns=1;i=15081 + ns=1;i=15082 + ns=1;i=15083 + i=58 + + + + Direction + Rx or tx + + i=63 + i=78 + ns=1;i=15079 + + + + Id + Identifier + + i=63 + i=78 + ns=1;i=15079 + + + + Frames + Frames number + + i=63 + i=78 + ns=1;i=15079 + + + + FramesErr + Frames errors number + + i=63 + i=78 + ns=1;i=15079 + + + + SocketStatsType + Base type for all the sockets + + ns=1;i=15085 + ns=1;i=15104 + ns=1;i=15123 + ns=1;i=15128 + ns=1;i=15133 + i=58 + + + + CyclicRxSocket0 + CyclicRxSocket0 + + ns=1;i=15086 + ns=1;i=15087 + ns=1;i=15088 + ns=1;i=15089 + ns=1;i=15090 + ns=1;i=15091 + ns=1;i=15092 + ns=1;i=15100 + ns=1;i=15060 + i=78 + ns=1;i=15084 + + + + PeerId + Peer identifier + + i=63 + i=78 + ns=1;i=15085 + + + + ValidFrames + Number of valid frames + + i=63 + i=78 + ns=1;i=15085 + + + + ErrId + Error parameter + + i=63 + i=78 + ns=1;i=15085 + + + + ErrTs + Error parameter + + i=63 + i=78 + ns=1;i=15085 + + + + ErrUnderflow + Error parameter + + i=63 + i=78 + ns=1;i=15085 + + + + Link + Link up or down + + i=63 + i=78 + ns=1;i=15085 + + + + TrafficLatencyStats + Traffic latency statistics + + ns=1;i=15093 + ns=1;i=15094 + ns=1;i=15095 + ns=1;i=15096 + ns=1;i=15097 + ns=1;i=15098 + ns=1;i=15099 + ns=1;i=15001 + i=78 + ns=1;i=15085 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15092 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15092 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15092 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15092 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15092 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15092 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15092 + + + + TrafficLatencyHisto + Traffic latency histogram + + ns=1;i=15101 + ns=1;i=15102 + ns=1;i=15103 + ns=1;i=15009 + i=78 + ns=1;i=15085 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15100 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15100 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15100 + + + + CyclicRxSocket1 + CyclicRxSocket1 + + ns=1;i=15105 + ns=1;i=15106 + ns=1;i=15107 + ns=1;i=15108 + ns=1;i=15109 + ns=1;i=15110 + ns=1;i=15111 + ns=1;i=15119 + ns=1;i=15060 + i=78 + ns=1;i=15084 + + + + PeerId + Peer identifier + + i=63 + i=78 + ns=1;i=15104 + + + + ValidFrames + Number of valid frames + + i=63 + i=78 + ns=1;i=15104 + + + + ErrId + Error parameter + + i=63 + i=78 + ns=1;i=15104 + + + + ErrTs + Error parameter + + i=63 + i=78 + ns=1;i=15104 + + + + ErrUnderflow + Error parameter + + i=63 + i=78 + ns=1;i=15104 + + + + Link + Link up or down + + i=63 + i=78 + ns=1;i=15104 + + + + TrafficLatencyStats + Traffic latency statistics + + ns=1;i=15112 + ns=1;i=15113 + ns=1;i=15114 + ns=1;i=15115 + ns=1;i=15116 + ns=1;i=15117 + ns=1;i=15118 + ns=1;i=15001 + i=78 + ns=1;i=15104 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15111 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15111 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15111 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15111 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15111 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15111 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15111 + + + + TrafficLatencyHisto + Traffic latency histogram + + ns=1;i=15120 + ns=1;i=15121 + ns=1;i=15122 + ns=1;i=15009 + i=78 + ns=1;i=15104 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15119 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15119 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15119 + + + + NetRxSocket0 + NetRxSocket0 + + ns=1;i=15124 + ns=1;i=15125 + ns=1;i=15126 + ns=1;i=15127 + ns=1;i=15079 + i=78 + ns=1;i=15084 + + + + Direction + Rx or tx + + i=63 + i=78 + ns=1;i=15123 + + + + Id + Identifier + + i=63 + i=78 + ns=1;i=15123 + + + + Frames + Frames number + + i=63 + i=78 + ns=1;i=15123 + + + + FramesErr + Frames errors number + + i=63 + i=78 + ns=1;i=15123 + + + + NetRxSocket1 + NetRxSocket1 + + ns=1;i=15129 + ns=1;i=15130 + ns=1;i=15131 + ns=1;i=15132 + ns=1;i=15079 + i=78 + ns=1;i=15084 + + + + Direction + Rx or tx + + i=63 + i=78 + ns=1;i=15128 + + + + Id + Identifier + + i=63 + i=78 + ns=1;i=15128 + + + + Frames + Frames number + + i=63 + i=78 + ns=1;i=15128 + + + + FramesErr + Frames errors number + + i=63 + i=78 + ns=1;i=15128 + + + + NetTxSocket0 + NetTxSocket0 + + ns=1;i=15134 + ns=1;i=15135 + ns=1;i=15136 + ns=1;i=15137 + ns=1;i=15079 + i=78 + ns=1;i=15084 + + + + Direction + Rx or tx + + i=63 + i=78 + ns=1;i=15133 + + + + Id + Identifier + + i=63 + i=78 + ns=1;i=15133 + + + + Frames + Frames number + + i=63 + i=78 + ns=1;i=15133 + + + + FramesErr + Frames errors number + + i=63 + i=78 + ns=1;i=15133 + + + + TsnAppType + Base type for tsn_app + + ns=1;i=15139 + ns=1;i=15142 + ns=1;i=15196 + i=58 + + + + Configuration + Configuration for the app + + ns=1;i=15140 + ns=1;i=15141 + ns=1;i=15013 + i=78 + ns=1;i=15138 + + + + Role + Role of the endpoint : controller or io_device + + i=68 + i=78 + ns=1;i=15139 + + + + NumPeers + Number of peers + + i=68 + i=78 + ns=1;i=15139 + + + + SocketStats + Statistics for the sockets + + ns=1;i=15143 + ns=1;i=15162 + ns=1;i=15181 + ns=1;i=15186 + ns=1;i=15191 + ns=1;i=15084 + i=78 + ns=1;i=15138 + + + + CyclicRxSocket0 + CyclicRxSocket0 + + ns=1;i=15144 + ns=1;i=15145 + ns=1;i=15146 + ns=1;i=15147 + ns=1;i=15148 + ns=1;i=15149 + ns=1;i=15150 + ns=1;i=15158 + ns=1;i=15060 + i=78 + ns=1;i=15142 + + + + PeerId + Peer identifier + + i=63 + i=78 + ns=1;i=15143 + + + + ValidFrames + Number of valid frames + + i=63 + i=78 + ns=1;i=15143 + + + + ErrId + Error parameter + + i=63 + i=78 + ns=1;i=15143 + + + + ErrTs + Error parameter + + i=63 + i=78 + ns=1;i=15143 + + + + ErrUnderflow + Error parameter + + i=63 + i=78 + ns=1;i=15143 + + + + Link + Link up or down + + i=63 + i=78 + ns=1;i=15143 + + + + TrafficLatencyStats + Traffic latency statistics + + ns=1;i=15151 + ns=1;i=15152 + ns=1;i=15153 + ns=1;i=15154 + ns=1;i=15155 + ns=1;i=15156 + ns=1;i=15157 + ns=1;i=15001 + i=78 + ns=1;i=15143 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15150 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15150 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15150 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15150 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15150 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15150 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15150 + + + + TrafficLatencyHisto + Traffic latency histogram + + ns=1;i=15159 + ns=1;i=15160 + ns=1;i=15161 + ns=1;i=15009 + i=78 + ns=1;i=15143 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15158 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15158 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15158 + + + + CyclicRxSocket1 + CyclicRxSocket1 + + ns=1;i=15163 + ns=1;i=15164 + ns=1;i=15165 + ns=1;i=15166 + ns=1;i=15167 + ns=1;i=15168 + ns=1;i=15169 + ns=1;i=15177 + ns=1;i=15060 + i=78 + ns=1;i=15142 + + + + PeerId + Peer identifier + + i=63 + i=78 + ns=1;i=15162 + + + + ValidFrames + Number of valid frames + + i=63 + i=78 + ns=1;i=15162 + + + + ErrId + Error parameter + + i=63 + i=78 + ns=1;i=15162 + + + + ErrTs + Error parameter + + i=63 + i=78 + ns=1;i=15162 + + + + ErrUnderflow + Error parameter + + i=63 + i=78 + ns=1;i=15162 + + + + Link + Link up or down + + i=63 + i=78 + ns=1;i=15162 + + + + TrafficLatencyStats + Traffic latency statistics + + ns=1;i=15170 + ns=1;i=15171 + ns=1;i=15172 + ns=1;i=15173 + ns=1;i=15174 + ns=1;i=15175 + ns=1;i=15176 + ns=1;i=15001 + i=78 + ns=1;i=15162 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15169 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15169 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15169 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15169 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15169 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15169 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15169 + + + + TrafficLatencyHisto + Traffic latency histogram + + ns=1;i=15178 + ns=1;i=15179 + ns=1;i=15180 + ns=1;i=15009 + i=78 + ns=1;i=15162 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15177 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15177 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15177 + + + + NetRxSocket0 + NetRxSocket0 + + ns=1;i=15182 + ns=1;i=15183 + ns=1;i=15184 + ns=1;i=15185 + ns=1;i=15079 + i=78 + ns=1;i=15142 + + + + Direction + Rx or tx + + i=63 + i=78 + ns=1;i=15181 + + + + Id + Identifier + + i=63 + i=78 + ns=1;i=15181 + + + + Frames + Frames number + + i=63 + i=78 + ns=1;i=15181 + + + + FramesErr + Frames errors number + + i=63 + i=78 + ns=1;i=15181 + + + + NetRxSocket1 + NetRxSocket1 + + ns=1;i=15187 + ns=1;i=15188 + ns=1;i=15189 + ns=1;i=15190 + ns=1;i=15079 + i=78 + ns=1;i=15142 + + + + Direction + Rx or tx + + i=63 + i=78 + ns=1;i=15186 + + + + Id + Identifier + + i=63 + i=78 + ns=1;i=15186 + + + + Frames + Frames number + + i=63 + i=78 + ns=1;i=15186 + + + + FramesErr + Frames errors number + + i=63 + i=78 + ns=1;i=15186 + + + + NetTxSocket0 + NetTxSocket0 + + ns=1;i=15192 + ns=1;i=15193 + ns=1;i=15194 + ns=1;i=15195 + ns=1;i=15079 + i=78 + ns=1;i=15142 + + + + Direction + Rx or tx + + i=63 + i=78 + ns=1;i=15191 + + + + Id + Identifier + + i=63 + i=78 + ns=1;i=15191 + + + + Frames + Frames number + + i=63 + i=78 + ns=1;i=15191 + + + + FramesErr + Frames errors number + + i=63 + i=78 + ns=1;i=15191 + + + + TaskStats + Statistics + + ns=1;i=15197 + ns=1;i=15198 + ns=1;i=15199 + ns=1;i=15200 + ns=1;i=15201 + ns=1;i=15202 + ns=1;i=15203 + ns=1;i=15204 + ns=1;i=15212 + ns=1;i=15216 + ns=1;i=15224 + ns=1;i=15228 + ns=1;i=15236 + ns=1;i=15016 + i=78 + ns=1;i=15138 + + + + Sched + Sched + + i=63 + i=78 + ns=1;i=15196 + + + + SchedEarly + SchedEarly + + i=63 + i=78 + ns=1;i=15196 + + + + SchedLate + SchedLate + + i=63 + i=78 + ns=1;i=15196 + + + + SchedMissed + SchedMissed + + i=63 + i=78 + ns=1;i=15196 + + + + SchedTimeout + SchedTimeout + + i=63 + i=78 + ns=1;i=15196 + + + + ClockDiscount + ClockDiscount + + i=63 + i=78 + ns=1;i=15196 + + + + ClockErr + ClockErr + + i=63 + i=78 + ns=1;i=15196 + + + + SchedErrStats + SchedErrStats + + ns=1;i=15205 + ns=1;i=15206 + ns=1;i=15207 + ns=1;i=15208 + ns=1;i=15209 + ns=1;i=15210 + ns=1;i=15211 + ns=1;i=15001 + i=78 + ns=1;i=15196 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15204 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15204 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15204 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15204 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15204 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15204 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15204 + + + + SchedErrHisto + SchedErrHisto + + ns=1;i=15213 + ns=1;i=15214 + ns=1;i=15215 + ns=1;i=15009 + i=78 + ns=1;i=15196 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15212 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15212 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15212 + + + + ProcTimeStats + Processing time statistics + + ns=1;i=15217 + ns=1;i=15218 + ns=1;i=15219 + ns=1;i=15220 + ns=1;i=15221 + ns=1;i=15222 + ns=1;i=15223 + ns=1;i=15001 + i=78 + ns=1;i=15196 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15216 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15216 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15216 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15216 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15216 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15216 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15216 + + + + ProcTimeHisto + Processing time histogram + + ns=1;i=15225 + ns=1;i=15226 + ns=1;i=15227 + ns=1;i=15009 + i=78 + ns=1;i=15196 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15224 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15224 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15224 + + + + TotalTimeStats + Total time statistics + + ns=1;i=15229 + ns=1;i=15230 + ns=1;i=15231 + ns=1;i=15232 + ns=1;i=15233 + ns=1;i=15234 + ns=1;i=15235 + ns=1;i=15001 + i=78 + ns=1;i=15196 + + + + Min + Minimum of the values + + i=63 + i=78 + ns=1;i=15228 + + + + Mean + Mean of the values + + i=63 + i=78 + ns=1;i=15228 + + + + Max + Maximum of the values + + i=63 + i=78 + ns=1;i=15228 + + + + AbsMax + Absolute maximum of the values + + i=63 + i=78 + ns=1;i=15228 + + + + AbsMin + Absolute minimum of the values + + i=63 + i=78 + ns=1;i=15228 + + + + Ms + Mean square of the values + + i=63 + i=78 + ns=1;i=15228 + + + + Variance + Variance of the values + + i=63 + i=78 + ns=1;i=15228 + + + + TotalTimeHisto + Total time histogram + + ns=1;i=15237 + ns=1;i=15238 + ns=1;i=15239 + ns=1;i=15009 + i=78 + ns=1;i=15196 + + + + NSlots + Number of slots + + i=63 + i=78 + ns=1;i=15236 + + + + SlotSize + Slot size + + i=63 + i=78 + ns=1;i=15236 + + + + Slots + Array for the repartition of the values + + i=63 + i=78 + ns=1;i=15236 + + + + TsnApp + TsnApp object + + ns=1;i=15241 + ns=1;i=15244 + ns=1;i=15298 + i=85 + ns=1;i=15138 + + + + Configuration + Configuration for the app + + ns=1;i=15242 + ns=1;i=15243 + ns=1;i=15013 + ns=1;i=15240 + + + + Role + Role of the endpoint : controller or io_device + + i=68 + ns=1;i=15241 + + + + NumPeers + Number of peers + + i=68 + ns=1;i=15241 + + + + SocketStats + Statistics for the sockets + + ns=1;i=15245 + ns=1;i=15264 + ns=1;i=15283 + ns=1;i=15288 + ns=1;i=15293 + ns=1;i=15084 + ns=1;i=15240 + + + + CyclicRxSocket0 + CyclicRxSocket0 + + ns=1;i=15246 + ns=1;i=15247 + ns=1;i=15248 + ns=1;i=15249 + ns=1;i=15250 + ns=1;i=15251 + ns=1;i=15252 + ns=1;i=15260 + ns=1;i=15060 + ns=1;i=15244 + + + + PeerId + Peer identifier + + i=63 + ns=1;i=15245 + + + + ValidFrames + Number of valid frames + + i=63 + ns=1;i=15245 + + + + ErrId + Error parameter + + i=63 + ns=1;i=15245 + + + + ErrTs + Error parameter + + i=63 + ns=1;i=15245 + + + + ErrUnderflow + Error parameter + + i=63 + ns=1;i=15245 + + + + Link + Link up or down + + i=63 + ns=1;i=15245 + + + + TrafficLatencyStats + Traffic latency statistics + + ns=1;i=15253 + ns=1;i=15254 + ns=1;i=15255 + ns=1;i=15256 + ns=1;i=15257 + ns=1;i=15258 + ns=1;i=15259 + ns=1;i=15001 + ns=1;i=15245 + + + + Min + Minimum of the values + + i=63 + ns=1;i=15252 + + + + Mean + Mean of the values + + i=63 + ns=1;i=15252 + + + + Max + Maximum of the values + + i=63 + ns=1;i=15252 + + + + AbsMax + Absolute maximum of the values + + i=63 + ns=1;i=15252 + + + + AbsMin + Absolute minimum of the values + + i=63 + ns=1;i=15252 + + + + Ms + Mean square of the values + + i=63 + ns=1;i=15252 + + + + Variance + Variance of the values + + i=63 + ns=1;i=15252 + + + + TrafficLatencyHisto + Traffic latency histogram + + ns=1;i=15261 + ns=1;i=15262 + ns=1;i=15263 + ns=1;i=15009 + ns=1;i=15245 + + + + NSlots + Number of slots + + i=63 + ns=1;i=15260 + + + + SlotSize + Slot size + + i=63 + ns=1;i=15260 + + + + Slots + Array for the repartition of the values + + i=63 + ns=1;i=15260 + + + + CyclicRxSocket1 + CyclicRxSocket1 + + ns=1;i=15265 + ns=1;i=15266 + ns=1;i=15267 + ns=1;i=15268 + ns=1;i=15269 + ns=1;i=15270 + ns=1;i=15271 + ns=1;i=15279 + ns=1;i=15060 + ns=1;i=15244 + + + + PeerId + Peer identifier + + i=63 + ns=1;i=15264 + + + + ValidFrames + Number of valid frames + + i=63 + ns=1;i=15264 + + + + ErrId + Error parameter + + i=63 + ns=1;i=15264 + + + + ErrTs + Error parameter + + i=63 + ns=1;i=15264 + + + + ErrUnderflow + Error parameter + + i=63 + ns=1;i=15264 + + + + Link + Link up or down + + i=63 + ns=1;i=15264 + + + + TrafficLatencyStats + Traffic latency statistics + + ns=1;i=15272 + ns=1;i=15273 + ns=1;i=15274 + ns=1;i=15275 + ns=1;i=15276 + ns=1;i=15277 + ns=1;i=15278 + ns=1;i=15001 + ns=1;i=15264 + + + + Min + Minimum of the values + + i=63 + ns=1;i=15271 + + + + Mean + Mean of the values + + i=63 + ns=1;i=15271 + + + + Max + Maximum of the values + + i=63 + ns=1;i=15271 + + + + AbsMax + Absolute maximum of the values + + i=63 + ns=1;i=15271 + + + + AbsMin + Absolute minimum of the values + + i=63 + ns=1;i=15271 + + + + Ms + Mean square of the values + + i=63 + ns=1;i=15271 + + + + Variance + Variance of the values + + i=63 + ns=1;i=15271 + + + + TrafficLatencyHisto + Traffic latency histogram + + ns=1;i=15280 + ns=1;i=15281 + ns=1;i=15282 + ns=1;i=15009 + ns=1;i=15264 + + + + NSlots + Number of slots + + i=63 + ns=1;i=15279 + + + + SlotSize + Slot size + + i=63 + ns=1;i=15279 + + + + Slots + Array for the repartition of the values + + i=63 + ns=1;i=15279 + + + + NetRxSocket0 + NetRxSocket0 + + ns=1;i=15284 + ns=1;i=15285 + ns=1;i=15286 + ns=1;i=15287 + ns=1;i=15079 + ns=1;i=15244 + + + + Direction + Rx or tx + + i=63 + ns=1;i=15283 + + + + Id + Identifier + + i=63 + ns=1;i=15283 + + + + Frames + Frames number + + i=63 + ns=1;i=15283 + + + + FramesErr + Frames errors number + + i=63 + ns=1;i=15283 + + + + NetRxSocket1 + NetRxSocket1 + + ns=1;i=15289 + ns=1;i=15290 + ns=1;i=15291 + ns=1;i=15292 + ns=1;i=15079 + ns=1;i=15244 + + + + Direction + Rx or tx + + i=63 + ns=1;i=15288 + + + + Id + Identifier + + i=63 + ns=1;i=15288 + + + + Frames + Frames number + + i=63 + ns=1;i=15288 + + + + FramesErr + Frames errors number + + i=63 + ns=1;i=15288 + + + + NetTxSocket0 + NetTxSocket0 + + ns=1;i=15294 + ns=1;i=15295 + ns=1;i=15296 + ns=1;i=15297 + ns=1;i=15079 + ns=1;i=15244 + + + + Direction + Rx or tx + + i=63 + ns=1;i=15293 + + + + Id + Identifier + + i=63 + ns=1;i=15293 + + + + Frames + Frames number + + i=63 + ns=1;i=15293 + + + + FramesErr + Frames errors number + + i=63 + ns=1;i=15293 + + + + TaskStats + Statistics + + ns=1;i=15299 + ns=1;i=15300 + ns=1;i=15301 + ns=1;i=15302 + ns=1;i=15303 + ns=1;i=15304 + ns=1;i=15305 + ns=1;i=15306 + ns=1;i=15314 + ns=1;i=15318 + ns=1;i=15326 + ns=1;i=15330 + ns=1;i=15338 + ns=1;i=15016 + ns=1;i=15240 + + + + Sched + Sched + + i=63 + ns=1;i=15298 + + + + SchedEarly + SchedEarly + + i=63 + ns=1;i=15298 + + + + SchedLate + SchedLate + + i=63 + ns=1;i=15298 + + + + SchedMissed + SchedMissed + + i=63 + ns=1;i=15298 + + + + SchedTimeout + SchedTimeout + + i=63 + ns=1;i=15298 + + + + ClockDiscount + ClockDiscount + + i=63 + ns=1;i=15298 + + + + ClockErr + ClockErr + + i=63 + ns=1;i=15298 + + + + SchedErrStats + SchedErrStats + + ns=1;i=15307 + ns=1;i=15308 + ns=1;i=15309 + ns=1;i=15310 + ns=1;i=15311 + ns=1;i=15312 + ns=1;i=15313 + ns=1;i=15001 + ns=1;i=15298 + + + + Min + Minimum of the values + + i=63 + ns=1;i=15306 + + + + Mean + Mean of the values + + i=63 + ns=1;i=15306 + + + + Max + Maximum of the values + + i=63 + ns=1;i=15306 + + + + AbsMax + Absolute maximum of the values + + i=63 + ns=1;i=15306 + + + + AbsMin + Absolute minimum of the values + + i=63 + ns=1;i=15306 + + + + Ms + Mean square of the values + + i=63 + ns=1;i=15306 + + + + Variance + Variance of the values + + i=63 + ns=1;i=15306 + + + + SchedErrHisto + SchedErrHisto + + ns=1;i=15315 + ns=1;i=15316 + ns=1;i=15317 + ns=1;i=15009 + ns=1;i=15298 + + + + NSlots + Number of slots + + i=63 + ns=1;i=15314 + + + + SlotSize + Slot size + + i=63 + ns=1;i=15314 + + + + Slots + Array for the repartition of the values + + i=63 + ns=1;i=15314 + + + + ProcTimeStats + Processing time statistics + + ns=1;i=15319 + ns=1;i=15320 + ns=1;i=15321 + ns=1;i=15322 + ns=1;i=15323 + ns=1;i=15324 + ns=1;i=15325 + ns=1;i=15001 + ns=1;i=15298 + + + + Min + Minimum of the values + + i=63 + ns=1;i=15318 + + + + Mean + Mean of the values + + i=63 + ns=1;i=15318 + + + + Max + Maximum of the values + + i=63 + ns=1;i=15318 + + + + AbsMax + Absolute maximum of the values + + i=63 + ns=1;i=15318 + + + + AbsMin + Absolute minimum of the values + + i=63 + ns=1;i=15318 + + + + Ms + Mean square of the values + + i=63 + ns=1;i=15318 + + + + Variance + Variance of the values + + i=63 + ns=1;i=15318 + + + + ProcTimeHisto + Processing time histogram + + ns=1;i=15327 + ns=1;i=15328 + ns=1;i=15329 + ns=1;i=15009 + ns=1;i=15298 + + + + NSlots + Number of slots + + i=63 + ns=1;i=15326 + + + + SlotSize + Slot size + + i=63 + ns=1;i=15326 + + + + Slots + Array for the repartition of the values + + i=63 + ns=1;i=15326 + + + + TotalTimeStats + Total time statistics + + ns=1;i=15331 + ns=1;i=15332 + ns=1;i=15333 + ns=1;i=15334 + ns=1;i=15335 + ns=1;i=15336 + ns=1;i=15337 + ns=1;i=15001 + ns=1;i=15298 + + + + Min + Minimum of the values + + i=63 + ns=1;i=15330 + + + + Mean + Mean of the values + + i=63 + ns=1;i=15330 + + + + Max + Maximum of the values + + i=63 + ns=1;i=15330 + + + + AbsMax + Absolute maximum of the values + + i=63 + ns=1;i=15330 + + + + AbsMin + Absolute minimum of the values + + i=63 + ns=1;i=15330 + + + + Ms + Mean square of the values + + i=63 + ns=1;i=15330 + + + + Variance + Variance of the values + + i=63 + ns=1;i=15330 + + + + TotalTimeHisto + Total time histogram + + ns=1;i=15339 + ns=1;i=15340 + ns=1;i=15341 + ns=1;i=15009 + ns=1;i=15298 + + + + NSlots + Number of slots + + i=63 + ns=1;i=15338 + + + + SlotSize + Slot size + + i=63 + ns=1;i=15338 + + + + Slots + Array for the repartition of the values + + i=63 + ns=1;i=15338 + + + \ No newline at end of file diff --git a/apps/linux/tsn-app/opcua/model/tsn_app_model.c b/apps/linux/tsn-app/opcua/model/tsn_app_model.c new file mode 100644 index 0000000..3e92bef --- /dev/null +++ b/apps/linux/tsn-app/opcua/model/tsn_app_model.c @@ -0,0 +1,10735 @@ +/* + * Copyright 2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + * + * WARNING: This is a generated file. + * Any manual changes will be overwritten. */ + +#include "tsn_app_model.h" + +/* TsnAppType - ns=1;i=15138 */ + +static UA_StatusCode function_tsn_app_model_0_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TsnAppType"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Base type for tsn_app"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECTTYPE, UA_NODEID_NUMERIC(ns[1], 15138LU), + UA_NODEID_NUMERIC(ns[0], 58LU), UA_NODEID_NUMERIC(ns[0], 45LU), + UA_QUALIFIEDNAME(ns[1], "TsnAppType"), UA_NODEID_NULL, + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_0_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15138LU)); +} + +/* TsnApp - ns=1;i=15240 */ + +static UA_StatusCode function_tsn_app_model_1_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.eventNotifier = true; + attr.displayName = UA_LOCALIZEDTEXT("", "TsnApp"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "TsnApp object"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15240LU), + UA_NODEID_NUMERIC(ns[0], 85LU), UA_NODEID_NUMERIC(ns[0], 35LU), + UA_QUALIFIEDNAME(ns[1], "TsnApp"), UA_NODEID_NUMERIC(ns[1], 15138LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_1_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15240LU)); +} + +/* SocketStatsType - ns=1;i=15084 */ + +static UA_StatusCode function_tsn_app_model_2_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SocketStatsType"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Base type for all the sockets"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECTTYPE, UA_NODEID_NUMERIC(ns[1], 15084LU), + UA_NODEID_NUMERIC(ns[0], 58LU), UA_NODEID_NUMERIC(ns[0], 45LU), + UA_QUALIFIEDNAME(ns[1], "SocketStatsType"), UA_NODEID_NULL, + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_2_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15084LU)); +} + +/* SocketStats - ns=1;i=15244 */ + +static UA_StatusCode function_tsn_app_model_3_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SocketStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Statistics for the sockets"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15244LU), + UA_NODEID_NUMERIC(ns[1], 15240LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SocketStats"), UA_NODEID_NUMERIC(ns[1], 15084LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_3_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15244LU)); +} + +/* SocketStats - ns=1;i=15142 */ + +static UA_StatusCode function_tsn_app_model_4_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SocketStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Statistics for the sockets"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15142LU), + UA_NODEID_NUMERIC(ns[1], 15138LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SocketStats"), UA_NODEID_NUMERIC(ns[1], 15084LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15142LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_4_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15142LU)); +} + +/* NetworkSocketType - ns=1;i=15079 */ + +static UA_StatusCode function_tsn_app_model_5_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetworkSocketType"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames from low-level network socket"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECTTYPE, UA_NODEID_NUMERIC(ns[1], 15079LU), + UA_NODEID_NUMERIC(ns[0], 58LU), UA_NODEID_NUMERIC(ns[0], 45LU), + UA_QUALIFIEDNAME(ns[1], "NetworkSocketType"), UA_NODEID_NULL, + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_5_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15079LU)); +} + +/* Direction - ns=1;i=15080 */ + +static UA_StatusCode function_tsn_app_model_6_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15080LU), + UA_NODEID_NUMERIC(ns[1], 15079LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15080LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_6_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15080LU)); +} + +/* NetRxSocket1 - ns=1;i=15128 */ + +static UA_StatusCode function_tsn_app_model_7_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetRxSocket1"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetRxSocket1"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15128LU), + UA_NODEID_NUMERIC(ns[1], 15084LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetRxSocket1"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15128LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_7_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15128LU)); +} + +/* FramesErr - ns=1;i=15132 */ + +static UA_StatusCode function_tsn_app_model_8_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15132LU), + UA_NODEID_NUMERIC(ns[1], 15128LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15132LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_8_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15132LU)); +} + +/* Id - ns=1;i=15130 */ + +static UA_StatusCode function_tsn_app_model_9_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15130LU), + UA_NODEID_NUMERIC(ns[1], 15128LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15130LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_9_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15130LU)); +} + +/* Frames - ns=1;i=15131 */ + +static UA_StatusCode function_tsn_app_model_10_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15131LU), + UA_NODEID_NUMERIC(ns[1], 15128LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15131LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_10_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15131LU)); +} + +/* Direction - ns=1;i=15129 */ + +static UA_StatusCode function_tsn_app_model_11_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15129LU), + UA_NODEID_NUMERIC(ns[1], 15128LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15129LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_11_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15129LU)); +} + +/* Frames - ns=1;i=15082 */ + +static UA_StatusCode function_tsn_app_model_12_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15082LU), + UA_NODEID_NUMERIC(ns[1], 15079LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15082LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_12_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15082LU)); +} + +/* NetRxSocket1 - ns=1;i=15288 */ + +static UA_StatusCode function_tsn_app_model_13_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetRxSocket1"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetRxSocket1"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15288LU), + UA_NODEID_NUMERIC(ns[1], 15244LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetRxSocket1"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_13_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15288LU)); +} + +/* Id - ns=1;i=15290 */ + +static UA_StatusCode function_tsn_app_model_14_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15290LU), + UA_NODEID_NUMERIC(ns[1], 15288LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_14_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15290LU)); +} + +/* Direction - ns=1;i=15289 */ + +static UA_StatusCode function_tsn_app_model_15_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15289LU), + UA_NODEID_NUMERIC(ns[1], 15288LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_15_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15289LU)); +} + +/* Frames - ns=1;i=15291 */ + +static UA_StatusCode function_tsn_app_model_16_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15291LU), + UA_NODEID_NUMERIC(ns[1], 15288LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_16_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15291LU)); +} + +/* FramesErr - ns=1;i=15292 */ + +static UA_StatusCode function_tsn_app_model_17_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15292LU), + UA_NODEID_NUMERIC(ns[1], 15288LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_17_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15292LU)); +} + +/* NetRxSocket1 - ns=1;i=15186 */ + +static UA_StatusCode function_tsn_app_model_18_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetRxSocket1"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetRxSocket1"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15186LU), + UA_NODEID_NUMERIC(ns[1], 15142LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetRxSocket1"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15186LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_18_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15186LU)); +} + +/* FramesErr - ns=1;i=15190 */ + +static UA_StatusCode function_tsn_app_model_19_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15190LU), + UA_NODEID_NUMERIC(ns[1], 15186LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15190LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_19_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15190LU)); +} + +/* Id - ns=1;i=15188 */ + +static UA_StatusCode function_tsn_app_model_20_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15188LU), + UA_NODEID_NUMERIC(ns[1], 15186LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15188LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_20_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15188LU)); +} + +/* Direction - ns=1;i=15187 */ + +static UA_StatusCode function_tsn_app_model_21_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15187LU), + UA_NODEID_NUMERIC(ns[1], 15186LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15187LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_21_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15187LU)); +} + +/* Frames - ns=1;i=15189 */ + +static UA_StatusCode function_tsn_app_model_22_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15189LU), + UA_NODEID_NUMERIC(ns[1], 15186LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15189LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_22_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15189LU)); +} + +/* NetRxSocket0 - ns=1;i=15123 */ + +static UA_StatusCode function_tsn_app_model_23_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetRxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetRxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15123LU), + UA_NODEID_NUMERIC(ns[1], 15084LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetRxSocket0"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15123LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_23_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15123LU)); +} + +/* Direction - ns=1;i=15124 */ + +static UA_StatusCode function_tsn_app_model_24_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15124LU), + UA_NODEID_NUMERIC(ns[1], 15123LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15124LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_24_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15124LU)); +} + +/* FramesErr - ns=1;i=15127 */ + +static UA_StatusCode function_tsn_app_model_25_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15127LU), + UA_NODEID_NUMERIC(ns[1], 15123LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15127LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_25_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15127LU)); +} + +/* Id - ns=1;i=15125 */ + +static UA_StatusCode function_tsn_app_model_26_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15125LU), + UA_NODEID_NUMERIC(ns[1], 15123LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15125LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_26_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15125LU)); +} + +/* Frames - ns=1;i=15126 */ + +static UA_StatusCode function_tsn_app_model_27_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15126LU), + UA_NODEID_NUMERIC(ns[1], 15123LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15126LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_27_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15126LU)); +} + +/* NetTxSocket0 - ns=1;i=15293 */ + +static UA_StatusCode function_tsn_app_model_28_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetTxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetTxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15293LU), + UA_NODEID_NUMERIC(ns[1], 15244LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetTxSocket0"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_28_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15293LU)); +} + +/* FramesErr - ns=1;i=15297 */ + +static UA_StatusCode function_tsn_app_model_29_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15297LU), + UA_NODEID_NUMERIC(ns[1], 15293LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_29_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15297LU)); +} + +/* Id - ns=1;i=15295 */ + +static UA_StatusCode function_tsn_app_model_30_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15295LU), + UA_NODEID_NUMERIC(ns[1], 15293LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_30_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15295LU)); +} + +/* Frames - ns=1;i=15296 */ + +static UA_StatusCode function_tsn_app_model_31_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15296LU), + UA_NODEID_NUMERIC(ns[1], 15293LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_31_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15296LU)); +} + +/* Direction - ns=1;i=15294 */ + +static UA_StatusCode function_tsn_app_model_32_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15294LU), + UA_NODEID_NUMERIC(ns[1], 15293LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_32_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15294LU)); +} + +/* NetRxSocket0 - ns=1;i=15283 */ + +static UA_StatusCode function_tsn_app_model_33_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetRxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetRxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15283LU), + UA_NODEID_NUMERIC(ns[1], 15244LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetRxSocket0"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_33_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15283LU)); +} + +/* Id - ns=1;i=15285 */ + +static UA_StatusCode function_tsn_app_model_34_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15285LU), + UA_NODEID_NUMERIC(ns[1], 15283LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_34_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15285LU)); +} + +/* Frames - ns=1;i=15286 */ + +static UA_StatusCode function_tsn_app_model_35_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15286LU), + UA_NODEID_NUMERIC(ns[1], 15283LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_35_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15286LU)); +} + +/* FramesErr - ns=1;i=15287 */ + +static UA_StatusCode function_tsn_app_model_36_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15287LU), + UA_NODEID_NUMERIC(ns[1], 15283LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_36_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15287LU)); +} + +/* Direction - ns=1;i=15284 */ + +static UA_StatusCode function_tsn_app_model_37_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15284LU), + UA_NODEID_NUMERIC(ns[1], 15283LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_37_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15284LU)); +} + +/* NetRxSocket0 - ns=1;i=15181 */ + +static UA_StatusCode function_tsn_app_model_38_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetRxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetRxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15181LU), + UA_NODEID_NUMERIC(ns[1], 15142LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetRxSocket0"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15181LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_38_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15181LU)); +} + +/* Id - ns=1;i=15183 */ + +static UA_StatusCode function_tsn_app_model_39_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15183LU), + UA_NODEID_NUMERIC(ns[1], 15181LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15183LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_39_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15183LU)); +} + +/* Frames - ns=1;i=15184 */ + +static UA_StatusCode function_tsn_app_model_40_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15184LU), + UA_NODEID_NUMERIC(ns[1], 15181LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15184LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_40_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15184LU)); +} + +/* FramesErr - ns=1;i=15185 */ + +static UA_StatusCode function_tsn_app_model_41_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15185LU), + UA_NODEID_NUMERIC(ns[1], 15181LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15185LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_41_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15185LU)); +} + +/* Direction - ns=1;i=15182 */ + +static UA_StatusCode function_tsn_app_model_42_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15182LU), + UA_NODEID_NUMERIC(ns[1], 15181LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15182LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_42_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15182LU)); +} + +/* Id - ns=1;i=15081 */ + +static UA_StatusCode function_tsn_app_model_43_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15081LU), + UA_NODEID_NUMERIC(ns[1], 15079LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15081LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_43_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15081LU)); +} + +/* NetTxSocket0 - ns=1;i=15133 */ + +static UA_StatusCode function_tsn_app_model_44_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetTxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetTxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15133LU), + UA_NODEID_NUMERIC(ns[1], 15084LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetTxSocket0"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15133LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_44_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15133LU)); +} + +/* Direction - ns=1;i=15134 */ + +static UA_StatusCode function_tsn_app_model_45_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15134LU), + UA_NODEID_NUMERIC(ns[1], 15133LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15134LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_45_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15134LU)); +} + +/* Frames - ns=1;i=15136 */ + +static UA_StatusCode function_tsn_app_model_46_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15136LU), + UA_NODEID_NUMERIC(ns[1], 15133LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15136LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_46_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15136LU)); +} + +/* FramesErr - ns=1;i=15137 */ + +static UA_StatusCode function_tsn_app_model_47_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15137LU), + UA_NODEID_NUMERIC(ns[1], 15133LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15137LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_47_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15137LU)); +} + +/* Id - ns=1;i=15135 */ + +static UA_StatusCode function_tsn_app_model_48_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15135LU), + UA_NODEID_NUMERIC(ns[1], 15133LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15135LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_48_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15135LU)); +} + +/* NetTxSocket0 - ns=1;i=15191 */ + +static UA_StatusCode function_tsn_app_model_49_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "NetTxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "NetTxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15191LU), + UA_NODEID_NUMERIC(ns[1], 15142LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NetTxSocket0"), UA_NODEID_NUMERIC(ns[1], 15079LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15191LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_49_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15191LU)); +} + +/* FramesErr - ns=1;i=15195 */ + +static UA_StatusCode function_tsn_app_model_50_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15195LU), + UA_NODEID_NUMERIC(ns[1], 15191LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15195LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_50_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15195LU)); +} + +/* Frames - ns=1;i=15194 */ + +static UA_StatusCode function_tsn_app_model_51_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Frames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15194LU), + UA_NODEID_NUMERIC(ns[1], 15191LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Frames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15194LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_51_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15194LU)); +} + +/* Id - ns=1;i=15193 */ + +static UA_StatusCode function_tsn_app_model_52_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Id"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15193LU), + UA_NODEID_NUMERIC(ns[1], 15191LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Id"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15193LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_52_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15193LU)); +} + +/* Direction - ns=1;i=15192 */ + +static UA_StatusCode function_tsn_app_model_53_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Direction"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Rx or tx"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15192LU), + UA_NODEID_NUMERIC(ns[1], 15191LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Direction"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15192LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_53_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15192LU)); +} + +/* FramesErr - ns=1;i=15083 */ + +static UA_StatusCode function_tsn_app_model_54_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "FramesErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Frames errors number"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15083LU), + UA_NODEID_NUMERIC(ns[1], 15079LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "FramesErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15083LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_54_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15083LU)); +} + +/* CyclicRxSocketType - ns=1;i=15060 */ + +static UA_StatusCode function_tsn_app_model_55_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "CyclicRxSocketType"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Application level socket"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECTTYPE, UA_NODEID_NUMERIC(ns[1], 15060LU), + UA_NODEID_NUMERIC(ns[0], 58LU), UA_NODEID_NUMERIC(ns[0], 45LU), + UA_QUALIFIEDNAME(ns[1], "CyclicRxSocketType"), UA_NODEID_NULL, + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_55_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15060LU)); +} + +/* Link - ns=1;i=15066 */ + +static UA_StatusCode function_tsn_app_model_56_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Link"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Link up or down"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15066LU), + UA_NODEID_NUMERIC(ns[1], 15060LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Link"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15066LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_56_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15066LU)); +} + +/* CyclicRxSocket1 - ns=1;i=15104 */ + +static UA_StatusCode function_tsn_app_model_57_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "CyclicRxSocket1"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "CyclicRxSocket1"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15104LU), + UA_NODEID_NUMERIC(ns[1], 15084LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "CyclicRxSocket1"), UA_NODEID_NUMERIC(ns[1], 15060LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_57_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15104LU)); +} + +/* ErrId - ns=1;i=15107 */ + +static UA_StatusCode function_tsn_app_model_58_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15107LU), + UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15107LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_58_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15107LU)); +} + +/* PeerId - ns=1;i=15105 */ + +static UA_StatusCode function_tsn_app_model_59_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "PeerId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Peer identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15105LU), + UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "PeerId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15105LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_59_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15105LU)); +} + +/* ErrUnderflow - ns=1;i=15109 */ + +static UA_StatusCode function_tsn_app_model_60_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrUnderflow"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15109LU), + UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrUnderflow"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15109LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_60_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15109LU)); +} + +/* ErrTs - ns=1;i=15108 */ + +static UA_StatusCode function_tsn_app_model_61_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrTs"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15108LU), + UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrTs"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15108LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_61_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15108LU)); +} + +/* ValidFrames - ns=1;i=15106 */ + +static UA_StatusCode function_tsn_app_model_62_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ValidFrames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of valid frames"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15106LU), + UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ValidFrames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15106LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_62_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15106LU)); +} + +/* Link - ns=1;i=15110 */ + +static UA_StatusCode function_tsn_app_model_63_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Link"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Link up or down"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15110LU), + UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Link"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15110LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_63_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15110LU)); +} + +/* CyclicRxSocket1 - ns=1;i=15264 */ + +static UA_StatusCode function_tsn_app_model_64_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "CyclicRxSocket1"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "CyclicRxSocket1"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15264LU), + UA_NODEID_NUMERIC(ns[1], 15244LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "CyclicRxSocket1"), UA_NODEID_NUMERIC(ns[1], 15060LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_64_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15264LU)); +} + +/* ErrUnderflow - ns=1;i=15269 */ + +static UA_StatusCode function_tsn_app_model_65_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrUnderflow"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15269LU), + UA_NODEID_NUMERIC(ns[1], 15264LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrUnderflow"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_65_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15269LU)); +} + +/* ValidFrames - ns=1;i=15266 */ + +static UA_StatusCode function_tsn_app_model_66_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ValidFrames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of valid frames"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15266LU), + UA_NODEID_NUMERIC(ns[1], 15264LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ValidFrames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_66_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15266LU)); +} + +/* ErrId - ns=1;i=15267 */ + +static UA_StatusCode function_tsn_app_model_67_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15267LU), + UA_NODEID_NUMERIC(ns[1], 15264LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_67_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15267LU)); +} + +/* Link - ns=1;i=15270 */ + +static UA_StatusCode function_tsn_app_model_68_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Link"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Link up or down"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15270LU), + UA_NODEID_NUMERIC(ns[1], 15264LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Link"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_68_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15270LU)); +} + +/* PeerId - ns=1;i=15265 */ + +static UA_StatusCode function_tsn_app_model_69_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "PeerId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Peer identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15265LU), + UA_NODEID_NUMERIC(ns[1], 15264LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "PeerId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_69_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15265LU)); +} + +/* ErrTs - ns=1;i=15268 */ + +static UA_StatusCode function_tsn_app_model_70_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrTs"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15268LU), + UA_NODEID_NUMERIC(ns[1], 15264LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrTs"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_70_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15268LU)); +} + +/* CyclicRxSocket0 - ns=1;i=15245 */ + +static UA_StatusCode function_tsn_app_model_71_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "CyclicRxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "CyclicRxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15245LU), + UA_NODEID_NUMERIC(ns[1], 15244LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "CyclicRxSocket0"), UA_NODEID_NUMERIC(ns[1], 15060LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_71_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15245LU)); +} + +/* Link - ns=1;i=15251 */ + +static UA_StatusCode function_tsn_app_model_72_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Link"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Link up or down"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15251LU), + UA_NODEID_NUMERIC(ns[1], 15245LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Link"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_72_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15251LU)); +} + +/* PeerId - ns=1;i=15246 */ + +static UA_StatusCode function_tsn_app_model_73_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "PeerId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Peer identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15246LU), + UA_NODEID_NUMERIC(ns[1], 15245LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "PeerId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_73_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15246LU)); +} + +/* ValidFrames - ns=1;i=15247 */ + +static UA_StatusCode function_tsn_app_model_74_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ValidFrames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of valid frames"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15247LU), + UA_NODEID_NUMERIC(ns[1], 15245LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ValidFrames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_74_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15247LU)); +} + +/* ErrId - ns=1;i=15248 */ + +static UA_StatusCode function_tsn_app_model_75_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15248LU), + UA_NODEID_NUMERIC(ns[1], 15245LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_75_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15248LU)); +} + +/* ErrUnderflow - ns=1;i=15250 */ + +static UA_StatusCode function_tsn_app_model_76_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrUnderflow"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15250LU), + UA_NODEID_NUMERIC(ns[1], 15245LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrUnderflow"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_76_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15250LU)); +} + +/* ErrTs - ns=1;i=15249 */ + +static UA_StatusCode function_tsn_app_model_77_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrTs"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15249LU), + UA_NODEID_NUMERIC(ns[1], 15245LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrTs"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_77_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15249LU)); +} + +/* CyclicRxSocket0 - ns=1;i=15143 */ + +static UA_StatusCode function_tsn_app_model_78_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "CyclicRxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "CyclicRxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15143LU), + UA_NODEID_NUMERIC(ns[1], 15142LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "CyclicRxSocket0"), UA_NODEID_NUMERIC(ns[1], 15060LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_78_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15143LU)); +} + +/* ErrTs - ns=1;i=15147 */ + +static UA_StatusCode function_tsn_app_model_79_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrTs"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15147LU), + UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrTs"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15147LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_79_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15147LU)); +} + +/* ErrUnderflow - ns=1;i=15148 */ + +static UA_StatusCode function_tsn_app_model_80_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrUnderflow"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15148LU), + UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrUnderflow"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15148LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_80_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15148LU)); +} + +/* PeerId - ns=1;i=15144 */ + +static UA_StatusCode function_tsn_app_model_81_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "PeerId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Peer identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15144LU), + UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "PeerId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15144LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_81_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15144LU)); +} + +/* ValidFrames - ns=1;i=15145 */ + +static UA_StatusCode function_tsn_app_model_82_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ValidFrames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of valid frames"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15145LU), + UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ValidFrames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15145LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_82_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15145LU)); +} + +/* Link - ns=1;i=15149 */ + +static UA_StatusCode function_tsn_app_model_83_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Link"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Link up or down"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15149LU), + UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Link"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15149LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_83_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15149LU)); +} + +/* ErrId - ns=1;i=15146 */ + +static UA_StatusCode function_tsn_app_model_84_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15146LU), + UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15146LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_84_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15146LU)); +} + +/* ErrId - ns=1;i=15063 */ + +static UA_StatusCode function_tsn_app_model_85_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15063LU), + UA_NODEID_NUMERIC(ns[1], 15060LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15063LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_85_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15063LU)); +} + +/* ErrUnderflow - ns=1;i=15065 */ + +static UA_StatusCode function_tsn_app_model_86_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrUnderflow"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15065LU), + UA_NODEID_NUMERIC(ns[1], 15060LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrUnderflow"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15065LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_86_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15065LU)); +} + +/* ValidFrames - ns=1;i=15062 */ + +static UA_StatusCode function_tsn_app_model_87_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ValidFrames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of valid frames"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15062LU), + UA_NODEID_NUMERIC(ns[1], 15060LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ValidFrames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15062LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_87_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15062LU)); +} + +/* PeerId - ns=1;i=15061 */ + +static UA_StatusCode function_tsn_app_model_88_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "PeerId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Peer identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15061LU), + UA_NODEID_NUMERIC(ns[1], 15060LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "PeerId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15061LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_88_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15061LU)); +} + +/* CyclicRxSocket1 - ns=1;i=15162 */ + +static UA_StatusCode function_tsn_app_model_89_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "CyclicRxSocket1"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "CyclicRxSocket1"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15162LU), + UA_NODEID_NUMERIC(ns[1], 15142LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "CyclicRxSocket1"), UA_NODEID_NUMERIC(ns[1], 15060LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_89_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15162LU)); +} + +/* PeerId - ns=1;i=15163 */ + +static UA_StatusCode function_tsn_app_model_90_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "PeerId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Peer identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15163LU), + UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "PeerId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15163LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_90_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15163LU)); +} + +/* ValidFrames - ns=1;i=15164 */ + +static UA_StatusCode function_tsn_app_model_91_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ValidFrames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of valid frames"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15164LU), + UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ValidFrames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15164LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_91_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15164LU)); +} + +/* Link - ns=1;i=15168 */ + +static UA_StatusCode function_tsn_app_model_92_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Link"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Link up or down"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15168LU), + UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Link"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15168LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_92_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15168LU)); +} + +/* ErrId - ns=1;i=15165 */ + +static UA_StatusCode function_tsn_app_model_93_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15165LU), + UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15165LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_93_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15165LU)); +} + +/* ErrTs - ns=1;i=15166 */ + +static UA_StatusCode function_tsn_app_model_94_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrTs"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15166LU), + UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrTs"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15166LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_94_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15166LU)); +} + +/* ErrUnderflow - ns=1;i=15167 */ + +static UA_StatusCode function_tsn_app_model_95_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrUnderflow"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15167LU), + UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrUnderflow"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15167LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_95_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15167LU)); +} + +/* CyclicRxSocket0 - ns=1;i=15085 */ + +static UA_StatusCode function_tsn_app_model_96_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "CyclicRxSocket0"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "CyclicRxSocket0"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15085LU), + UA_NODEID_NUMERIC(ns[1], 15084LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "CyclicRxSocket0"), UA_NODEID_NUMERIC(ns[1], 15060LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_96_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15085LU)); +} + +/* Link - ns=1;i=15091 */ + +static UA_StatusCode function_tsn_app_model_97_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Link"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Link up or down"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15091LU), + UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Link"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15091LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_97_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15091LU)); +} + +/* ErrTs - ns=1;i=15089 */ + +static UA_StatusCode function_tsn_app_model_98_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrTs"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15089LU), + UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrTs"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15089LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_98_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15089LU)); +} + +/* PeerId - ns=1;i=15086 */ + +static UA_StatusCode function_tsn_app_model_99_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "PeerId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Peer identifier"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15086LU), + UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "PeerId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15086LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_99_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15086LU)); +} + +/* ValidFrames - ns=1;i=15087 */ + +static UA_StatusCode function_tsn_app_model_100_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ValidFrames"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of valid frames"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15087LU), + UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ValidFrames"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15087LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_100_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15087LU)); +} + +/* ErrUnderflow - ns=1;i=15090 */ + +static UA_StatusCode function_tsn_app_model_101_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrUnderflow"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15090LU), + UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrUnderflow"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15090LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_101_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15090LU)); +} + +/* ErrId - ns=1;i=15088 */ + +static UA_StatusCode function_tsn_app_model_102_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrId"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15088LU), + UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrId"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15088LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_102_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15088LU)); +} + +/* ErrTs - ns=1;i=15064 */ + +static UA_StatusCode function_tsn_app_model_103_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ErrTs"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Error parameter"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15064LU), + UA_NODEID_NUMERIC(ns[1], 15060LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ErrTs"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15064LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_103_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15064LU)); +} + +/* TaskStatsType - ns=1;i=15016 */ + +static UA_StatusCode function_tsn_app_model_104_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TaskStatsType"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Statistics of the task"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECTTYPE, UA_NODEID_NUMERIC(ns[1], 15016LU), + UA_NODEID_NUMERIC(ns[0], 58LU), UA_NODEID_NUMERIC(ns[0], 45LU), + UA_QUALIFIEDNAME(ns[1], "TaskStatsType"), UA_NODEID_NULL, + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_104_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15016LU)); +} + +/* SchedTimeout - ns=1;i=15021 */ + +static UA_StatusCode function_tsn_app_model_105_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedTimeout"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedTimeout"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15021LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedTimeout"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15021LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_105_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15021LU)); +} + +/* Sched - ns=1;i=15017 */ + +static UA_StatusCode function_tsn_app_model_106_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Sched"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Sched"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15017LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Sched"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15017LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_106_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15017LU)); +} + +/* ClockDiscount - ns=1;i=15022 */ + +static UA_StatusCode function_tsn_app_model_107_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ClockDiscount"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "ClockDiscount"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15022LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ClockDiscount"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15022LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_107_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15022LU)); +} + +/* TaskStats - ns=1;i=15298 */ + +static UA_StatusCode function_tsn_app_model_108_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TaskStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15298LU), + UA_NODEID_NUMERIC(ns[1], 15240LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TaskStats"), UA_NODEID_NUMERIC(ns[1], 15016LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_108_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15298LU)); +} + +/* ClockErr - ns=1;i=15305 */ + +static UA_StatusCode function_tsn_app_model_109_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ClockErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "ClockErr"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15305LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ClockErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_109_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15305LU)); +} + +/* Sched - ns=1;i=15299 */ + +static UA_StatusCode function_tsn_app_model_110_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Sched"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Sched"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15299LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Sched"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_110_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15299LU)); +} + +/* ClockDiscount - ns=1;i=15304 */ + +static UA_StatusCode function_tsn_app_model_111_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ClockDiscount"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "ClockDiscount"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15304LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ClockDiscount"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_111_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15304LU)); +} + +/* SchedEarly - ns=1;i=15300 */ + +static UA_StatusCode function_tsn_app_model_112_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedEarly"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedEarly"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15300LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedEarly"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_112_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15300LU)); +} + +/* SchedMissed - ns=1;i=15302 */ + +static UA_StatusCode function_tsn_app_model_113_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedMissed"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedMissed"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15302LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedMissed"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_113_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15302LU)); +} + +/* SchedLate - ns=1;i=15301 */ + +static UA_StatusCode function_tsn_app_model_114_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedLate"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedLate"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15301LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedLate"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_114_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15301LU)); +} + +/* SchedTimeout - ns=1;i=15303 */ + +static UA_StatusCode function_tsn_app_model_115_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedTimeout"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedTimeout"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15303LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedTimeout"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_115_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15303LU)); +} + +/* ClockErr - ns=1;i=15023 */ + +static UA_StatusCode function_tsn_app_model_116_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ClockErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "ClockErr"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15023LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ClockErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15023LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_116_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15023LU)); +} + +/* SchedEarly - ns=1;i=15018 */ + +static UA_StatusCode function_tsn_app_model_117_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedEarly"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedEarly"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15018LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedEarly"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15018LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_117_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15018LU)); +} + +/* SchedMissed - ns=1;i=15020 */ + +static UA_StatusCode function_tsn_app_model_118_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedMissed"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedMissed"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15020LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedMissed"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15020LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_118_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15020LU)); +} + +/* SchedLate - ns=1;i=15019 */ + +static UA_StatusCode function_tsn_app_model_119_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedLate"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedLate"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15019LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedLate"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15019LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_119_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15019LU)); +} + +/* TaskStats - ns=1;i=15196 */ + +static UA_StatusCode function_tsn_app_model_120_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TaskStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15196LU), + UA_NODEID_NUMERIC(ns[1], 15138LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TaskStats"), UA_NODEID_NUMERIC(ns[1], 15016LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_120_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15196LU)); +} + +/* SchedLate - ns=1;i=15199 */ + +static UA_StatusCode function_tsn_app_model_121_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedLate"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedLate"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15199LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedLate"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15199LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_121_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15199LU)); +} + +/* ClockDiscount - ns=1;i=15202 */ + +static UA_StatusCode function_tsn_app_model_122_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ClockDiscount"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "ClockDiscount"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15202LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ClockDiscount"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15202LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_122_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15202LU)); +} + +/* SchedEarly - ns=1;i=15198 */ + +static UA_StatusCode function_tsn_app_model_123_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedEarly"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedEarly"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15198LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedEarly"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15198LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_123_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15198LU)); +} + +/* ClockErr - ns=1;i=15203 */ + +static UA_StatusCode function_tsn_app_model_124_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "ClockErr"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "ClockErr"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15203LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ClockErr"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15203LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_124_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15203LU)); +} + +/* Sched - ns=1;i=15197 */ + +static UA_StatusCode function_tsn_app_model_125_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Sched"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Sched"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15197LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Sched"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15197LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_125_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15197LU)); +} + +/* SchedMissed - ns=1;i=15200 */ + +static UA_StatusCode function_tsn_app_model_126_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedMissed"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedMissed"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15200LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedMissed"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15200LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_126_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15200LU)); +} + +/* SchedTimeout - ns=1;i=15201 */ + +static UA_StatusCode function_tsn_app_model_127_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SchedTimeout"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedTimeout"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15201LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedTimeout"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15201LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_127_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15201LU)); +} + +/* ConfigurationType - ns=1;i=15013 */ + +static UA_StatusCode function_tsn_app_model_128_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "ConfigurationType"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Base type for the app configuration"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECTTYPE, UA_NODEID_NUMERIC(ns[1], 15013LU), + UA_NODEID_NUMERIC(ns[0], 58LU), UA_NODEID_NUMERIC(ns[0], 45LU), + UA_QUALIFIEDNAME(ns[1], "ConfigurationType"), UA_NODEID_NULL, + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_128_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15013LU)); +} + +/* Role - ns=1;i=15014 */ + +static UA_StatusCode function_tsn_app_model_129_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Role"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Role of the endpoint : controller or io_device"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15014LU), + UA_NODEID_NUMERIC(ns[1], 15013LU), UA_NODEID_NUMERIC(ns[0], 46LU), + UA_QUALIFIEDNAME(ns[1], "Role"), UA_NODEID_NUMERIC(ns[0], 68LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15014LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_129_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15014LU)); +} + +/* Configuration - ns=1;i=15241 */ + +static UA_StatusCode function_tsn_app_model_130_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "Configuration"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Configuration for the app"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15241LU), + UA_NODEID_NUMERIC(ns[1], 15240LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Configuration"), UA_NODEID_NUMERIC(ns[1], 15013LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_130_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15241LU)); +} + +/* NumPeers - ns=1;i=15243 */ + +static UA_StatusCode function_tsn_app_model_131_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NumPeers"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of peers"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15243LU), + UA_NODEID_NUMERIC(ns[1], 15241LU), UA_NODEID_NUMERIC(ns[0], 46LU), + UA_QUALIFIEDNAME(ns[1], "NumPeers"), UA_NODEID_NUMERIC(ns[0], 68LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_131_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15243LU)); +} + +/* Role - ns=1;i=15242 */ + +static UA_StatusCode function_tsn_app_model_132_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Role"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Role of the endpoint : controller or io_device"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15242LU), + UA_NODEID_NUMERIC(ns[1], 15241LU), UA_NODEID_NUMERIC(ns[0], 46LU), + UA_QUALIFIEDNAME(ns[1], "Role"), UA_NODEID_NUMERIC(ns[0], 68LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_132_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15242LU)); +} + +/* NumPeers - ns=1;i=15015 */ + +static UA_StatusCode function_tsn_app_model_133_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NumPeers"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of peers"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15015LU), + UA_NODEID_NUMERIC(ns[1], 15013LU), UA_NODEID_NUMERIC(ns[0], 46LU), + UA_QUALIFIEDNAME(ns[1], "NumPeers"), UA_NODEID_NUMERIC(ns[0], 68LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15015LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_133_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15015LU)); +} + +/* Configuration - ns=1;i=15139 */ + +static UA_StatusCode function_tsn_app_model_134_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "Configuration"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Configuration for the app"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15139LU), + UA_NODEID_NUMERIC(ns[1], 15138LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Configuration"), UA_NODEID_NUMERIC(ns[1], 15013LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15139LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_134_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15139LU)); +} + +/* Role - ns=1;i=15140 */ + +static UA_StatusCode function_tsn_app_model_135_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 12LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Role"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Role of the endpoint : controller or io_device"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15140LU), + UA_NODEID_NUMERIC(ns[1], 15139LU), UA_NODEID_NUMERIC(ns[0], 46LU), + UA_QUALIFIEDNAME(ns[1], "Role"), UA_NODEID_NUMERIC(ns[0], 68LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15140LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_135_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15140LU)); +} + +/* NumPeers - ns=1;i=15141 */ + +static UA_StatusCode function_tsn_app_model_136_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NumPeers"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of peers"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15141LU), + UA_NODEID_NUMERIC(ns[1], 15139LU), UA_NODEID_NUMERIC(ns[0], 46LU), + UA_QUALIFIEDNAME(ns[1], "NumPeers"), UA_NODEID_NUMERIC(ns[0], 68LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15141LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_136_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15141LU)); +} + +/* HistogramType - ns=1;i=15009 */ + +static UA_StatusCode function_tsn_app_model_137_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "HistogramType"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Base type for all histograms"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECTTYPE, UA_NODEID_NUMERIC(ns[1], 15009LU), + UA_NODEID_NUMERIC(ns[0], 58LU), UA_NODEID_NUMERIC(ns[0], 45LU), + UA_QUALIFIEDNAME(ns[1], "HistogramType"), UA_NODEID_NULL, + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_137_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15009LU)); +} + +/* SchedErrHisto - ns=1;i=15314 */ + +static UA_StatusCode function_tsn_app_model_138_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SchedErrHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedErrHisto"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15314LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedErrHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_138_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15314LU)); +} + +/* SlotSize - ns=1;i=15316 */ + +static UA_StatusCode function_tsn_app_model_139_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15316LU), + UA_NODEID_NUMERIC(ns[1], 15314LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_139_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15316LU)); +} + +/* Slots - ns=1;i=15317 */ + +static UA_StatusCode function_tsn_app_model_140_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15317LU), + UA_NODEID_NUMERIC(ns[1], 15314LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_140_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15317LU)); +} + +/* NSlots - ns=1;i=15315 */ + +static UA_StatusCode function_tsn_app_model_141_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15315LU), + UA_NODEID_NUMERIC(ns[1], 15314LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_141_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15315LU)); +} + +/* ProcTimeHisto - ns=1;i=15224 */ + +static UA_StatusCode function_tsn_app_model_142_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "ProcTimeHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Processing time histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15224LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ProcTimeHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15224LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_142_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15224LU)); +} + +/* SlotSize - ns=1;i=15226 */ + +static UA_StatusCode function_tsn_app_model_143_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15226LU), + UA_NODEID_NUMERIC(ns[1], 15224LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15226LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_143_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15226LU)); +} + +/* Slots - ns=1;i=15227 */ + +static UA_StatusCode function_tsn_app_model_144_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15227LU), + UA_NODEID_NUMERIC(ns[1], 15224LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15227LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_144_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15227LU)); +} + +/* NSlots - ns=1;i=15225 */ + +static UA_StatusCode function_tsn_app_model_145_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15225LU), + UA_NODEID_NUMERIC(ns[1], 15224LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15225LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_145_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15225LU)); +} + +/* TrafficLatencyHisto - ns=1;i=15260 */ + +static UA_StatusCode function_tsn_app_model_146_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15260LU), + UA_NODEID_NUMERIC(ns[1], 15245LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyHisto"), + UA_NODEID_NUMERIC(ns[1], 15009LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_146_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15260LU)); +} + +/* SlotSize - ns=1;i=15262 */ + +static UA_StatusCode function_tsn_app_model_147_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15262LU), + UA_NODEID_NUMERIC(ns[1], 15260LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_147_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15262LU)); +} + +/* Slots - ns=1;i=15263 */ + +static UA_StatusCode function_tsn_app_model_148_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15263LU), + UA_NODEID_NUMERIC(ns[1], 15260LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_148_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15263LU)); +} + +/* NSlots - ns=1;i=15261 */ + +static UA_StatusCode function_tsn_app_model_149_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15261LU), + UA_NODEID_NUMERIC(ns[1], 15260LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_149_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15261LU)); +} + +/* ProcTimeHisto - ns=1;i=15044 */ + +static UA_StatusCode function_tsn_app_model_150_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "ProcTimeHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Processing time histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15044LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ProcTimeHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15044LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_150_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15044LU)); +} + +/* Slots - ns=1;i=15047 */ + +static UA_StatusCode function_tsn_app_model_151_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15047LU), + UA_NODEID_NUMERIC(ns[1], 15044LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15047LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_151_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15047LU)); +} + +/* NSlots - ns=1;i=15045 */ + +static UA_StatusCode function_tsn_app_model_152_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15045LU), + UA_NODEID_NUMERIC(ns[1], 15044LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15045LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_152_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15045LU)); +} + +/* SlotSize - ns=1;i=15046 */ + +static UA_StatusCode function_tsn_app_model_153_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15046LU), + UA_NODEID_NUMERIC(ns[1], 15044LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15046LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_153_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15046LU)); +} + +/* TrafficLatencyHisto - ns=1;i=15075 */ + +static UA_StatusCode function_tsn_app_model_154_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15075LU), + UA_NODEID_NUMERIC(ns[1], 15060LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyHisto"), + UA_NODEID_NUMERIC(ns[1], 15009LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15075LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_154_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15075LU)); +} + +/* NSlots - ns=1;i=15076 */ + +static UA_StatusCode function_tsn_app_model_155_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15076LU), + UA_NODEID_NUMERIC(ns[1], 15075LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15076LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_155_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15076LU)); +} + +/* SlotSize - ns=1;i=15077 */ + +static UA_StatusCode function_tsn_app_model_156_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15077LU), + UA_NODEID_NUMERIC(ns[1], 15075LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15077LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_156_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15077LU)); +} + +/* Slots - ns=1;i=15078 */ + +static UA_StatusCode function_tsn_app_model_157_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15078LU), + UA_NODEID_NUMERIC(ns[1], 15075LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15078LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_157_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15078LU)); +} + +/* TrafficLatencyHisto - ns=1;i=15119 */ + +static UA_StatusCode function_tsn_app_model_158_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15119LU), + UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyHisto"), + UA_NODEID_NUMERIC(ns[1], 15009LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15119LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_158_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15119LU)); +} + +/* SlotSize - ns=1;i=15121 */ + +static UA_StatusCode function_tsn_app_model_159_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15121LU), + UA_NODEID_NUMERIC(ns[1], 15119LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15121LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_159_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15121LU)); +} + +/* Slots - ns=1;i=15122 */ + +static UA_StatusCode function_tsn_app_model_160_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15122LU), + UA_NODEID_NUMERIC(ns[1], 15119LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15122LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_160_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15122LU)); +} + +/* NSlots - ns=1;i=15120 */ + +static UA_StatusCode function_tsn_app_model_161_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15120LU), + UA_NODEID_NUMERIC(ns[1], 15119LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15120LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_161_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15120LU)); +} + +/* TrafficLatencyHisto - ns=1;i=15279 */ + +static UA_StatusCode function_tsn_app_model_162_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15279LU), + UA_NODEID_NUMERIC(ns[1], 15264LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyHisto"), + UA_NODEID_NUMERIC(ns[1], 15009LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_162_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15279LU)); +} + +/* Slots - ns=1;i=15282 */ + +static UA_StatusCode function_tsn_app_model_163_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15282LU), + UA_NODEID_NUMERIC(ns[1], 15279LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_163_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15282LU)); +} + +/* SlotSize - ns=1;i=15281 */ + +static UA_StatusCode function_tsn_app_model_164_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15281LU), + UA_NODEID_NUMERIC(ns[1], 15279LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_164_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15281LU)); +} + +/* NSlots - ns=1;i=15280 */ + +static UA_StatusCode function_tsn_app_model_165_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15280LU), + UA_NODEID_NUMERIC(ns[1], 15279LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_165_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15280LU)); +} + +/* TotalTimeHisto - ns=1;i=15236 */ + +static UA_StatusCode function_tsn_app_model_166_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TotalTimeHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Total time histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15236LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TotalTimeHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15236LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_166_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15236LU)); +} + +/* NSlots - ns=1;i=15237 */ + +static UA_StatusCode function_tsn_app_model_167_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15237LU), + UA_NODEID_NUMERIC(ns[1], 15236LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15237LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_167_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15237LU)); +} + +/* Slots - ns=1;i=15239 */ + +static UA_StatusCode function_tsn_app_model_168_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15239LU), + UA_NODEID_NUMERIC(ns[1], 15236LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15239LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_168_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15239LU)); +} + +/* SlotSize - ns=1;i=15238 */ + +static UA_StatusCode function_tsn_app_model_169_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15238LU), + UA_NODEID_NUMERIC(ns[1], 15236LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15238LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_169_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15238LU)); +} + +/* Slots - ns=1;i=15012 */ + +static UA_StatusCode function_tsn_app_model_170_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15012LU), + UA_NODEID_NUMERIC(ns[1], 15009LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15012LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_170_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15012LU)); +} + +/* TrafficLatencyHisto - ns=1;i=15177 */ + +static UA_StatusCode function_tsn_app_model_171_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15177LU), + UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyHisto"), + UA_NODEID_NUMERIC(ns[1], 15009LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15177LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_171_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15177LU)); +} + +/* SlotSize - ns=1;i=15179 */ + +static UA_StatusCode function_tsn_app_model_172_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15179LU), + UA_NODEID_NUMERIC(ns[1], 15177LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15179LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_172_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15179LU)); +} + +/* NSlots - ns=1;i=15178 */ + +static UA_StatusCode function_tsn_app_model_173_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15178LU), + UA_NODEID_NUMERIC(ns[1], 15177LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15178LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_173_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15178LU)); +} + +/* Slots - ns=1;i=15180 */ + +static UA_StatusCode function_tsn_app_model_174_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15180LU), + UA_NODEID_NUMERIC(ns[1], 15177LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15180LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_174_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15180LU)); +} + +/* NSlots - ns=1;i=15010 */ + +static UA_StatusCode function_tsn_app_model_175_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15010LU), + UA_NODEID_NUMERIC(ns[1], 15009LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15010LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_175_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15010LU)); +} + +/* ProcTimeHisto - ns=1;i=15326 */ + +static UA_StatusCode function_tsn_app_model_176_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "ProcTimeHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Processing time histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15326LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ProcTimeHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_176_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15326LU)); +} + +/* Slots - ns=1;i=15329 */ + +static UA_StatusCode function_tsn_app_model_177_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15329LU), + UA_NODEID_NUMERIC(ns[1], 15326LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_177_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15329LU)); +} + +/* NSlots - ns=1;i=15327 */ + +static UA_StatusCode function_tsn_app_model_178_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15327LU), + UA_NODEID_NUMERIC(ns[1], 15326LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_178_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15327LU)); +} + +/* SlotSize - ns=1;i=15328 */ + +static UA_StatusCode function_tsn_app_model_179_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15328LU), + UA_NODEID_NUMERIC(ns[1], 15326LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_179_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15328LU)); +} + +/* TrafficLatencyHisto - ns=1;i=15158 */ + +static UA_StatusCode function_tsn_app_model_180_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15158LU), + UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyHisto"), + UA_NODEID_NUMERIC(ns[1], 15009LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15158LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_180_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15158LU)); +} + +/* Slots - ns=1;i=15161 */ + +static UA_StatusCode function_tsn_app_model_181_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15161LU), + UA_NODEID_NUMERIC(ns[1], 15158LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15161LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_181_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15161LU)); +} + +/* NSlots - ns=1;i=15159 */ + +static UA_StatusCode function_tsn_app_model_182_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15159LU), + UA_NODEID_NUMERIC(ns[1], 15158LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15159LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_182_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15159LU)); +} + +/* SlotSize - ns=1;i=15160 */ + +static UA_StatusCode function_tsn_app_model_183_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15160LU), + UA_NODEID_NUMERIC(ns[1], 15158LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15160LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_183_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15160LU)); +} + +/* SlotSize - ns=1;i=15011 */ + +static UA_StatusCode function_tsn_app_model_184_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15011LU), + UA_NODEID_NUMERIC(ns[1], 15009LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15011LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_184_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15011LU)); +} + +/* TotalTimeHisto - ns=1;i=15056 */ + +static UA_StatusCode function_tsn_app_model_185_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TotalTimeHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Total time histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15056LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TotalTimeHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15056LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_185_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15056LU)); +} + +/* Slots - ns=1;i=15059 */ + +static UA_StatusCode function_tsn_app_model_186_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15059LU), + UA_NODEID_NUMERIC(ns[1], 15056LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15059LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_186_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15059LU)); +} + +/* SlotSize - ns=1;i=15058 */ + +static UA_StatusCode function_tsn_app_model_187_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15058LU), + UA_NODEID_NUMERIC(ns[1], 15056LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15058LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_187_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15058LU)); +} + +/* NSlots - ns=1;i=15057 */ + +static UA_StatusCode function_tsn_app_model_188_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15057LU), + UA_NODEID_NUMERIC(ns[1], 15056LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15057LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_188_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15057LU)); +} + +/* SchedErrHisto - ns=1;i=15212 */ + +static UA_StatusCode function_tsn_app_model_189_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SchedErrHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedErrHisto"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15212LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedErrHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15212LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_189_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15212LU)); +} + +/* NSlots - ns=1;i=15213 */ + +static UA_StatusCode function_tsn_app_model_190_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15213LU), + UA_NODEID_NUMERIC(ns[1], 15212LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15213LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_190_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15213LU)); +} + +/* SlotSize - ns=1;i=15214 */ + +static UA_StatusCode function_tsn_app_model_191_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15214LU), + UA_NODEID_NUMERIC(ns[1], 15212LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15214LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_191_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15214LU)); +} + +/* Slots - ns=1;i=15215 */ + +static UA_StatusCode function_tsn_app_model_192_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15215LU), + UA_NODEID_NUMERIC(ns[1], 15212LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15215LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_192_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15215LU)); +} + +/* SchedErrHisto - ns=1;i=15032 */ + +static UA_StatusCode function_tsn_app_model_193_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SchedErrHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedErrHisto"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15032LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedErrHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15032LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_193_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15032LU)); +} + +/* SlotSize - ns=1;i=15034 */ + +static UA_StatusCode function_tsn_app_model_194_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15034LU), + UA_NODEID_NUMERIC(ns[1], 15032LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15034LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_194_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15034LU)); +} + +/* Slots - ns=1;i=15035 */ + +static UA_StatusCode function_tsn_app_model_195_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15035LU), + UA_NODEID_NUMERIC(ns[1], 15032LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15035LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_195_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15035LU)); +} + +/* NSlots - ns=1;i=15033 */ + +static UA_StatusCode function_tsn_app_model_196_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15033LU), + UA_NODEID_NUMERIC(ns[1], 15032LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15033LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_196_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15033LU)); +} + +/* TrafficLatencyHisto - ns=1;i=15100 */ + +static UA_StatusCode function_tsn_app_model_197_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15100LU), + UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyHisto"), + UA_NODEID_NUMERIC(ns[1], 15009LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15100LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_197_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15100LU)); +} + +/* Slots - ns=1;i=15103 */ + +static UA_StatusCode function_tsn_app_model_198_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15103LU), + UA_NODEID_NUMERIC(ns[1], 15100LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15103LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_198_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15103LU)); +} + +/* SlotSize - ns=1;i=15102 */ + +static UA_StatusCode function_tsn_app_model_199_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15102LU), + UA_NODEID_NUMERIC(ns[1], 15100LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15102LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_199_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15102LU)); +} + +/* NSlots - ns=1;i=15101 */ + +static UA_StatusCode function_tsn_app_model_200_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15101LU), + UA_NODEID_NUMERIC(ns[1], 15100LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15101LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_200_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15101LU)); +} + +/* TotalTimeHisto - ns=1;i=15338 */ + +static UA_StatusCode function_tsn_app_model_201_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TotalTimeHisto"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Total time histogram"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15338LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TotalTimeHisto"), UA_NODEID_NUMERIC(ns[1], 15009LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_201_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15338LU)); +} + +/* NSlots - ns=1;i=15339 */ + +static UA_StatusCode function_tsn_app_model_202_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "NSlots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Number of slots"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15339LU), + UA_NODEID_NUMERIC(ns[1], 15338LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "NSlots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_202_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15339LU)); +} + +/* SlotSize - ns=1;i=15340 */ + +static UA_StatusCode function_tsn_app_model_203_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "SlotSize"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Slot size"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15340LU), + UA_NODEID_NUMERIC(ns[1], 15338LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SlotSize"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_203_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15340LU)); +} + +/* Slots - ns=1;i=15341 */ + +static UA_StatusCode function_tsn_app_model_204_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + attr.valueRank = 1; + attr.arrayDimensionsSize = 1; + UA_UInt32 arrayDimensions[1]; + arrayDimensions[0] = 0; + attr.arrayDimensions = &arrayDimensions[0]; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 7LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Slots"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Array for the repartition of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15341LU), + UA_NODEID_NUMERIC(ns[1], 15338LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Slots"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_204_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15341LU)); +} + +/* StatsType - ns=1;i=15001 */ + +static UA_StatusCode function_tsn_app_model_205_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "StatsType"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Base type for all statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECTTYPE, UA_NODEID_NUMERIC(ns[1], 15001LU), + UA_NODEID_NUMERIC(ns[0], 58LU), UA_NODEID_NUMERIC(ns[0], 45LU), + UA_QUALIFIEDNAME(ns[1], "StatsType"), UA_NODEID_NULL, + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_205_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15001LU)); +} + +/* TotalTimeStats - ns=1;i=15228 */ + +static UA_StatusCode function_tsn_app_model_206_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TotalTimeStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Total time statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15228LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TotalTimeStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15228LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_206_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15228LU)); +} + +/* Ms - ns=1;i=15234 */ + +static UA_StatusCode function_tsn_app_model_207_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15234LU), + UA_NODEID_NUMERIC(ns[1], 15228LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15234LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_207_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15234LU)); +} + +/* Min - ns=1;i=15229 */ + +static UA_StatusCode function_tsn_app_model_208_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15229LU), + UA_NODEID_NUMERIC(ns[1], 15228LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15229LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_208_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15229LU)); +} + +/* AbsMax - ns=1;i=15232 */ + +static UA_StatusCode function_tsn_app_model_209_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15232LU), + UA_NODEID_NUMERIC(ns[1], 15228LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15232LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_209_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15232LU)); +} + +/* Max - ns=1;i=15231 */ + +static UA_StatusCode function_tsn_app_model_210_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15231LU), + UA_NODEID_NUMERIC(ns[1], 15228LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15231LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_210_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15231LU)); +} + +/* AbsMin - ns=1;i=15233 */ + +static UA_StatusCode function_tsn_app_model_211_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15233LU), + UA_NODEID_NUMERIC(ns[1], 15228LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15233LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_211_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15233LU)); +} + +/* Variance - ns=1;i=15235 */ + +static UA_StatusCode function_tsn_app_model_212_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15235LU), + UA_NODEID_NUMERIC(ns[1], 15228LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15235LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_212_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15235LU)); +} + +/* Mean - ns=1;i=15230 */ + +static UA_StatusCode function_tsn_app_model_213_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15230LU), + UA_NODEID_NUMERIC(ns[1], 15228LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15230LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_213_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15230LU)); +} + +/* TrafficLatencyStats - ns=1;i=15150 */ + +static UA_StatusCode function_tsn_app_model_214_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15150LU), + UA_NODEID_NUMERIC(ns[1], 15143LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyStats"), + UA_NODEID_NUMERIC(ns[1], 15001LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15150LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_214_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15150LU)); +} + +/* AbsMin - ns=1;i=15155 */ + +static UA_StatusCode function_tsn_app_model_215_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15155LU), + UA_NODEID_NUMERIC(ns[1], 15150LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15155LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_215_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15155LU)); +} + +/* AbsMax - ns=1;i=15154 */ + +static UA_StatusCode function_tsn_app_model_216_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15154LU), + UA_NODEID_NUMERIC(ns[1], 15150LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15154LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_216_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15154LU)); +} + +/* Ms - ns=1;i=15156 */ + +static UA_StatusCode function_tsn_app_model_217_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15156LU), + UA_NODEID_NUMERIC(ns[1], 15150LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15156LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_217_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15156LU)); +} + +/* Max - ns=1;i=15153 */ + +static UA_StatusCode function_tsn_app_model_218_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15153LU), + UA_NODEID_NUMERIC(ns[1], 15150LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15153LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_218_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15153LU)); +} + +/* Variance - ns=1;i=15157 */ + +static UA_StatusCode function_tsn_app_model_219_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15157LU), + UA_NODEID_NUMERIC(ns[1], 15150LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15157LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_219_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15157LU)); +} + +/* Mean - ns=1;i=15152 */ + +static UA_StatusCode function_tsn_app_model_220_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15152LU), + UA_NODEID_NUMERIC(ns[1], 15150LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15152LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_220_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15152LU)); +} + +/* Min - ns=1;i=15151 */ + +static UA_StatusCode function_tsn_app_model_221_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15151LU), + UA_NODEID_NUMERIC(ns[1], 15150LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15151LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_221_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15151LU)); +} + +/* Variance - ns=1;i=15008 */ + +static UA_StatusCode function_tsn_app_model_222_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15008LU), + UA_NODEID_NUMERIC(ns[1], 15001LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15008LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_222_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15008LU)); +} + +/* ProcTimeStats - ns=1;i=15036 */ + +static UA_StatusCode function_tsn_app_model_223_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "ProcTimeStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Processing time statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15036LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ProcTimeStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15036LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_223_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15036LU)); +} + +/* Mean - ns=1;i=15038 */ + +static UA_StatusCode function_tsn_app_model_224_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15038LU), + UA_NODEID_NUMERIC(ns[1], 15036LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15038LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_224_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15038LU)); +} + +/* Max - ns=1;i=15039 */ + +static UA_StatusCode function_tsn_app_model_225_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15039LU), + UA_NODEID_NUMERIC(ns[1], 15036LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15039LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_225_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15039LU)); +} + +/* AbsMax - ns=1;i=15040 */ + +static UA_StatusCode function_tsn_app_model_226_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15040LU), + UA_NODEID_NUMERIC(ns[1], 15036LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15040LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_226_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15040LU)); +} + +/* Variance - ns=1;i=15043 */ + +static UA_StatusCode function_tsn_app_model_227_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15043LU), + UA_NODEID_NUMERIC(ns[1], 15036LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15043LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_227_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15043LU)); +} + +/* Ms - ns=1;i=15042 */ + +static UA_StatusCode function_tsn_app_model_228_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15042LU), + UA_NODEID_NUMERIC(ns[1], 15036LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15042LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_228_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15042LU)); +} + +/* Min - ns=1;i=15037 */ + +static UA_StatusCode function_tsn_app_model_229_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15037LU), + UA_NODEID_NUMERIC(ns[1], 15036LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15037LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_229_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15037LU)); +} + +/* AbsMin - ns=1;i=15041 */ + +static UA_StatusCode function_tsn_app_model_230_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15041LU), + UA_NODEID_NUMERIC(ns[1], 15036LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15041LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_230_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15041LU)); +} + +/* Min - ns=1;i=15002 */ + +static UA_StatusCode function_tsn_app_model_231_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15002LU), + UA_NODEID_NUMERIC(ns[1], 15001LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15002LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_231_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15002LU)); +} + +/* ProcTimeStats - ns=1;i=15216 */ + +static UA_StatusCode function_tsn_app_model_232_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "ProcTimeStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Processing time statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15216LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ProcTimeStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15216LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_232_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15216LU)); +} + +/* Variance - ns=1;i=15223 */ + +static UA_StatusCode function_tsn_app_model_233_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15223LU), + UA_NODEID_NUMERIC(ns[1], 15216LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15223LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_233_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15223LU)); +} + +/* Max - ns=1;i=15219 */ + +static UA_StatusCode function_tsn_app_model_234_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15219LU), + UA_NODEID_NUMERIC(ns[1], 15216LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15219LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_234_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15219LU)); +} + +/* AbsMin - ns=1;i=15221 */ + +static UA_StatusCode function_tsn_app_model_235_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15221LU), + UA_NODEID_NUMERIC(ns[1], 15216LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15221LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_235_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15221LU)); +} + +/* Mean - ns=1;i=15218 */ + +static UA_StatusCode function_tsn_app_model_236_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15218LU), + UA_NODEID_NUMERIC(ns[1], 15216LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15218LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_236_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15218LU)); +} + +/* AbsMax - ns=1;i=15220 */ + +static UA_StatusCode function_tsn_app_model_237_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15220LU), + UA_NODEID_NUMERIC(ns[1], 15216LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15220LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_237_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15220LU)); +} + +/* Ms - ns=1;i=15222 */ + +static UA_StatusCode function_tsn_app_model_238_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15222LU), + UA_NODEID_NUMERIC(ns[1], 15216LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15222LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_238_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15222LU)); +} + +/* Min - ns=1;i=15217 */ + +static UA_StatusCode function_tsn_app_model_239_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15217LU), + UA_NODEID_NUMERIC(ns[1], 15216LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15217LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_239_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15217LU)); +} + +/* Max - ns=1;i=15004 */ + +static UA_StatusCode function_tsn_app_model_240_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15004LU), + UA_NODEID_NUMERIC(ns[1], 15001LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15004LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_240_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15004LU)); +} + +/* Ms - ns=1;i=15007 */ + +static UA_StatusCode function_tsn_app_model_241_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15007LU), + UA_NODEID_NUMERIC(ns[1], 15001LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15007LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_241_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15007LU)); +} + +/* TrafficLatencyStats - ns=1;i=15252 */ + +static UA_StatusCode function_tsn_app_model_242_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15252LU), + UA_NODEID_NUMERIC(ns[1], 15245LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyStats"), + UA_NODEID_NUMERIC(ns[1], 15001LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_242_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15252LU)); +} + +/* AbsMax - ns=1;i=15256 */ + +static UA_StatusCode function_tsn_app_model_243_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15256LU), + UA_NODEID_NUMERIC(ns[1], 15252LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_243_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15256LU)); +} + +/* Max - ns=1;i=15255 */ + +static UA_StatusCode function_tsn_app_model_244_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15255LU), + UA_NODEID_NUMERIC(ns[1], 15252LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_244_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15255LU)); +} + +/* Min - ns=1;i=15253 */ + +static UA_StatusCode function_tsn_app_model_245_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15253LU), + UA_NODEID_NUMERIC(ns[1], 15252LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_245_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15253LU)); +} + +/* Variance - ns=1;i=15259 */ + +static UA_StatusCode function_tsn_app_model_246_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15259LU), + UA_NODEID_NUMERIC(ns[1], 15252LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_246_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15259LU)); +} + +/* Ms - ns=1;i=15258 */ + +static UA_StatusCode function_tsn_app_model_247_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15258LU), + UA_NODEID_NUMERIC(ns[1], 15252LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_247_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15258LU)); +} + +/* Mean - ns=1;i=15254 */ + +static UA_StatusCode function_tsn_app_model_248_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15254LU), + UA_NODEID_NUMERIC(ns[1], 15252LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_248_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15254LU)); +} + +/* AbsMin - ns=1;i=15257 */ + +static UA_StatusCode function_tsn_app_model_249_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15257LU), + UA_NODEID_NUMERIC(ns[1], 15252LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_249_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15257LU)); +} + +/* TotalTimeStats - ns=1;i=15330 */ + +static UA_StatusCode function_tsn_app_model_250_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TotalTimeStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Total time statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15330LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TotalTimeStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_250_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15330LU)); +} + +/* Ms - ns=1;i=15336 */ + +static UA_StatusCode function_tsn_app_model_251_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15336LU), + UA_NODEID_NUMERIC(ns[1], 15330LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_251_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15336LU)); +} + +/* Variance - ns=1;i=15337 */ + +static UA_StatusCode function_tsn_app_model_252_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15337LU), + UA_NODEID_NUMERIC(ns[1], 15330LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_252_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15337LU)); +} + +/* AbsMax - ns=1;i=15334 */ + +static UA_StatusCode function_tsn_app_model_253_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15334LU), + UA_NODEID_NUMERIC(ns[1], 15330LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_253_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15334LU)); +} + +/* Max - ns=1;i=15333 */ + +static UA_StatusCode function_tsn_app_model_254_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15333LU), + UA_NODEID_NUMERIC(ns[1], 15330LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_254_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15333LU)); +} + +/* AbsMin - ns=1;i=15335 */ + +static UA_StatusCode function_tsn_app_model_255_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15335LU), + UA_NODEID_NUMERIC(ns[1], 15330LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_255_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15335LU)); +} + +/* Min - ns=1;i=15331 */ + +static UA_StatusCode function_tsn_app_model_256_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15331LU), + UA_NODEID_NUMERIC(ns[1], 15330LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_256_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15331LU)); +} + +/* Mean - ns=1;i=15332 */ + +static UA_StatusCode function_tsn_app_model_257_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15332LU), + UA_NODEID_NUMERIC(ns[1], 15330LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_257_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15332LU)); +} + +/* ProcTimeStats - ns=1;i=15318 */ + +static UA_StatusCode function_tsn_app_model_258_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "ProcTimeStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Processing time statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15318LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "ProcTimeStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_258_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15318LU)); +} + +/* Max - ns=1;i=15321 */ + +static UA_StatusCode function_tsn_app_model_259_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15321LU), + UA_NODEID_NUMERIC(ns[1], 15318LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_259_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15321LU)); +} + +/* Mean - ns=1;i=15320 */ + +static UA_StatusCode function_tsn_app_model_260_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15320LU), + UA_NODEID_NUMERIC(ns[1], 15318LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_260_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15320LU)); +} + +/* Variance - ns=1;i=15325 */ + +static UA_StatusCode function_tsn_app_model_261_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15325LU), + UA_NODEID_NUMERIC(ns[1], 15318LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_261_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15325LU)); +} + +/* Min - ns=1;i=15319 */ + +static UA_StatusCode function_tsn_app_model_262_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15319LU), + UA_NODEID_NUMERIC(ns[1], 15318LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_262_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15319LU)); +} + +/* Ms - ns=1;i=15324 */ + +static UA_StatusCode function_tsn_app_model_263_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15324LU), + UA_NODEID_NUMERIC(ns[1], 15318LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_263_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15324LU)); +} + +/* AbsMax - ns=1;i=15322 */ + +static UA_StatusCode function_tsn_app_model_264_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15322LU), + UA_NODEID_NUMERIC(ns[1], 15318LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_264_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15322LU)); +} + +/* AbsMin - ns=1;i=15323 */ + +static UA_StatusCode function_tsn_app_model_265_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15323LU), + UA_NODEID_NUMERIC(ns[1], 15318LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_265_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15323LU)); +} + +/* SchedErrStats - ns=1;i=15306 */ + +static UA_StatusCode function_tsn_app_model_266_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SchedErrStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedErrStats"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15306LU), + UA_NODEID_NUMERIC(ns[1], 15298LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedErrStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_266_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15306LU)); +} + +/* Max - ns=1;i=15309 */ + +static UA_StatusCode function_tsn_app_model_267_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15309LU), + UA_NODEID_NUMERIC(ns[1], 15306LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_267_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15309LU)); +} + +/* Ms - ns=1;i=15312 */ + +static UA_StatusCode function_tsn_app_model_268_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15312LU), + UA_NODEID_NUMERIC(ns[1], 15306LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_268_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15312LU)); +} + +/* Min - ns=1;i=15307 */ + +static UA_StatusCode function_tsn_app_model_269_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15307LU), + UA_NODEID_NUMERIC(ns[1], 15306LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_269_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15307LU)); +} + +/* Mean - ns=1;i=15308 */ + +static UA_StatusCode function_tsn_app_model_270_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15308LU), + UA_NODEID_NUMERIC(ns[1], 15306LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_270_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15308LU)); +} + +/* AbsMin - ns=1;i=15311 */ + +static UA_StatusCode function_tsn_app_model_271_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15311LU), + UA_NODEID_NUMERIC(ns[1], 15306LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_271_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15311LU)); +} + +/* Variance - ns=1;i=15313 */ + +static UA_StatusCode function_tsn_app_model_272_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15313LU), + UA_NODEID_NUMERIC(ns[1], 15306LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_272_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15313LU)); +} + +/* AbsMax - ns=1;i=15310 */ + +static UA_StatusCode function_tsn_app_model_273_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15310LU), + UA_NODEID_NUMERIC(ns[1], 15306LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_273_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15310LU)); +} + +/* SchedErrStats - ns=1;i=15204 */ + +static UA_StatusCode function_tsn_app_model_274_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SchedErrStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedErrStats"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15204LU), + UA_NODEID_NUMERIC(ns[1], 15196LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedErrStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15204LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_274_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15204LU)); +} + +/* Mean - ns=1;i=15206 */ + +static UA_StatusCode function_tsn_app_model_275_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15206LU), + UA_NODEID_NUMERIC(ns[1], 15204LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15206LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_275_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15206LU)); +} + +/* Max - ns=1;i=15207 */ + +static UA_StatusCode function_tsn_app_model_276_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15207LU), + UA_NODEID_NUMERIC(ns[1], 15204LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15207LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_276_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15207LU)); +} + +/* AbsMin - ns=1;i=15209 */ + +static UA_StatusCode function_tsn_app_model_277_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15209LU), + UA_NODEID_NUMERIC(ns[1], 15204LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15209LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_277_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15209LU)); +} + +/* Min - ns=1;i=15205 */ + +static UA_StatusCode function_tsn_app_model_278_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15205LU), + UA_NODEID_NUMERIC(ns[1], 15204LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15205LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_278_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15205LU)); +} + +/* Ms - ns=1;i=15210 */ + +static UA_StatusCode function_tsn_app_model_279_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15210LU), + UA_NODEID_NUMERIC(ns[1], 15204LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15210LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_279_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15210LU)); +} + +/* AbsMax - ns=1;i=15208 */ + +static UA_StatusCode function_tsn_app_model_280_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15208LU), + UA_NODEID_NUMERIC(ns[1], 15204LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15208LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_280_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15208LU)); +} + +/* Variance - ns=1;i=15211 */ + +static UA_StatusCode function_tsn_app_model_281_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15211LU), + UA_NODEID_NUMERIC(ns[1], 15204LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15211LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_281_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15211LU)); +} + +/* SchedErrStats - ns=1;i=15024 */ + +static UA_StatusCode function_tsn_app_model_282_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "SchedErrStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "SchedErrStats"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15024LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "SchedErrStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15024LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_282_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15024LU)); +} + +/* Max - ns=1;i=15027 */ + +static UA_StatusCode function_tsn_app_model_283_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15027LU), + UA_NODEID_NUMERIC(ns[1], 15024LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15027LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_283_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15027LU)); +} + +/* Mean - ns=1;i=15026 */ + +static UA_StatusCode function_tsn_app_model_284_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15026LU), + UA_NODEID_NUMERIC(ns[1], 15024LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15026LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_284_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15026LU)); +} + +/* Ms - ns=1;i=15030 */ + +static UA_StatusCode function_tsn_app_model_285_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15030LU), + UA_NODEID_NUMERIC(ns[1], 15024LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15030LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_285_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15030LU)); +} + +/* Min - ns=1;i=15025 */ + +static UA_StatusCode function_tsn_app_model_286_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15025LU), + UA_NODEID_NUMERIC(ns[1], 15024LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15025LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_286_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15025LU)); +} + +/* Variance - ns=1;i=15031 */ + +static UA_StatusCode function_tsn_app_model_287_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15031LU), + UA_NODEID_NUMERIC(ns[1], 15024LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15031LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_287_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15031LU)); +} + +/* AbsMax - ns=1;i=15028 */ + +static UA_StatusCode function_tsn_app_model_288_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15028LU), + UA_NODEID_NUMERIC(ns[1], 15024LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15028LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_288_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15028LU)); +} + +/* AbsMin - ns=1;i=15029 */ + +static UA_StatusCode function_tsn_app_model_289_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15029LU), + UA_NODEID_NUMERIC(ns[1], 15024LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15029LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_289_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15029LU)); +} + +/* TrafficLatencyStats - ns=1;i=15111 */ + +static UA_StatusCode function_tsn_app_model_290_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15111LU), + UA_NODEID_NUMERIC(ns[1], 15104LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyStats"), + UA_NODEID_NUMERIC(ns[1], 15001LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15111LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_290_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15111LU)); +} + +/* Max - ns=1;i=15114 */ + +static UA_StatusCode function_tsn_app_model_291_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15114LU), + UA_NODEID_NUMERIC(ns[1], 15111LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15114LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_291_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15114LU)); +} + +/* Mean - ns=1;i=15113 */ + +static UA_StatusCode function_tsn_app_model_292_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15113LU), + UA_NODEID_NUMERIC(ns[1], 15111LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15113LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_292_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15113LU)); +} + +/* Ms - ns=1;i=15117 */ + +static UA_StatusCode function_tsn_app_model_293_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15117LU), + UA_NODEID_NUMERIC(ns[1], 15111LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15117LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_293_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15117LU)); +} + +/* Variance - ns=1;i=15118 */ + +static UA_StatusCode function_tsn_app_model_294_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15118LU), + UA_NODEID_NUMERIC(ns[1], 15111LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15118LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_294_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15118LU)); +} + +/* AbsMin - ns=1;i=15116 */ + +static UA_StatusCode function_tsn_app_model_295_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15116LU), + UA_NODEID_NUMERIC(ns[1], 15111LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15116LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_295_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15116LU)); +} + +/* AbsMax - ns=1;i=15115 */ + +static UA_StatusCode function_tsn_app_model_296_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15115LU), + UA_NODEID_NUMERIC(ns[1], 15111LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15115LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_296_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15115LU)); +} + +/* Min - ns=1;i=15112 */ + +static UA_StatusCode function_tsn_app_model_297_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15112LU), + UA_NODEID_NUMERIC(ns[1], 15111LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15112LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_297_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15112LU)); +} + +/* AbsMin - ns=1;i=15006 */ + +static UA_StatusCode function_tsn_app_model_298_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15006LU), + UA_NODEID_NUMERIC(ns[1], 15001LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15006LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_298_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15006LU)); +} + +/* Mean - ns=1;i=15003 */ + +static UA_StatusCode function_tsn_app_model_299_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15003LU), + UA_NODEID_NUMERIC(ns[1], 15001LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15003LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_299_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15003LU)); +} + +/* AbsMax - ns=1;i=15005 */ + +static UA_StatusCode function_tsn_app_model_300_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15005LU), + UA_NODEID_NUMERIC(ns[1], 15001LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15005LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_300_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15005LU)); +} + +/* TotalTimeStats - ns=1;i=15048 */ + +static UA_StatusCode function_tsn_app_model_301_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TotalTimeStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Total time statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15048LU), + UA_NODEID_NUMERIC(ns[1], 15016LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TotalTimeStats"), UA_NODEID_NUMERIC(ns[1], 15001LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, + NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15048LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_301_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15048LU)); +} + +/* Ms - ns=1;i=15054 */ + +static UA_StatusCode function_tsn_app_model_302_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15054LU), + UA_NODEID_NUMERIC(ns[1], 15048LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15054LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_302_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15054LU)); +} + +/* Min - ns=1;i=15049 */ + +static UA_StatusCode function_tsn_app_model_303_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15049LU), + UA_NODEID_NUMERIC(ns[1], 15048LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15049LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_303_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15049LU)); +} + +/* Max - ns=1;i=15051 */ + +static UA_StatusCode function_tsn_app_model_304_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15051LU), + UA_NODEID_NUMERIC(ns[1], 15048LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15051LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_304_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15051LU)); +} + +/* AbsMax - ns=1;i=15052 */ + +static UA_StatusCode function_tsn_app_model_305_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15052LU), + UA_NODEID_NUMERIC(ns[1], 15048LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15052LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_305_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15052LU)); +} + +/* AbsMin - ns=1;i=15053 */ + +static UA_StatusCode function_tsn_app_model_306_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15053LU), + UA_NODEID_NUMERIC(ns[1], 15048LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15053LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_306_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15053LU)); +} + +/* Mean - ns=1;i=15050 */ + +static UA_StatusCode function_tsn_app_model_307_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15050LU), + UA_NODEID_NUMERIC(ns[1], 15048LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15050LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_307_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15050LU)); +} + +/* Variance - ns=1;i=15055 */ + +static UA_StatusCode function_tsn_app_model_308_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15055LU), + UA_NODEID_NUMERIC(ns[1], 15048LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15055LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_308_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15055LU)); +} + +/* TrafficLatencyStats - ns=1;i=15092 */ + +static UA_StatusCode function_tsn_app_model_309_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15092LU), + UA_NODEID_NUMERIC(ns[1], 15085LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyStats"), + UA_NODEID_NUMERIC(ns[1], 15001LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15092LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_309_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15092LU)); +} + +/* Min - ns=1;i=15093 */ + +static UA_StatusCode function_tsn_app_model_310_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15093LU), + UA_NODEID_NUMERIC(ns[1], 15092LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15093LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_310_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15093LU)); +} + +/* AbsMax - ns=1;i=15096 */ + +static UA_StatusCode function_tsn_app_model_311_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15096LU), + UA_NODEID_NUMERIC(ns[1], 15092LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15096LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_311_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15096LU)); +} + +/* Mean - ns=1;i=15094 */ + +static UA_StatusCode function_tsn_app_model_312_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15094LU), + UA_NODEID_NUMERIC(ns[1], 15092LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15094LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_312_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15094LU)); +} + +/* Variance - ns=1;i=15099 */ + +static UA_StatusCode function_tsn_app_model_313_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15099LU), + UA_NODEID_NUMERIC(ns[1], 15092LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15099LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_313_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15099LU)); +} + +/* AbsMin - ns=1;i=15097 */ + +static UA_StatusCode function_tsn_app_model_314_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15097LU), + UA_NODEID_NUMERIC(ns[1], 15092LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15097LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_314_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15097LU)); +} + +/* Ms - ns=1;i=15098 */ + +static UA_StatusCode function_tsn_app_model_315_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15098LU), + UA_NODEID_NUMERIC(ns[1], 15092LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15098LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_315_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15098LU)); +} + +/* Max - ns=1;i=15095 */ + +static UA_StatusCode function_tsn_app_model_316_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15095LU), + UA_NODEID_NUMERIC(ns[1], 15092LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15095LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_316_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15095LU)); +} + +/* TrafficLatencyStats - ns=1;i=15271 */ + +static UA_StatusCode function_tsn_app_model_317_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15271LU), + UA_NODEID_NUMERIC(ns[1], 15264LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyStats"), + UA_NODEID_NUMERIC(ns[1], 15001LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_317_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15271LU)); +} + +/* Variance - ns=1;i=15278 */ + +static UA_StatusCode function_tsn_app_model_318_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15278LU), + UA_NODEID_NUMERIC(ns[1], 15271LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_318_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15278LU)); +} + +/* AbsMin - ns=1;i=15276 */ + +static UA_StatusCode function_tsn_app_model_319_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15276LU), + UA_NODEID_NUMERIC(ns[1], 15271LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_319_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15276LU)); +} + +/* Max - ns=1;i=15274 */ + +static UA_StatusCode function_tsn_app_model_320_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15274LU), + UA_NODEID_NUMERIC(ns[1], 15271LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_320_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15274LU)); +} + +/* Min - ns=1;i=15272 */ + +static UA_StatusCode function_tsn_app_model_321_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15272LU), + UA_NODEID_NUMERIC(ns[1], 15271LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_321_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15272LU)); +} + +/* Ms - ns=1;i=15277 */ + +static UA_StatusCode function_tsn_app_model_322_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15277LU), + UA_NODEID_NUMERIC(ns[1], 15271LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_322_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15277LU)); +} + +/* Mean - ns=1;i=15273 */ + +static UA_StatusCode function_tsn_app_model_323_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15273LU), + UA_NODEID_NUMERIC(ns[1], 15271LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_323_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15273LU)); +} + +/* AbsMax - ns=1;i=15275 */ + +static UA_StatusCode function_tsn_app_model_324_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15275LU), + UA_NODEID_NUMERIC(ns[1], 15271LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_324_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15275LU)); +} + +/* TrafficLatencyStats - ns=1;i=15067 */ + +static UA_StatusCode function_tsn_app_model_325_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15067LU), + UA_NODEID_NUMERIC(ns[1], 15060LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyStats"), + UA_NODEID_NUMERIC(ns[1], 15001LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15067LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_325_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15067LU)); +} + +/* Min - ns=1;i=15068 */ + +static UA_StatusCode function_tsn_app_model_326_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15068LU), + UA_NODEID_NUMERIC(ns[1], 15067LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15068LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_326_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15068LU)); +} + +/* Variance - ns=1;i=15074 */ + +static UA_StatusCode function_tsn_app_model_327_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15074LU), + UA_NODEID_NUMERIC(ns[1], 15067LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15074LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_327_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15074LU)); +} + +/* Mean - ns=1;i=15069 */ + +static UA_StatusCode function_tsn_app_model_328_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15069LU), + UA_NODEID_NUMERIC(ns[1], 15067LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15069LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_328_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15069LU)); +} + +/* AbsMax - ns=1;i=15071 */ + +static UA_StatusCode function_tsn_app_model_329_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15071LU), + UA_NODEID_NUMERIC(ns[1], 15067LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15071LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_329_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15071LU)); +} + +/* Max - ns=1;i=15070 */ + +static UA_StatusCode function_tsn_app_model_330_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15070LU), + UA_NODEID_NUMERIC(ns[1], 15067LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15070LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_330_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15070LU)); +} + +/* Ms - ns=1;i=15073 */ + +static UA_StatusCode function_tsn_app_model_331_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15073LU), + UA_NODEID_NUMERIC(ns[1], 15067LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15073LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_331_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15073LU)); +} + +/* AbsMin - ns=1;i=15072 */ + +static UA_StatusCode function_tsn_app_model_332_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15072LU), + UA_NODEID_NUMERIC(ns[1], 15067LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15072LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_332_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15072LU)); +} + +/* TrafficLatencyStats - ns=1;i=15169 */ + +static UA_StatusCode function_tsn_app_model_333_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_ObjectAttributes attr = UA_ObjectAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("", "TrafficLatencyStats"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Traffic latency statistics"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(ns[1], 15169LU), + UA_NODEID_NUMERIC(ns[1], 15162LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "TrafficLatencyStats"), + UA_NODEID_NUMERIC(ns[1], 15001LU), (const UA_NodeAttributes *)&attr, + &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15169LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_333_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15169LU)); +} + +/* Max - ns=1;i=15172 */ + +static UA_StatusCode function_tsn_app_model_334_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Max"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15172LU), + UA_NODEID_NUMERIC(ns[1], 15169LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Max"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15172LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_334_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15172LU)); +} + +/* Ms - ns=1;i=15175 */ + +static UA_StatusCode function_tsn_app_model_335_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Ms"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean square of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15175LU), + UA_NODEID_NUMERIC(ns[1], 15169LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Ms"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15175LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_335_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15175LU)); +} + +/* Mean - ns=1;i=15171 */ + +static UA_StatusCode function_tsn_app_model_336_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Mean"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Mean of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15171LU), + UA_NODEID_NUMERIC(ns[1], 15169LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Mean"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15171LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_336_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15171LU)); +} + +/* Min - ns=1;i=15170 */ + +static UA_StatusCode function_tsn_app_model_337_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Min"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15170LU), + UA_NODEID_NUMERIC(ns[1], 15169LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Min"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15170LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_337_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15170LU)); +} + +/* Variance - ns=1;i=15176 */ + +static UA_StatusCode function_tsn_app_model_338_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 9LU); + attr.displayName = UA_LOCALIZEDTEXT("", "Variance"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Variance of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15176LU), + UA_NODEID_NUMERIC(ns[1], 15169LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "Variance"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15176LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_338_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15176LU)); +} + +/* AbsMax - ns=1;i=15173 */ + +static UA_StatusCode function_tsn_app_model_339_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMax"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute maximum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15173LU), + UA_NODEID_NUMERIC(ns[1], 15169LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMax"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15173LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_339_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15173LU)); +} + +/* AbsMin - ns=1;i=15174 */ + +static UA_StatusCode function_tsn_app_model_340_begin(UA_Server *server, UA_UInt16 *ns) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.minimumSamplingInterval = 0.000000; + attr.userAccessLevel = 1; + attr.accessLevel = 1; + /* Value rank inherited */ + attr.valueRank = -1; + attr.dataType = UA_NODEID_NUMERIC(ns[0], 6LU); + attr.displayName = UA_LOCALIZEDTEXT("", "AbsMin"); +#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS + attr.description = UA_LOCALIZEDTEXT("", "Absolute minimum of the values"); +#endif + retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_VARIABLE, UA_NODEID_NUMERIC(ns[1], 15174LU), + UA_NODEID_NUMERIC(ns[1], 15169LU), UA_NODEID_NUMERIC(ns[0], 47LU), + UA_QUALIFIEDNAME(ns[1], "AbsMin"), UA_NODEID_NUMERIC(ns[0], 63LU), + (const UA_NodeAttributes *)&attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, NULL); + retVal |= UA_Server_addReference(server, UA_NODEID_NUMERIC(ns[1], 15174LU), UA_NODEID_NUMERIC(ns[0], 37LU), + UA_EXPANDEDNODEID_NUMERIC(ns[0], 78LU), true); + return retVal; +} + +static UA_StatusCode function_tsn_app_model_340_finish(UA_Server *server, UA_UInt16 *ns) +{ + return UA_Server_addNode_finish(server, UA_NODEID_NUMERIC(ns[1], 15174LU)); +} + +UA_StatusCode tsn_app_model(UA_Server *server) +{ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + /* Use namespace ids generated by the server */ + UA_UInt16 ns[2]; + ns[0] = UA_Server_addNamespace(server, "http://opcfoundation.org/UA/"); + ns[1] = UA_Server_addNamespace(server, "https://opcua/UA/Tsn/"); + + /* Load custom datatype definitions into the server */ + bool dummy = (!(retVal = function_tsn_app_model_0_begin(server, ns)) && + !(retVal = function_tsn_app_model_1_begin(server, ns)) && + !(retVal = function_tsn_app_model_2_begin(server, ns)) && + !(retVal = function_tsn_app_model_3_begin(server, ns)) && + !(retVal = function_tsn_app_model_4_begin(server, ns)) && + !(retVal = function_tsn_app_model_5_begin(server, ns)) && + !(retVal = function_tsn_app_model_6_begin(server, ns)) && + !(retVal = function_tsn_app_model_7_begin(server, ns)) && + !(retVal = function_tsn_app_model_8_begin(server, ns)) && + !(retVal = function_tsn_app_model_9_begin(server, ns)) && + !(retVal = function_tsn_app_model_10_begin(server, ns)) && + !(retVal = function_tsn_app_model_11_begin(server, ns)) && + !(retVal = function_tsn_app_model_12_begin(server, ns)) && + !(retVal = function_tsn_app_model_13_begin(server, ns)) && + !(retVal = function_tsn_app_model_14_begin(server, ns)) && + !(retVal = function_tsn_app_model_15_begin(server, ns)) && + !(retVal = function_tsn_app_model_16_begin(server, ns)) && + !(retVal = function_tsn_app_model_17_begin(server, ns)) && + !(retVal = function_tsn_app_model_18_begin(server, ns)) && + !(retVal = function_tsn_app_model_19_begin(server, ns)) && + !(retVal = function_tsn_app_model_20_begin(server, ns)) && + !(retVal = function_tsn_app_model_21_begin(server, ns)) && + !(retVal = function_tsn_app_model_22_begin(server, ns)) && + !(retVal = function_tsn_app_model_23_begin(server, ns)) && + !(retVal = function_tsn_app_model_24_begin(server, ns)) && + !(retVal = function_tsn_app_model_25_begin(server, ns)) && + !(retVal = function_tsn_app_model_26_begin(server, ns)) && + !(retVal = function_tsn_app_model_27_begin(server, ns)) && + !(retVal = function_tsn_app_model_28_begin(server, ns)) && + !(retVal = function_tsn_app_model_29_begin(server, ns)) && + !(retVal = function_tsn_app_model_30_begin(server, ns)) && + !(retVal = function_tsn_app_model_31_begin(server, ns)) && + !(retVal = function_tsn_app_model_32_begin(server, ns)) && + !(retVal = function_tsn_app_model_33_begin(server, ns)) && + !(retVal = function_tsn_app_model_34_begin(server, ns)) && + !(retVal = function_tsn_app_model_35_begin(server, ns)) && + !(retVal = function_tsn_app_model_36_begin(server, ns)) && + !(retVal = function_tsn_app_model_37_begin(server, ns)) && + !(retVal = function_tsn_app_model_38_begin(server, ns)) && + !(retVal = function_tsn_app_model_39_begin(server, ns)) && + !(retVal = function_tsn_app_model_40_begin(server, ns)) && + !(retVal = function_tsn_app_model_41_begin(server, ns)) && + !(retVal = function_tsn_app_model_42_begin(server, ns)) && + !(retVal = function_tsn_app_model_43_begin(server, ns)) && + !(retVal = function_tsn_app_model_44_begin(server, ns)) && + !(retVal = function_tsn_app_model_45_begin(server, ns)) && + !(retVal = function_tsn_app_model_46_begin(server, ns)) && + !(retVal = function_tsn_app_model_47_begin(server, ns)) && + !(retVal = function_tsn_app_model_48_begin(server, ns)) && + !(retVal = function_tsn_app_model_49_begin(server, ns)) && + !(retVal = function_tsn_app_model_50_begin(server, ns)) && + !(retVal = function_tsn_app_model_51_begin(server, ns)) && + !(retVal = function_tsn_app_model_52_begin(server, ns)) && + !(retVal = function_tsn_app_model_53_begin(server, ns)) && + !(retVal = function_tsn_app_model_54_begin(server, ns)) && + !(retVal = function_tsn_app_model_55_begin(server, ns)) && + !(retVal = function_tsn_app_model_56_begin(server, ns)) && + !(retVal = function_tsn_app_model_57_begin(server, ns)) && + !(retVal = function_tsn_app_model_58_begin(server, ns)) && + !(retVal = function_tsn_app_model_59_begin(server, ns)) && + !(retVal = function_tsn_app_model_60_begin(server, ns)) && + !(retVal = function_tsn_app_model_61_begin(server, ns)) && + !(retVal = function_tsn_app_model_62_begin(server, ns)) && + !(retVal = function_tsn_app_model_63_begin(server, ns)) && + !(retVal = function_tsn_app_model_64_begin(server, ns)) && + !(retVal = function_tsn_app_model_65_begin(server, ns)) && + !(retVal = function_tsn_app_model_66_begin(server, ns)) && + !(retVal = function_tsn_app_model_67_begin(server, ns)) && + !(retVal = function_tsn_app_model_68_begin(server, ns)) && + !(retVal = function_tsn_app_model_69_begin(server, ns)) && + !(retVal = function_tsn_app_model_70_begin(server, ns)) && + !(retVal = function_tsn_app_model_71_begin(server, ns)) && + !(retVal = function_tsn_app_model_72_begin(server, ns)) && + !(retVal = function_tsn_app_model_73_begin(server, ns)) && + !(retVal = function_tsn_app_model_74_begin(server, ns)) && + !(retVal = function_tsn_app_model_75_begin(server, ns)) && + !(retVal = function_tsn_app_model_76_begin(server, ns)) && + !(retVal = function_tsn_app_model_77_begin(server, ns)) && + !(retVal = function_tsn_app_model_78_begin(server, ns)) && + !(retVal = function_tsn_app_model_79_begin(server, ns)) && + !(retVal = function_tsn_app_model_80_begin(server, ns)) && + !(retVal = function_tsn_app_model_81_begin(server, ns)) && + !(retVal = function_tsn_app_model_82_begin(server, ns)) && + !(retVal = function_tsn_app_model_83_begin(server, ns)) && + !(retVal = function_tsn_app_model_84_begin(server, ns)) && + !(retVal = function_tsn_app_model_85_begin(server, ns)) && + !(retVal = function_tsn_app_model_86_begin(server, ns)) && + !(retVal = function_tsn_app_model_87_begin(server, ns)) && + !(retVal = function_tsn_app_model_88_begin(server, ns)) && + !(retVal = function_tsn_app_model_89_begin(server, ns)) && + !(retVal = function_tsn_app_model_90_begin(server, ns)) && + !(retVal = function_tsn_app_model_91_begin(server, ns)) && + !(retVal = function_tsn_app_model_92_begin(server, ns)) && + !(retVal = function_tsn_app_model_93_begin(server, ns)) && + !(retVal = function_tsn_app_model_94_begin(server, ns)) && + !(retVal = function_tsn_app_model_95_begin(server, ns)) && + !(retVal = function_tsn_app_model_96_begin(server, ns)) && + !(retVal = function_tsn_app_model_97_begin(server, ns)) && + !(retVal = function_tsn_app_model_98_begin(server, ns)) && + !(retVal = function_tsn_app_model_99_begin(server, ns)) && + !(retVal = function_tsn_app_model_100_begin(server, ns)) && + !(retVal = function_tsn_app_model_101_begin(server, ns)) && + !(retVal = function_tsn_app_model_102_begin(server, ns)) && + !(retVal = function_tsn_app_model_103_begin(server, ns)) && + !(retVal = function_tsn_app_model_104_begin(server, ns)) && + !(retVal = function_tsn_app_model_105_begin(server, ns)) && + !(retVal = function_tsn_app_model_106_begin(server, ns)) && + !(retVal = function_tsn_app_model_107_begin(server, ns)) && + !(retVal = function_tsn_app_model_108_begin(server, ns)) && + !(retVal = function_tsn_app_model_109_begin(server, ns)) && + !(retVal = function_tsn_app_model_110_begin(server, ns)) && + !(retVal = function_tsn_app_model_111_begin(server, ns)) && + !(retVal = function_tsn_app_model_112_begin(server, ns)) && + !(retVal = function_tsn_app_model_113_begin(server, ns)) && + !(retVal = function_tsn_app_model_114_begin(server, ns)) && + !(retVal = function_tsn_app_model_115_begin(server, ns)) && + !(retVal = function_tsn_app_model_116_begin(server, ns)) && + !(retVal = function_tsn_app_model_117_begin(server, ns)) && + !(retVal = function_tsn_app_model_118_begin(server, ns)) && + !(retVal = function_tsn_app_model_119_begin(server, ns)) && + !(retVal = function_tsn_app_model_120_begin(server, ns)) && + !(retVal = function_tsn_app_model_121_begin(server, ns)) && + !(retVal = function_tsn_app_model_122_begin(server, ns)) && + !(retVal = function_tsn_app_model_123_begin(server, ns)) && + !(retVal = function_tsn_app_model_124_begin(server, ns)) && + !(retVal = function_tsn_app_model_125_begin(server, ns)) && + !(retVal = function_tsn_app_model_126_begin(server, ns)) && + !(retVal = function_tsn_app_model_127_begin(server, ns)) && + !(retVal = function_tsn_app_model_128_begin(server, ns)) && + !(retVal = function_tsn_app_model_129_begin(server, ns)) && + !(retVal = function_tsn_app_model_130_begin(server, ns)) && + !(retVal = function_tsn_app_model_131_begin(server, ns)) && + !(retVal = function_tsn_app_model_132_begin(server, ns)) && + !(retVal = function_tsn_app_model_133_begin(server, ns)) && + !(retVal = function_tsn_app_model_134_begin(server, ns)) && + !(retVal = function_tsn_app_model_135_begin(server, ns)) && + !(retVal = function_tsn_app_model_136_begin(server, ns)) && + !(retVal = function_tsn_app_model_137_begin(server, ns)) && + !(retVal = function_tsn_app_model_138_begin(server, ns)) && + !(retVal = function_tsn_app_model_139_begin(server, ns)) && + !(retVal = function_tsn_app_model_140_begin(server, ns)) && + !(retVal = function_tsn_app_model_141_begin(server, ns)) && + !(retVal = function_tsn_app_model_142_begin(server, ns)) && + !(retVal = function_tsn_app_model_143_begin(server, ns)) && + !(retVal = function_tsn_app_model_144_begin(server, ns)) && + !(retVal = function_tsn_app_model_145_begin(server, ns)) && + !(retVal = function_tsn_app_model_146_begin(server, ns)) && + !(retVal = function_tsn_app_model_147_begin(server, ns)) && + !(retVal = function_tsn_app_model_148_begin(server, ns)) && + !(retVal = function_tsn_app_model_149_begin(server, ns)) && + !(retVal = function_tsn_app_model_150_begin(server, ns)) && + !(retVal = function_tsn_app_model_151_begin(server, ns)) && + !(retVal = function_tsn_app_model_152_begin(server, ns)) && + !(retVal = function_tsn_app_model_153_begin(server, ns)) && + !(retVal = function_tsn_app_model_154_begin(server, ns)) && + !(retVal = function_tsn_app_model_155_begin(server, ns)) && + !(retVal = function_tsn_app_model_156_begin(server, ns)) && + !(retVal = function_tsn_app_model_157_begin(server, ns)) && + !(retVal = function_tsn_app_model_158_begin(server, ns)) && + !(retVal = function_tsn_app_model_159_begin(server, ns)) && + !(retVal = function_tsn_app_model_160_begin(server, ns)) && + !(retVal = function_tsn_app_model_161_begin(server, ns)) && + !(retVal = function_tsn_app_model_162_begin(server, ns)) && + !(retVal = function_tsn_app_model_163_begin(server, ns)) && + !(retVal = function_tsn_app_model_164_begin(server, ns)) && + !(retVal = function_tsn_app_model_165_begin(server, ns)) && + !(retVal = function_tsn_app_model_166_begin(server, ns)) && + !(retVal = function_tsn_app_model_167_begin(server, ns)) && + !(retVal = function_tsn_app_model_168_begin(server, ns)) && + !(retVal = function_tsn_app_model_169_begin(server, ns)) && + !(retVal = function_tsn_app_model_170_begin(server, ns)) && + !(retVal = function_tsn_app_model_171_begin(server, ns)) && + !(retVal = function_tsn_app_model_172_begin(server, ns)) && + !(retVal = function_tsn_app_model_173_begin(server, ns)) && + !(retVal = function_tsn_app_model_174_begin(server, ns)) && + !(retVal = function_tsn_app_model_175_begin(server, ns)) && + !(retVal = function_tsn_app_model_176_begin(server, ns)) && + !(retVal = function_tsn_app_model_177_begin(server, ns)) && + !(retVal = function_tsn_app_model_178_begin(server, ns)) && + !(retVal = function_tsn_app_model_179_begin(server, ns)) && + !(retVal = function_tsn_app_model_180_begin(server, ns)) && + !(retVal = function_tsn_app_model_181_begin(server, ns)) && + !(retVal = function_tsn_app_model_182_begin(server, ns)) && + !(retVal = function_tsn_app_model_183_begin(server, ns)) && + !(retVal = function_tsn_app_model_184_begin(server, ns)) && + !(retVal = function_tsn_app_model_185_begin(server, ns)) && + !(retVal = function_tsn_app_model_186_begin(server, ns)) && + !(retVal = function_tsn_app_model_187_begin(server, ns)) && + !(retVal = function_tsn_app_model_188_begin(server, ns)) && + !(retVal = function_tsn_app_model_189_begin(server, ns)) && + !(retVal = function_tsn_app_model_190_begin(server, ns)) && + !(retVal = function_tsn_app_model_191_begin(server, ns)) && + !(retVal = function_tsn_app_model_192_begin(server, ns)) && + !(retVal = function_tsn_app_model_193_begin(server, ns)) && + !(retVal = function_tsn_app_model_194_begin(server, ns)) && + !(retVal = function_tsn_app_model_195_begin(server, ns)) && + !(retVal = function_tsn_app_model_196_begin(server, ns)) && + !(retVal = function_tsn_app_model_197_begin(server, ns)) && + !(retVal = function_tsn_app_model_198_begin(server, ns)) && + !(retVal = function_tsn_app_model_199_begin(server, ns)) && + !(retVal = function_tsn_app_model_200_begin(server, ns)) && + !(retVal = function_tsn_app_model_201_begin(server, ns)) && + !(retVal = function_tsn_app_model_202_begin(server, ns)) && + !(retVal = function_tsn_app_model_203_begin(server, ns)) && + !(retVal = function_tsn_app_model_204_begin(server, ns)) && + !(retVal = function_tsn_app_model_205_begin(server, ns)) && + !(retVal = function_tsn_app_model_206_begin(server, ns)) && + !(retVal = function_tsn_app_model_207_begin(server, ns)) && + !(retVal = function_tsn_app_model_208_begin(server, ns)) && + !(retVal = function_tsn_app_model_209_begin(server, ns)) && + !(retVal = function_tsn_app_model_210_begin(server, ns)) && + !(retVal = function_tsn_app_model_211_begin(server, ns)) && + !(retVal = function_tsn_app_model_212_begin(server, ns)) && + !(retVal = function_tsn_app_model_213_begin(server, ns)) && + !(retVal = function_tsn_app_model_214_begin(server, ns)) && + !(retVal = function_tsn_app_model_215_begin(server, ns)) && + !(retVal = function_tsn_app_model_216_begin(server, ns)) && + !(retVal = function_tsn_app_model_217_begin(server, ns)) && + !(retVal = function_tsn_app_model_218_begin(server, ns)) && + !(retVal = function_tsn_app_model_219_begin(server, ns)) && + !(retVal = function_tsn_app_model_220_begin(server, ns)) && + !(retVal = function_tsn_app_model_221_begin(server, ns)) && + !(retVal = function_tsn_app_model_222_begin(server, ns)) && + !(retVal = function_tsn_app_model_223_begin(server, ns)) && + !(retVal = function_tsn_app_model_224_begin(server, ns)) && + !(retVal = function_tsn_app_model_225_begin(server, ns)) && + !(retVal = function_tsn_app_model_226_begin(server, ns)) && + !(retVal = function_tsn_app_model_227_begin(server, ns)) && + !(retVal = function_tsn_app_model_228_begin(server, ns)) && + !(retVal = function_tsn_app_model_229_begin(server, ns)) && + !(retVal = function_tsn_app_model_230_begin(server, ns)) && + !(retVal = function_tsn_app_model_231_begin(server, ns)) && + !(retVal = function_tsn_app_model_232_begin(server, ns)) && + !(retVal = function_tsn_app_model_233_begin(server, ns)) && + !(retVal = function_tsn_app_model_234_begin(server, ns)) && + !(retVal = function_tsn_app_model_235_begin(server, ns)) && + !(retVal = function_tsn_app_model_236_begin(server, ns)) && + !(retVal = function_tsn_app_model_237_begin(server, ns)) && + !(retVal = function_tsn_app_model_238_begin(server, ns)) && + !(retVal = function_tsn_app_model_239_begin(server, ns)) && + !(retVal = function_tsn_app_model_240_begin(server, ns)) && + !(retVal = function_tsn_app_model_241_begin(server, ns)) && + !(retVal = function_tsn_app_model_242_begin(server, ns)) && + !(retVal = function_tsn_app_model_243_begin(server, ns)) && + !(retVal = function_tsn_app_model_244_begin(server, ns)) && + !(retVal = function_tsn_app_model_245_begin(server, ns)) && + !(retVal = function_tsn_app_model_246_begin(server, ns)) && + !(retVal = function_tsn_app_model_247_begin(server, ns)) && + !(retVal = function_tsn_app_model_248_begin(server, ns)) && + !(retVal = function_tsn_app_model_249_begin(server, ns)) && + !(retVal = function_tsn_app_model_250_begin(server, ns)) && + !(retVal = function_tsn_app_model_251_begin(server, ns)) && + !(retVal = function_tsn_app_model_252_begin(server, ns)) && + !(retVal = function_tsn_app_model_253_begin(server, ns)) && + !(retVal = function_tsn_app_model_254_begin(server, ns)) && + !(retVal = function_tsn_app_model_255_begin(server, ns)) && + !(retVal = function_tsn_app_model_256_begin(server, ns)) && + !(retVal = function_tsn_app_model_257_begin(server, ns)) && + !(retVal = function_tsn_app_model_258_begin(server, ns)) && + !(retVal = function_tsn_app_model_259_begin(server, ns)) && + !(retVal = function_tsn_app_model_260_begin(server, ns)) && + !(retVal = function_tsn_app_model_261_begin(server, ns)) && + !(retVal = function_tsn_app_model_262_begin(server, ns)) && + !(retVal = function_tsn_app_model_263_begin(server, ns)) && + !(retVal = function_tsn_app_model_264_begin(server, ns)) && + !(retVal = function_tsn_app_model_265_begin(server, ns)) && + !(retVal = function_tsn_app_model_266_begin(server, ns)) && + !(retVal = function_tsn_app_model_267_begin(server, ns)) && + !(retVal = function_tsn_app_model_268_begin(server, ns)) && + !(retVal = function_tsn_app_model_269_begin(server, ns)) && + !(retVal = function_tsn_app_model_270_begin(server, ns)) && + !(retVal = function_tsn_app_model_271_begin(server, ns)) && + !(retVal = function_tsn_app_model_272_begin(server, ns)) && + !(retVal = function_tsn_app_model_273_begin(server, ns)) && + !(retVal = function_tsn_app_model_274_begin(server, ns)) && + !(retVal = function_tsn_app_model_275_begin(server, ns)) && + !(retVal = function_tsn_app_model_276_begin(server, ns)) && + !(retVal = function_tsn_app_model_277_begin(server, ns)) && + !(retVal = function_tsn_app_model_278_begin(server, ns)) && + !(retVal = function_tsn_app_model_279_begin(server, ns)) && + !(retVal = function_tsn_app_model_280_begin(server, ns)) && + !(retVal = function_tsn_app_model_281_begin(server, ns)) && + !(retVal = function_tsn_app_model_282_begin(server, ns)) && + !(retVal = function_tsn_app_model_283_begin(server, ns)) && + !(retVal = function_tsn_app_model_284_begin(server, ns)) && + !(retVal = function_tsn_app_model_285_begin(server, ns)) && + !(retVal = function_tsn_app_model_286_begin(server, ns)) && + !(retVal = function_tsn_app_model_287_begin(server, ns)) && + !(retVal = function_tsn_app_model_288_begin(server, ns)) && + !(retVal = function_tsn_app_model_289_begin(server, ns)) && + !(retVal = function_tsn_app_model_290_begin(server, ns)) && + !(retVal = function_tsn_app_model_291_begin(server, ns)) && + !(retVal = function_tsn_app_model_292_begin(server, ns)) && + !(retVal = function_tsn_app_model_293_begin(server, ns)) && + !(retVal = function_tsn_app_model_294_begin(server, ns)) && + !(retVal = function_tsn_app_model_295_begin(server, ns)) && + !(retVal = function_tsn_app_model_296_begin(server, ns)) && + !(retVal = function_tsn_app_model_297_begin(server, ns)) && + !(retVal = function_tsn_app_model_298_begin(server, ns)) && + !(retVal = function_tsn_app_model_299_begin(server, ns)) && + !(retVal = function_tsn_app_model_300_begin(server, ns)) && + !(retVal = function_tsn_app_model_301_begin(server, ns)) && + !(retVal = function_tsn_app_model_302_begin(server, ns)) && + !(retVal = function_tsn_app_model_303_begin(server, ns)) && + !(retVal = function_tsn_app_model_304_begin(server, ns)) && + !(retVal = function_tsn_app_model_305_begin(server, ns)) && + !(retVal = function_tsn_app_model_306_begin(server, ns)) && + !(retVal = function_tsn_app_model_307_begin(server, ns)) && + !(retVal = function_tsn_app_model_308_begin(server, ns)) && + !(retVal = function_tsn_app_model_309_begin(server, ns)) && + !(retVal = function_tsn_app_model_310_begin(server, ns)) && + !(retVal = function_tsn_app_model_311_begin(server, ns)) && + !(retVal = function_tsn_app_model_312_begin(server, ns)) && + !(retVal = function_tsn_app_model_313_begin(server, ns)) && + !(retVal = function_tsn_app_model_314_begin(server, ns)) && + !(retVal = function_tsn_app_model_315_begin(server, ns)) && + !(retVal = function_tsn_app_model_316_begin(server, ns)) && + !(retVal = function_tsn_app_model_317_begin(server, ns)) && + !(retVal = function_tsn_app_model_318_begin(server, ns)) && + !(retVal = function_tsn_app_model_319_begin(server, ns)) && + !(retVal = function_tsn_app_model_320_begin(server, ns)) && + !(retVal = function_tsn_app_model_321_begin(server, ns)) && + !(retVal = function_tsn_app_model_322_begin(server, ns)) && + !(retVal = function_tsn_app_model_323_begin(server, ns)) && + !(retVal = function_tsn_app_model_324_begin(server, ns)) && + !(retVal = function_tsn_app_model_325_begin(server, ns)) && + !(retVal = function_tsn_app_model_326_begin(server, ns)) && + !(retVal = function_tsn_app_model_327_begin(server, ns)) && + !(retVal = function_tsn_app_model_328_begin(server, ns)) && + !(retVal = function_tsn_app_model_329_begin(server, ns)) && + !(retVal = function_tsn_app_model_330_begin(server, ns)) && + !(retVal = function_tsn_app_model_331_begin(server, ns)) && + !(retVal = function_tsn_app_model_332_begin(server, ns)) && + !(retVal = function_tsn_app_model_333_begin(server, ns)) && + !(retVal = function_tsn_app_model_334_begin(server, ns)) && + !(retVal = function_tsn_app_model_335_begin(server, ns)) && + !(retVal = function_tsn_app_model_336_begin(server, ns)) && + !(retVal = function_tsn_app_model_337_begin(server, ns)) && + !(retVal = function_tsn_app_model_338_begin(server, ns)) && + !(retVal = function_tsn_app_model_339_begin(server, ns)) && + !(retVal = function_tsn_app_model_340_begin(server, ns)) && + !(retVal = function_tsn_app_model_340_finish(server, ns)) && + !(retVal = function_tsn_app_model_339_finish(server, ns)) && + !(retVal = function_tsn_app_model_338_finish(server, ns)) && + !(retVal = function_tsn_app_model_337_finish(server, ns)) && + !(retVal = function_tsn_app_model_336_finish(server, ns)) && + !(retVal = function_tsn_app_model_335_finish(server, ns)) && + !(retVal = function_tsn_app_model_334_finish(server, ns)) && + !(retVal = function_tsn_app_model_333_finish(server, ns)) && + !(retVal = function_tsn_app_model_332_finish(server, ns)) && + !(retVal = function_tsn_app_model_331_finish(server, ns)) && + !(retVal = function_tsn_app_model_330_finish(server, ns)) && + !(retVal = function_tsn_app_model_329_finish(server, ns)) && + !(retVal = function_tsn_app_model_328_finish(server, ns)) && + !(retVal = function_tsn_app_model_327_finish(server, ns)) && + !(retVal = function_tsn_app_model_326_finish(server, ns)) && + !(retVal = function_tsn_app_model_325_finish(server, ns)) && + !(retVal = function_tsn_app_model_324_finish(server, ns)) && + !(retVal = function_tsn_app_model_323_finish(server, ns)) && + !(retVal = function_tsn_app_model_322_finish(server, ns)) && + !(retVal = function_tsn_app_model_321_finish(server, ns)) && + !(retVal = function_tsn_app_model_320_finish(server, ns)) && + !(retVal = function_tsn_app_model_319_finish(server, ns)) && + !(retVal = function_tsn_app_model_318_finish(server, ns)) && + !(retVal = function_tsn_app_model_317_finish(server, ns)) && + !(retVal = function_tsn_app_model_316_finish(server, ns)) && + !(retVal = function_tsn_app_model_315_finish(server, ns)) && + !(retVal = function_tsn_app_model_314_finish(server, ns)) && + !(retVal = function_tsn_app_model_313_finish(server, ns)) && + !(retVal = function_tsn_app_model_312_finish(server, ns)) && + !(retVal = function_tsn_app_model_311_finish(server, ns)) && + !(retVal = function_tsn_app_model_310_finish(server, ns)) && + !(retVal = function_tsn_app_model_309_finish(server, ns)) && + !(retVal = function_tsn_app_model_308_finish(server, ns)) && + !(retVal = function_tsn_app_model_307_finish(server, ns)) && + !(retVal = function_tsn_app_model_306_finish(server, ns)) && + !(retVal = function_tsn_app_model_305_finish(server, ns)) && + !(retVal = function_tsn_app_model_304_finish(server, ns)) && + !(retVal = function_tsn_app_model_303_finish(server, ns)) && + !(retVal = function_tsn_app_model_302_finish(server, ns)) && + !(retVal = function_tsn_app_model_301_finish(server, ns)) && + !(retVal = function_tsn_app_model_300_finish(server, ns)) && + !(retVal = function_tsn_app_model_299_finish(server, ns)) && + !(retVal = function_tsn_app_model_298_finish(server, ns)) && + !(retVal = function_tsn_app_model_297_finish(server, ns)) && + !(retVal = function_tsn_app_model_296_finish(server, ns)) && + !(retVal = function_tsn_app_model_295_finish(server, ns)) && + !(retVal = function_tsn_app_model_294_finish(server, ns)) && + !(retVal = function_tsn_app_model_293_finish(server, ns)) && + !(retVal = function_tsn_app_model_292_finish(server, ns)) && + !(retVal = function_tsn_app_model_291_finish(server, ns)) && + !(retVal = function_tsn_app_model_290_finish(server, ns)) && + !(retVal = function_tsn_app_model_289_finish(server, ns)) && + !(retVal = function_tsn_app_model_288_finish(server, ns)) && + !(retVal = function_tsn_app_model_287_finish(server, ns)) && + !(retVal = function_tsn_app_model_286_finish(server, ns)) && + !(retVal = function_tsn_app_model_285_finish(server, ns)) && + !(retVal = function_tsn_app_model_284_finish(server, ns)) && + !(retVal = function_tsn_app_model_283_finish(server, ns)) && + !(retVal = function_tsn_app_model_282_finish(server, ns)) && + !(retVal = function_tsn_app_model_281_finish(server, ns)) && + !(retVal = function_tsn_app_model_280_finish(server, ns)) && + !(retVal = function_tsn_app_model_279_finish(server, ns)) && + !(retVal = function_tsn_app_model_278_finish(server, ns)) && + !(retVal = function_tsn_app_model_277_finish(server, ns)) && + !(retVal = function_tsn_app_model_276_finish(server, ns)) && + !(retVal = function_tsn_app_model_275_finish(server, ns)) && + !(retVal = function_tsn_app_model_274_finish(server, ns)) && + !(retVal = function_tsn_app_model_273_finish(server, ns)) && + !(retVal = function_tsn_app_model_272_finish(server, ns)) && + !(retVal = function_tsn_app_model_271_finish(server, ns)) && + !(retVal = function_tsn_app_model_270_finish(server, ns)) && + !(retVal = function_tsn_app_model_269_finish(server, ns)) && + !(retVal = function_tsn_app_model_268_finish(server, ns)) && + !(retVal = function_tsn_app_model_267_finish(server, ns)) && + !(retVal = function_tsn_app_model_266_finish(server, ns)) && + !(retVal = function_tsn_app_model_265_finish(server, ns)) && + !(retVal = function_tsn_app_model_264_finish(server, ns)) && + !(retVal = function_tsn_app_model_263_finish(server, ns)) && + !(retVal = function_tsn_app_model_262_finish(server, ns)) && + !(retVal = function_tsn_app_model_261_finish(server, ns)) && + !(retVal = function_tsn_app_model_260_finish(server, ns)) && + !(retVal = function_tsn_app_model_259_finish(server, ns)) && + !(retVal = function_tsn_app_model_258_finish(server, ns)) && + !(retVal = function_tsn_app_model_257_finish(server, ns)) && + !(retVal = function_tsn_app_model_256_finish(server, ns)) && + !(retVal = function_tsn_app_model_255_finish(server, ns)) && + !(retVal = function_tsn_app_model_254_finish(server, ns)) && + !(retVal = function_tsn_app_model_253_finish(server, ns)) && + !(retVal = function_tsn_app_model_252_finish(server, ns)) && + !(retVal = function_tsn_app_model_251_finish(server, ns)) && + !(retVal = function_tsn_app_model_250_finish(server, ns)) && + !(retVal = function_tsn_app_model_249_finish(server, ns)) && + !(retVal = function_tsn_app_model_248_finish(server, ns)) && + !(retVal = function_tsn_app_model_247_finish(server, ns)) && + !(retVal = function_tsn_app_model_246_finish(server, ns)) && + !(retVal = function_tsn_app_model_245_finish(server, ns)) && + !(retVal = function_tsn_app_model_244_finish(server, ns)) && + !(retVal = function_tsn_app_model_243_finish(server, ns)) && + !(retVal = function_tsn_app_model_242_finish(server, ns)) && + !(retVal = function_tsn_app_model_241_finish(server, ns)) && + !(retVal = function_tsn_app_model_240_finish(server, ns)) && + !(retVal = function_tsn_app_model_239_finish(server, ns)) && + !(retVal = function_tsn_app_model_238_finish(server, ns)) && + !(retVal = function_tsn_app_model_237_finish(server, ns)) && + !(retVal = function_tsn_app_model_236_finish(server, ns)) && + !(retVal = function_tsn_app_model_235_finish(server, ns)) && + !(retVal = function_tsn_app_model_234_finish(server, ns)) && + !(retVal = function_tsn_app_model_233_finish(server, ns)) && + !(retVal = function_tsn_app_model_232_finish(server, ns)) && + !(retVal = function_tsn_app_model_231_finish(server, ns)) && + !(retVal = function_tsn_app_model_230_finish(server, ns)) && + !(retVal = function_tsn_app_model_229_finish(server, ns)) && + !(retVal = function_tsn_app_model_228_finish(server, ns)) && + !(retVal = function_tsn_app_model_227_finish(server, ns)) && + !(retVal = function_tsn_app_model_226_finish(server, ns)) && + !(retVal = function_tsn_app_model_225_finish(server, ns)) && + !(retVal = function_tsn_app_model_224_finish(server, ns)) && + !(retVal = function_tsn_app_model_223_finish(server, ns)) && + !(retVal = function_tsn_app_model_222_finish(server, ns)) && + !(retVal = function_tsn_app_model_221_finish(server, ns)) && + !(retVal = function_tsn_app_model_220_finish(server, ns)) && + !(retVal = function_tsn_app_model_219_finish(server, ns)) && + !(retVal = function_tsn_app_model_218_finish(server, ns)) && + !(retVal = function_tsn_app_model_217_finish(server, ns)) && + !(retVal = function_tsn_app_model_216_finish(server, ns)) && + !(retVal = function_tsn_app_model_215_finish(server, ns)) && + !(retVal = function_tsn_app_model_214_finish(server, ns)) && + !(retVal = function_tsn_app_model_213_finish(server, ns)) && + !(retVal = function_tsn_app_model_212_finish(server, ns)) && + !(retVal = function_tsn_app_model_211_finish(server, ns)) && + !(retVal = function_tsn_app_model_210_finish(server, ns)) && + !(retVal = function_tsn_app_model_209_finish(server, ns)) && + !(retVal = function_tsn_app_model_208_finish(server, ns)) && + !(retVal = function_tsn_app_model_207_finish(server, ns)) && + !(retVal = function_tsn_app_model_206_finish(server, ns)) && + !(retVal = function_tsn_app_model_205_finish(server, ns)) && + !(retVal = function_tsn_app_model_204_finish(server, ns)) && + !(retVal = function_tsn_app_model_203_finish(server, ns)) && + !(retVal = function_tsn_app_model_202_finish(server, ns)) && + !(retVal = function_tsn_app_model_201_finish(server, ns)) && + !(retVal = function_tsn_app_model_200_finish(server, ns)) && + !(retVal = function_tsn_app_model_199_finish(server, ns)) && + !(retVal = function_tsn_app_model_198_finish(server, ns)) && + !(retVal = function_tsn_app_model_197_finish(server, ns)) && + !(retVal = function_tsn_app_model_196_finish(server, ns)) && + !(retVal = function_tsn_app_model_195_finish(server, ns)) && + !(retVal = function_tsn_app_model_194_finish(server, ns)) && + !(retVal = function_tsn_app_model_193_finish(server, ns)) && + !(retVal = function_tsn_app_model_192_finish(server, ns)) && + !(retVal = function_tsn_app_model_191_finish(server, ns)) && + !(retVal = function_tsn_app_model_190_finish(server, ns)) && + !(retVal = function_tsn_app_model_189_finish(server, ns)) && + !(retVal = function_tsn_app_model_188_finish(server, ns)) && + !(retVal = function_tsn_app_model_187_finish(server, ns)) && + !(retVal = function_tsn_app_model_186_finish(server, ns)) && + !(retVal = function_tsn_app_model_185_finish(server, ns)) && + !(retVal = function_tsn_app_model_184_finish(server, ns)) && + !(retVal = function_tsn_app_model_183_finish(server, ns)) && + !(retVal = function_tsn_app_model_182_finish(server, ns)) && + !(retVal = function_tsn_app_model_181_finish(server, ns)) && + !(retVal = function_tsn_app_model_180_finish(server, ns)) && + !(retVal = function_tsn_app_model_179_finish(server, ns)) && + !(retVal = function_tsn_app_model_178_finish(server, ns)) && + !(retVal = function_tsn_app_model_177_finish(server, ns)) && + !(retVal = function_tsn_app_model_176_finish(server, ns)) && + !(retVal = function_tsn_app_model_175_finish(server, ns)) && + !(retVal = function_tsn_app_model_174_finish(server, ns)) && + !(retVal = function_tsn_app_model_173_finish(server, ns)) && + !(retVal = function_tsn_app_model_172_finish(server, ns)) && + !(retVal = function_tsn_app_model_171_finish(server, ns)) && + !(retVal = function_tsn_app_model_170_finish(server, ns)) && + !(retVal = function_tsn_app_model_169_finish(server, ns)) && + !(retVal = function_tsn_app_model_168_finish(server, ns)) && + !(retVal = function_tsn_app_model_167_finish(server, ns)) && + !(retVal = function_tsn_app_model_166_finish(server, ns)) && + !(retVal = function_tsn_app_model_165_finish(server, ns)) && + !(retVal = function_tsn_app_model_164_finish(server, ns)) && + !(retVal = function_tsn_app_model_163_finish(server, ns)) && + !(retVal = function_tsn_app_model_162_finish(server, ns)) && + !(retVal = function_tsn_app_model_161_finish(server, ns)) && + !(retVal = function_tsn_app_model_160_finish(server, ns)) && + !(retVal = function_tsn_app_model_159_finish(server, ns)) && + !(retVal = function_tsn_app_model_158_finish(server, ns)) && + !(retVal = function_tsn_app_model_157_finish(server, ns)) && + !(retVal = function_tsn_app_model_156_finish(server, ns)) && + !(retVal = function_tsn_app_model_155_finish(server, ns)) && + !(retVal = function_tsn_app_model_154_finish(server, ns)) && + !(retVal = function_tsn_app_model_153_finish(server, ns)) && + !(retVal = function_tsn_app_model_152_finish(server, ns)) && + !(retVal = function_tsn_app_model_151_finish(server, ns)) && + !(retVal = function_tsn_app_model_150_finish(server, ns)) && + !(retVal = function_tsn_app_model_149_finish(server, ns)) && + !(retVal = function_tsn_app_model_148_finish(server, ns)) && + !(retVal = function_tsn_app_model_147_finish(server, ns)) && + !(retVal = function_tsn_app_model_146_finish(server, ns)) && + !(retVal = function_tsn_app_model_145_finish(server, ns)) && + !(retVal = function_tsn_app_model_144_finish(server, ns)) && + !(retVal = function_tsn_app_model_143_finish(server, ns)) && + !(retVal = function_tsn_app_model_142_finish(server, ns)) && + !(retVal = function_tsn_app_model_141_finish(server, ns)) && + !(retVal = function_tsn_app_model_140_finish(server, ns)) && + !(retVal = function_tsn_app_model_139_finish(server, ns)) && + !(retVal = function_tsn_app_model_138_finish(server, ns)) && + !(retVal = function_tsn_app_model_137_finish(server, ns)) && + !(retVal = function_tsn_app_model_136_finish(server, ns)) && + !(retVal = function_tsn_app_model_135_finish(server, ns)) && + !(retVal = function_tsn_app_model_134_finish(server, ns)) && + !(retVal = function_tsn_app_model_133_finish(server, ns)) && + !(retVal = function_tsn_app_model_132_finish(server, ns)) && + !(retVal = function_tsn_app_model_131_finish(server, ns)) && + !(retVal = function_tsn_app_model_130_finish(server, ns)) && + !(retVal = function_tsn_app_model_129_finish(server, ns)) && + !(retVal = function_tsn_app_model_128_finish(server, ns)) && + !(retVal = function_tsn_app_model_127_finish(server, ns)) && + !(retVal = function_tsn_app_model_126_finish(server, ns)) && + !(retVal = function_tsn_app_model_125_finish(server, ns)) && + !(retVal = function_tsn_app_model_124_finish(server, ns)) && + !(retVal = function_tsn_app_model_123_finish(server, ns)) && + !(retVal = function_tsn_app_model_122_finish(server, ns)) && + !(retVal = function_tsn_app_model_121_finish(server, ns)) && + !(retVal = function_tsn_app_model_120_finish(server, ns)) && + !(retVal = function_tsn_app_model_119_finish(server, ns)) && + !(retVal = function_tsn_app_model_118_finish(server, ns)) && + !(retVal = function_tsn_app_model_117_finish(server, ns)) && + !(retVal = function_tsn_app_model_116_finish(server, ns)) && + !(retVal = function_tsn_app_model_115_finish(server, ns)) && + !(retVal = function_tsn_app_model_114_finish(server, ns)) && + !(retVal = function_tsn_app_model_113_finish(server, ns)) && + !(retVal = function_tsn_app_model_112_finish(server, ns)) && + !(retVal = function_tsn_app_model_111_finish(server, ns)) && + !(retVal = function_tsn_app_model_110_finish(server, ns)) && + !(retVal = function_tsn_app_model_109_finish(server, ns)) && + !(retVal = function_tsn_app_model_108_finish(server, ns)) && + !(retVal = function_tsn_app_model_107_finish(server, ns)) && + !(retVal = function_tsn_app_model_106_finish(server, ns)) && + !(retVal = function_tsn_app_model_105_finish(server, ns)) && + !(retVal = function_tsn_app_model_104_finish(server, ns)) && + !(retVal = function_tsn_app_model_103_finish(server, ns)) && + !(retVal = function_tsn_app_model_102_finish(server, ns)) && + !(retVal = function_tsn_app_model_101_finish(server, ns)) && + !(retVal = function_tsn_app_model_100_finish(server, ns)) && + !(retVal = function_tsn_app_model_99_finish(server, ns)) && + !(retVal = function_tsn_app_model_98_finish(server, ns)) && + !(retVal = function_tsn_app_model_97_finish(server, ns)) && + !(retVal = function_tsn_app_model_96_finish(server, ns)) && + !(retVal = function_tsn_app_model_95_finish(server, ns)) && + !(retVal = function_tsn_app_model_94_finish(server, ns)) && + !(retVal = function_tsn_app_model_93_finish(server, ns)) && + !(retVal = function_tsn_app_model_92_finish(server, ns)) && + !(retVal = function_tsn_app_model_91_finish(server, ns)) && + !(retVal = function_tsn_app_model_90_finish(server, ns)) && + !(retVal = function_tsn_app_model_89_finish(server, ns)) && + !(retVal = function_tsn_app_model_88_finish(server, ns)) && + !(retVal = function_tsn_app_model_87_finish(server, ns)) && + !(retVal = function_tsn_app_model_86_finish(server, ns)) && + !(retVal = function_tsn_app_model_85_finish(server, ns)) && + !(retVal = function_tsn_app_model_84_finish(server, ns)) && + !(retVal = function_tsn_app_model_83_finish(server, ns)) && + !(retVal = function_tsn_app_model_82_finish(server, ns)) && + !(retVal = function_tsn_app_model_81_finish(server, ns)) && + !(retVal = function_tsn_app_model_80_finish(server, ns)) && + !(retVal = function_tsn_app_model_79_finish(server, ns)) && + !(retVal = function_tsn_app_model_78_finish(server, ns)) && + !(retVal = function_tsn_app_model_77_finish(server, ns)) && + !(retVal = function_tsn_app_model_76_finish(server, ns)) && + !(retVal = function_tsn_app_model_75_finish(server, ns)) && + !(retVal = function_tsn_app_model_74_finish(server, ns)) && + !(retVal = function_tsn_app_model_73_finish(server, ns)) && + !(retVal = function_tsn_app_model_72_finish(server, ns)) && + !(retVal = function_tsn_app_model_71_finish(server, ns)) && + !(retVal = function_tsn_app_model_70_finish(server, ns)) && + !(retVal = function_tsn_app_model_69_finish(server, ns)) && + !(retVal = function_tsn_app_model_68_finish(server, ns)) && + !(retVal = function_tsn_app_model_67_finish(server, ns)) && + !(retVal = function_tsn_app_model_66_finish(server, ns)) && + !(retVal = function_tsn_app_model_65_finish(server, ns)) && + !(retVal = function_tsn_app_model_64_finish(server, ns)) && + !(retVal = function_tsn_app_model_63_finish(server, ns)) && + !(retVal = function_tsn_app_model_62_finish(server, ns)) && + !(retVal = function_tsn_app_model_61_finish(server, ns)) && + !(retVal = function_tsn_app_model_60_finish(server, ns)) && + !(retVal = function_tsn_app_model_59_finish(server, ns)) && + !(retVal = function_tsn_app_model_58_finish(server, ns)) && + !(retVal = function_tsn_app_model_57_finish(server, ns)) && + !(retVal = function_tsn_app_model_56_finish(server, ns)) && + !(retVal = function_tsn_app_model_55_finish(server, ns)) && + !(retVal = function_tsn_app_model_54_finish(server, ns)) && + !(retVal = function_tsn_app_model_53_finish(server, ns)) && + !(retVal = function_tsn_app_model_52_finish(server, ns)) && + !(retVal = function_tsn_app_model_51_finish(server, ns)) && + !(retVal = function_tsn_app_model_50_finish(server, ns)) && + !(retVal = function_tsn_app_model_49_finish(server, ns)) && + !(retVal = function_tsn_app_model_48_finish(server, ns)) && + !(retVal = function_tsn_app_model_47_finish(server, ns)) && + !(retVal = function_tsn_app_model_46_finish(server, ns)) && + !(retVal = function_tsn_app_model_45_finish(server, ns)) && + !(retVal = function_tsn_app_model_44_finish(server, ns)) && + !(retVal = function_tsn_app_model_43_finish(server, ns)) && + !(retVal = function_tsn_app_model_42_finish(server, ns)) && + !(retVal = function_tsn_app_model_41_finish(server, ns)) && + !(retVal = function_tsn_app_model_40_finish(server, ns)) && + !(retVal = function_tsn_app_model_39_finish(server, ns)) && + !(retVal = function_tsn_app_model_38_finish(server, ns)) && + !(retVal = function_tsn_app_model_37_finish(server, ns)) && + !(retVal = function_tsn_app_model_36_finish(server, ns)) && + !(retVal = function_tsn_app_model_35_finish(server, ns)) && + !(retVal = function_tsn_app_model_34_finish(server, ns)) && + !(retVal = function_tsn_app_model_33_finish(server, ns)) && + !(retVal = function_tsn_app_model_32_finish(server, ns)) && + !(retVal = function_tsn_app_model_31_finish(server, ns)) && + !(retVal = function_tsn_app_model_30_finish(server, ns)) && + !(retVal = function_tsn_app_model_29_finish(server, ns)) && + !(retVal = function_tsn_app_model_28_finish(server, ns)) && + !(retVal = function_tsn_app_model_27_finish(server, ns)) && + !(retVal = function_tsn_app_model_26_finish(server, ns)) && + !(retVal = function_tsn_app_model_25_finish(server, ns)) && + !(retVal = function_tsn_app_model_24_finish(server, ns)) && + !(retVal = function_tsn_app_model_23_finish(server, ns)) && + !(retVal = function_tsn_app_model_22_finish(server, ns)) && + !(retVal = function_tsn_app_model_21_finish(server, ns)) && + !(retVal = function_tsn_app_model_20_finish(server, ns)) && + !(retVal = function_tsn_app_model_19_finish(server, ns)) && + !(retVal = function_tsn_app_model_18_finish(server, ns)) && + !(retVal = function_tsn_app_model_17_finish(server, ns)) && + !(retVal = function_tsn_app_model_16_finish(server, ns)) && + !(retVal = function_tsn_app_model_15_finish(server, ns)) && + !(retVal = function_tsn_app_model_14_finish(server, ns)) && + !(retVal = function_tsn_app_model_13_finish(server, ns)) && + !(retVal = function_tsn_app_model_12_finish(server, ns)) && + !(retVal = function_tsn_app_model_11_finish(server, ns)) && + !(retVal = function_tsn_app_model_10_finish(server, ns)) && + !(retVal = function_tsn_app_model_9_finish(server, ns)) && + !(retVal = function_tsn_app_model_8_finish(server, ns)) && + !(retVal = function_tsn_app_model_7_finish(server, ns)) && + !(retVal = function_tsn_app_model_6_finish(server, ns)) && + !(retVal = function_tsn_app_model_5_finish(server, ns)) && + !(retVal = function_tsn_app_model_4_finish(server, ns)) && + !(retVal = function_tsn_app_model_3_finish(server, ns)) && + !(retVal = function_tsn_app_model_2_finish(server, ns)) && + !(retVal = function_tsn_app_model_1_finish(server, ns)) && + !(retVal = function_tsn_app_model_0_finish(server, ns))); + (void)(dummy); + return retVal; +} diff --git a/apps/linux/tsn-app/opcua/model/tsn_app_model.h b/apps/linux/tsn-app/opcua/model/tsn_app_model.h new file mode 100644 index 0000000..b40049c --- /dev/null +++ b/apps/linux/tsn-app/opcua/model/tsn_app_model.h @@ -0,0 +1,24 @@ +/* + * Copyright 2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + * + * WARNING: This is a generated file. + * Any manual changes will be overwritten. */ + +#ifndef TSN_APP_MODEL_H_ +#define TSN_APP_MODEL_H_ + +#ifdef UA_ENABLE_AMALGAMATION +#include "open62541.h" +#else +#include +#endif + +_UA_BEGIN_DECLS + +extern UA_StatusCode tsn_app_model(UA_Server *server); + +_UA_END_DECLS + +#endif /* TSN_APP_MODEL_H_ */ diff --git a/apps/linux/tsn-app/opcua/model/tsn_app_model_design.xml b/apps/linux/tsn-app/opcua/model/tsn_app_model_design.xml new file mode 100644 index 0000000..6bf0a39 --- /dev/null +++ b/apps/linux/tsn-app/opcua/model/tsn_app_model_design.xml @@ -0,0 +1,227 @@ + + + + + + https://opcua/UA/Tsn/ + http://opcfoundation.org/UA/ + + + + + + + + + Base type for all statistics + + + Minimum of the values + + + Mean of the values + + + Maximum of the values + + + Absolute maximum of the values + + + Absolute minimum of the values + + + Mean square of the values + + + Variance of the values + + + + + + + Base type for all histograms + + + Number of slots + + + Slot size + + + Array for the repartition of the values + + + + + + + Base type for the app configuration + + + Role of the endpoint : controller or io_device + + + Number of peers + + + + + + + Statistics of the task + + + Sched + + + SchedEarly + + + SchedLate + + + SchedMissed + + + SchedTimeout + + + ClockDiscount + + + ClockErr + + + SchedErrStats + + + SchedErrHisto + + + Processing time statistics + + + Processing time histogram + + + Total time statistics + + + Total time histogram + + + + + + + Application level socket + + + Peer identifier + + + Number of valid frames + + + Error parameter + + + Error parameter + + + Error parameter + + + Link up or down + + + Traffic latency statistics + + + Traffic latency histogram + + + + + + + Frames from low-level network socket + + + Rx or tx + + + Identifier + + + Frames number + + + Frames errors number + + + + + + + Base type for all the sockets + + + CyclicRxSocket0 + + + CyclicRxSocket1 + + + NetRxSocket0 + + + NetRxSocket1 + + + NetTxSocket0 + + + + + + + Base type for tsn_app + + + Configuration for the app + + + Statistics for the sockets + + + Statistics + + + + + + + TsnApp object + + + ua:Organizes + ua:ObjectsFolder + + + + + + \ No newline at end of file diff --git a/apps/linux/tsn-app/opcua/model/tsn_app_ua_nodeid_header.h b/apps/linux/tsn-app/opcua/model/tsn_app_ua_nodeid_header.h new file mode 100644 index 0000000..58ee527 --- /dev/null +++ b/apps/linux/tsn-app/opcua/model/tsn_app_ua_nodeid_header.h @@ -0,0 +1,360 @@ +/*--------------------------------------------------------- + * Copyright 2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Autogenerated -- do not modify + * Generated from src_generated/tsn_app_model_design.csv with script tools/generate_nodeid_header.py + *-------------------------------------------------------*/ + +#ifndef UA_NODEIDS_1_H_ +#define UA_NODEIDS_1_H_ + +/** + * Namespace Zero NodeIds + * ---------------------- + * Numeric identifiers of standard-defined nodes in namespace zero. The + * following definitions are autogenerated from the ``src_generated/tsn_app_model_design.csv`` file */ + +#define UA_1ID_STATSTYPE 15001 /* ObjectType */ +#define UA_1ID_STATSTYPE_MIN 15002 /* Variable */ +#define UA_1ID_STATSTYPE_MEAN 15003 /* Variable */ +#define UA_1ID_STATSTYPE_MAX 15004 /* Variable */ +#define UA_1ID_STATSTYPE_ABSMAX 15005 /* Variable */ +#define UA_1ID_STATSTYPE_ABSMIN 15006 /* Variable */ +#define UA_1ID_STATSTYPE_MS 15007 /* Variable */ +#define UA_1ID_STATSTYPE_VARIANCE 15008 /* Variable */ +#define UA_1ID_HISTOGRAMTYPE 15009 /* ObjectType */ +#define UA_1ID_HISTOGRAMTYPE_NSLOTS 15010 /* Variable */ +#define UA_1ID_HISTOGRAMTYPE_SLOTSIZE 15011 /* Variable */ +#define UA_1ID_HISTOGRAMTYPE_SLOTS 15012 /* Variable */ +#define UA_1ID_CONFIGURATIONTYPE 15013 /* ObjectType */ +#define UA_1ID_CONFIGURATIONTYPE_ROLE 15014 /* Variable */ +#define UA_1ID_CONFIGURATIONTYPE_NUMPEERS 15015 /* Variable */ +#define UA_1ID_TASKSTATSTYPE 15016 /* ObjectType */ +#define UA_1ID_TASKSTATSTYPE_SCHED 15017 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDEARLY 15018 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDLATE 15019 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDMISSED 15020 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDTIMEOUT 15021 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_CLOCKDISCOUNT 15022 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_CLOCKERR 15023 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRSTATS 15024 /* Object */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRSTATS_MIN 15025 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRSTATS_MEAN 15026 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRSTATS_MAX 15027 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRSTATS_ABSMAX 15028 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRSTATS_ABSMIN 15029 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRSTATS_MS 15030 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRSTATS_VARIANCE 15031 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRHISTO 15032 /* Object */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRHISTO_NSLOTS 15033 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRHISTO_SLOTSIZE 15034 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_SCHEDERRHISTO_SLOTS 15035 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMESTATS 15036 /* Object */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMESTATS_MIN 15037 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMESTATS_MEAN 15038 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMESTATS_MAX 15039 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMESTATS_ABSMAX 15040 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMESTATS_ABSMIN 15041 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMESTATS_MS 15042 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMESTATS_VARIANCE 15043 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMEHISTO 15044 /* Object */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMEHISTO_NSLOTS 15045 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMEHISTO_SLOTSIZE 15046 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_PROCTIMEHISTO_SLOTS 15047 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMESTATS 15048 /* Object */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMESTATS_MIN 15049 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMESTATS_MEAN 15050 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMESTATS_MAX 15051 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMESTATS_ABSMAX 15052 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMESTATS_ABSMIN 15053 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMESTATS_MS 15054 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMESTATS_VARIANCE 15055 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMEHISTO 15056 /* Object */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMEHISTO_NSLOTS 15057 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMEHISTO_SLOTSIZE 15058 /* Variable */ +#define UA_1ID_TASKSTATSTYPE_TOTALTIMEHISTO_SLOTS 15059 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE 15060 /* ObjectType */ +#define UA_1ID_CYCLICRXSOCKETTYPE_PEERID 15061 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_VALIDFRAMES 15062 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_ERRID 15063 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_ERRTS 15064 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_ERRUNDERFLOW 15065 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_LINK 15066 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYSTATS 15067 /* Object */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYSTATS_MIN 15068 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYSTATS_MEAN 15069 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYSTATS_MAX 15070 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYSTATS_ABSMAX 15071 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYSTATS_ABSMIN 15072 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYSTATS_MS 15073 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYSTATS_VARIANCE 15074 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYHISTO 15075 /* Object */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYHISTO_NSLOTS 15076 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYHISTO_SLOTSIZE 15077 /* Variable */ +#define UA_1ID_CYCLICRXSOCKETTYPE_TRAFFICLATENCYHISTO_SLOTS 15078 /* Variable */ +#define UA_1ID_NETWORKSOCKETTYPE 15079 /* ObjectType */ +#define UA_1ID_NETWORKSOCKETTYPE_DIRECTION 15080 /* Variable */ +#define UA_1ID_NETWORKSOCKETTYPE_ID 15081 /* Variable */ +#define UA_1ID_NETWORKSOCKETTYPE_FRAMES 15082 /* Variable */ +#define UA_1ID_NETWORKSOCKETTYPE_FRAMESERR 15083 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE 15084 /* ObjectType */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0 15085 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_PEERID 15086 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_VALIDFRAMES 15087 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_ERRID 15088 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_ERRTS 15089 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_ERRUNDERFLOW 15090 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_LINK 15091 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS 15092 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MIN 15093 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MEAN 15094 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MAX 15095 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_ABSMAX 15096 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_ABSMIN 15097 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MS 15098 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_VARIANCE 15099 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO 15100 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_NSLOTS 15101 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_SLOTSIZE 15102 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_SLOTS 15103 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1 15104 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_PEERID 15105 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_VALIDFRAMES 15106 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_ERRID 15107 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_ERRTS 15108 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_ERRUNDERFLOW 15109 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_LINK 15110 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS 15111 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MIN 15112 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MEAN 15113 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MAX 15114 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_ABSMAX 15115 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_ABSMIN 15116 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MS 15117 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_VARIANCE 15118 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO 15119 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_NSLOTS 15120 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_SLOTSIZE 15121 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_SLOTS 15122 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET0 15123 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET0_DIRECTION 15124 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET0_ID 15125 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET0_FRAMES 15126 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET0_FRAMESERR 15127 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET1 15128 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET1_DIRECTION 15129 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET1_ID 15130 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET1_FRAMES 15131 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETRXSOCKET1_FRAMESERR 15132 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETTXSOCKET0 15133 /* Object */ +#define UA_1ID_SOCKETSTATSTYPE_NETTXSOCKET0_DIRECTION 15134 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETTXSOCKET0_ID 15135 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETTXSOCKET0_FRAMES 15136 /* Variable */ +#define UA_1ID_SOCKETSTATSTYPE_NETTXSOCKET0_FRAMESERR 15137 /* Variable */ +#define UA_1ID_TSNAPPTYPE 15138 /* ObjectType */ +#define UA_1ID_TSNAPPTYPE_CONFIGURATION 15139 /* Object */ +#define UA_1ID_TSNAPPTYPE_CONFIGURATION_ROLE 15140 /* Variable */ +#define UA_1ID_TSNAPPTYPE_CONFIGURATION_NUMPEERS 15141 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS 15142 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0 15143 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_PEERID 15144 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_VALIDFRAMES 15145 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_ERRID 15146 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_ERRTS 15147 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_ERRUNDERFLOW 15148 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_LINK 15149 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS 15150 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MIN 15151 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MEAN 15152 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MAX 15153 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_ABSMAX 15154 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_ABSMIN 15155 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MS 15156 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_VARIANCE 15157 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO 15158 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_NSLOTS 15159 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_SLOTSIZE 15160 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_SLOTS 15161 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1 15162 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_PEERID 15163 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_VALIDFRAMES 15164 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_ERRID 15165 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_ERRTS 15166 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_ERRUNDERFLOW 15167 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_LINK 15168 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS 15169 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MIN 15170 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MEAN 15171 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MAX 15172 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_ABSMAX 15173 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_ABSMIN 15174 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MS 15175 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_VARIANCE 15176 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO 15177 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_NSLOTS 15178 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_SLOTSIZE 15179 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_SLOTS 15180 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET0 15181 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET0_DIRECTION 15182 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET0_ID 15183 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET0_FRAMES 15184 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET0_FRAMESERR 15185 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET1 15186 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET1_DIRECTION 15187 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET1_ID 15188 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET1_FRAMES 15189 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETRXSOCKET1_FRAMESERR 15190 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETTXSOCKET0 15191 /* Object */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETTXSOCKET0_DIRECTION 15192 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETTXSOCKET0_ID 15193 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETTXSOCKET0_FRAMES 15194 /* Variable */ +#define UA_1ID_TSNAPPTYPE_SOCKETSTATS_NETTXSOCKET0_FRAMESERR 15195 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS 15196 /* Object */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHED 15197 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDEARLY 15198 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDLATE 15199 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDMISSED 15200 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDTIMEOUT 15201 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_CLOCKDISCOUNT 15202 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_CLOCKERR 15203 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRSTATS 15204 /* Object */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRSTATS_MIN 15205 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRSTATS_MEAN 15206 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRSTATS_MAX 15207 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRSTATS_ABSMAX 15208 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRSTATS_ABSMIN 15209 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRSTATS_MS 15210 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRSTATS_VARIANCE 15211 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRHISTO 15212 /* Object */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRHISTO_NSLOTS 15213 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRHISTO_SLOTSIZE 15214 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_SCHEDERRHISTO_SLOTS 15215 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMESTATS 15216 /* Object */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMESTATS_MIN 15217 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMESTATS_MEAN 15218 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMESTATS_MAX 15219 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMESTATS_ABSMAX 15220 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMESTATS_ABSMIN 15221 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMESTATS_MS 15222 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMESTATS_VARIANCE 15223 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMEHISTO 15224 /* Object */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMEHISTO_NSLOTS 15225 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMEHISTO_SLOTSIZE 15226 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_PROCTIMEHISTO_SLOTS 15227 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMESTATS 15228 /* Object */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMESTATS_MIN 15229 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMESTATS_MEAN 15230 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMESTATS_MAX 15231 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMESTATS_ABSMAX 15232 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMESTATS_ABSMIN 15233 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMESTATS_MS 15234 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMESTATS_VARIANCE 15235 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMEHISTO 15236 /* Object */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMEHISTO_NSLOTS 15237 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMEHISTO_SLOTSIZE 15238 /* Variable */ +#define UA_1ID_TSNAPPTYPE_TASKSTATS_TOTALTIMEHISTO_SLOTS 15239 /* Variable */ +#define UA_1ID_TSNAPP 15240 /* Object */ +#define UA_1ID_TSNAPP_CONFIGURATION 15241 /* Object */ +#define UA_1ID_TSNAPP_CONFIGURATION_ROLE 15242 /* Variable */ +#define UA_1ID_TSNAPP_CONFIGURATION_NUMPEERS 15243 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS 15244 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0 15245 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_PEERID 15246 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_VALIDFRAMES 15247 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_ERRID 15248 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_ERRTS 15249 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_ERRUNDERFLOW 15250 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_LINK 15251 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS 15252 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MIN 15253 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MEAN 15254 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MAX 15255 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_ABSMAX 15256 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_ABSMIN 15257 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MS 15258 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_VARIANCE 15259 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO 15260 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_NSLOTS 15261 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_SLOTSIZE 15262 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_SLOTS 15263 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1 15264 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_PEERID 15265 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_VALIDFRAMES 15266 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_ERRID 15267 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_ERRTS 15268 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_ERRUNDERFLOW 15269 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_LINK 15270 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS 15271 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MIN 15272 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MEAN 15273 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MAX 15274 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_ABSMAX 15275 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_ABSMIN 15276 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MS 15277 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_VARIANCE 15278 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO 15279 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_NSLOTS 15280 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_SLOTSIZE 15281 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_SLOTS 15282 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0 15283 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0_DIRECTION 15284 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0_ID 15285 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0_FRAMES 15286 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0_FRAMESERR 15287 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1 15288 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1_DIRECTION 15289 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1_ID 15290 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1_FRAMES 15291 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1_FRAMESERR 15292 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0 15293 /* Object */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0_DIRECTION 15294 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0_ID 15295 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0_FRAMES 15296 /* Variable */ +#define UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0_FRAMESERR 15297 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS 15298 /* Object */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHED 15299 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDEARLY 15300 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDLATE 15301 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDMISSED 15302 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDTIMEOUT 15303 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_CLOCKDISCOUNT 15304 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_CLOCKERR 15305 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS 15306 /* Object */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_MIN 15307 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_MEAN 15308 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_MAX 15309 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_ABSMAX 15310 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_ABSMIN 15311 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_MS 15312 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_VARIANCE 15313 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRHISTO 15314 /* Object */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRHISTO_NSLOTS 15315 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRHISTO_SLOTSIZE 15316 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_SCHEDERRHISTO_SLOTS 15317 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS 15318 /* Object */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_MIN 15319 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_MEAN 15320 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_MAX 15321 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_ABSMAX 15322 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_ABSMIN 15323 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_MS 15324 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_VARIANCE 15325 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMEHISTO 15326 /* Object */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMEHISTO_NSLOTS 15327 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMEHISTO_SLOTSIZE 15328 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_PROCTIMEHISTO_SLOTS 15329 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS 15330 /* Object */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_MIN 15331 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_MEAN 15332 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_MAX 15333 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_ABSMAX 15334 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_ABSMIN 15335 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_MS 15336 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_VARIANCE 15337 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMEHISTO 15338 /* Object */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMEHISTO_NSLOTS 15339 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMEHISTO_SLOTSIZE 15340 /* Variable */ +#define UA_1ID_TSNAPP_TASKSTATS_TOTALTIMEHISTO_SLOTS 15341 /* Variable */ +#endif /* UA_NODEIDS_1_H_ */ diff --git a/apps/linux/tsn-app/opcua/opcua_server.c b/apps/linux/tsn-app/opcua/opcua_server.c new file mode 100644 index 0000000..5c65872 --- /dev/null +++ b/apps/linux/tsn-app/opcua/opcua_server.c @@ -0,0 +1,456 @@ +/* + * Copyright 2021-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE +#include +#include + +#include +#include +#include + +#include "../../common/log.h" +#include "../../common/timer.h" + +#include "../tsn_task.h" +#include "opcua_server.h" +#include "model/tsn_app_model.h" +#include "model/tsn_app_ua_nodeid_header.h" +#include "../tsn_tasks_config.h" + +#define OPCUA_LOG_LEVEL UA_LOGLEVEL_INFO +#define OPCUA_THREAD_PRIORITY 1 +#define OPCUA_THREAD_CPU_CORE 2 + +#define OP_STRING &UA_TYPES[UA_TYPES_STRING] +#define OP_INT32 &UA_TYPES[UA_TYPES_INT32] +#define OP_UINT32 &UA_TYPES[UA_TYPES_UINT32] +#define OP_UINT64 &UA_TYPES[UA_TYPES_UINT64] + +static volatile UA_Boolean running = true; + +static struct opcua_server *op_server; + +struct opcua_server { + pthread_t opcua_thread; + UA_Server *server; +}; + +struct stats_nid { + UA_UInt32 min; + UA_UInt32 mean; + UA_UInt32 max; + UA_UInt32 abs_min; + UA_UInt32 abs_max; + UA_UInt32 ms; + UA_UInt32 variance; +}; + +struct hist_nid { + UA_UInt32 n_slots; + UA_UInt32 slot_size; + UA_UInt32 slots; +}; + +struct tsn_task_stats_nid { + struct stats_nid sched_err; + struct hist_nid sched_err_hist; + struct stats_nid proc_time; + struct hist_nid proc_time_hist; + struct stats_nid total_time; + struct hist_nid total_time_hist; + UA_UInt32 sched; + UA_UInt32 sched_early; + UA_UInt32 sched_late; + UA_UInt32 sched_missed; + UA_UInt32 sched_timeout; + UA_UInt32 clock_discont; + UA_UInt32 clock_err; + UA_UInt32 sched_err_max; +}; + +struct tsn_config_nid { + UA_UInt32 role; + UA_UInt32 num_peers; +}; + +struct net_socket_stats_nid { + UA_UInt32 direction; + UA_UInt32 frames; + UA_UInt32 frames_err; + UA_UInt32 id; +}; + +struct cyclic_socket_stats_nid { + struct stats_nid traffic_latency; + struct hist_nid traffic_latency_hist; + UA_UInt32 peer_id; + UA_UInt32 valid_frames; + UA_UInt32 err_id; + UA_UInt32 err_ts; + UA_UInt32 err_underflow; + UA_UInt32 link; +}; + +static struct tsn_task_stats_nid tsn_task_stats_nid = { + .sched_err = { + .min = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_MIN, + .mean = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_MEAN, + .max = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_MAX, + .abs_min = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_ABSMIN, + .abs_max = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_ABSMAX, + .ms = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_MS, + .variance = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRSTATS_VARIANCE, + }, + .sched_err_hist = { + .n_slots = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRHISTO_NSLOTS, + .slot_size = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRHISTO_SLOTSIZE, + .slots = UA_1ID_TSNAPP_TASKSTATS_SCHEDERRHISTO_SLOTS, + }, + .proc_time = { + .min = UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_MIN, + .mean = UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_MEAN, + .max = UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_MAX, + .abs_min = UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_ABSMIN, + .abs_max = UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_ABSMAX, + .ms = UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_MS, + .variance = UA_1ID_TSNAPP_TASKSTATS_PROCTIMESTATS_VARIANCE, + }, + .proc_time_hist = { + .n_slots = UA_1ID_TSNAPP_TASKSTATS_PROCTIMEHISTO_NSLOTS, + .slot_size = UA_1ID_TSNAPP_TASKSTATS_PROCTIMEHISTO_SLOTSIZE, + .slots = UA_1ID_TSNAPP_TASKSTATS_PROCTIMEHISTO_SLOTS, + }, + .total_time = { + .min = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_MIN, + .mean = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_MEAN, + .max = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_MAX, + .abs_min = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_ABSMIN, + .abs_max = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_ABSMAX, + .ms = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_MS, + .variance = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMESTATS_VARIANCE, + }, + .total_time_hist = { + .n_slots = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMEHISTO_NSLOTS, + .slot_size = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMEHISTO_SLOTSIZE, + .slots = UA_1ID_TSNAPP_TASKSTATS_TOTALTIMEHISTO_SLOTS, + }, + .sched = UA_1ID_TSNAPP_TASKSTATS_SCHED, + .sched_early = UA_1ID_TSNAPP_TASKSTATS_SCHEDEARLY, + .sched_late = UA_1ID_TSNAPP_TASKSTATS_SCHEDLATE, + .sched_missed = UA_1ID_TSNAPP_TASKSTATS_SCHEDMISSED, + .sched_timeout = UA_1ID_TSNAPP_TASKSTATS_SCHEDTIMEOUT, + .clock_discont = UA_1ID_TSNAPP_TASKSTATS_CLOCKDISCOUNT, + .clock_err = UA_1ID_TSNAPP_TASKSTATS_CLOCKERR, +}; + +static struct tsn_config_nid tsn_config_nid = { + .role = UA_1ID_TSNAPP_CONFIGURATION_ROLE, + .num_peers = UA_1ID_TSNAPP_CONFIGURATION_NUMPEERS, +}; + +static struct net_socket_stats_nid net_socket_stats_nid[MAX_RX_SOCKET + MAX_TX_SOCKET] = { + [0] = { + .direction = UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0_DIRECTION, + .frames = UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0_FRAMES, + .frames_err = UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0_FRAMESERR, + .id = UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET0_ID, + }, + [1] = { + .direction = UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1_DIRECTION, + .frames = UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1_FRAMES, + .frames_err = UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1_FRAMESERR, + .id = UA_1ID_TSNAPP_SOCKETSTATS_NETRXSOCKET1_ID, + }, + [MAX_RX_SOCKET] = { + .direction = UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0_DIRECTION, + .frames = UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0_FRAMES, + .frames_err = UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0_FRAMESERR, + .id = UA_1ID_TSNAPP_SOCKETSTATS_NETTXSOCKET0_ID, + }, +}; + +struct cyclic_socket_stats_nid cyclic_socket_stats_nid[MAX_PEERS] = { + [0] = { + .traffic_latency = { + .min = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MIN, + .mean = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MEAN, + .max = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MAX, + .abs_min = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_ABSMIN, + .abs_max = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_ABSMAX, + .ms = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_MS, + .variance = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYSTATS_VARIANCE, + }, + .traffic_latency_hist = { + .n_slots = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_NSLOTS, + .slot_size = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_SLOTSIZE, + .slots = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_TRAFFICLATENCYHISTO_SLOTS, + }, + .peer_id = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_PEERID, + .valid_frames = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_VALIDFRAMES, + .err_id = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_ERRID, + .err_ts = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_ERRTS, + .err_underflow = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_ERRUNDERFLOW, + .link = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET0_LINK, + }, + [1] = { + .traffic_latency = { + .min = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MIN, + .mean = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MEAN, + .max = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MAX, + .abs_min = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_ABSMIN, + .abs_max = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_ABSMAX, + .ms = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_MS, + .variance = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYSTATS_VARIANCE, + }, + .traffic_latency_hist = { + .n_slots = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_NSLOTS, + .slot_size = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_SLOTSIZE, + .slots = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_TRAFFICLATENCYHISTO_SLOTS, + }, + .peer_id = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_PEERID, + .valid_frames = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_VALIDFRAMES, + .err_id = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_ERRID, + .err_ts = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_ERRTS, + .err_underflow = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_ERRUNDERFLOW, + .link = UA_1ID_TSNAPP_SOCKETSTATS_CYCLICRXSOCKET1_LINK, + }, +}; + +static void opcua_set_value(void *UA_RESTRICT p, const UA_DataType *type, const int nodeId) +{ + UA_Variant v; + + UA_Variant_setScalar(&v, p, type); + UA_Server_writeValue(op_server->server, UA_NODEID_NUMERIC(2, nodeId), v); +} + +static void opcua_update_dir(int direction, UA_Int32 nid_dir) +{ + char *dir; + UA_String opcua_dir; + + if (direction == TX) + dir = "tx"; + else + dir = "rx"; + + opcua_dir = UA_STRING(dir); + + opcua_set_value(&opcua_dir, OP_STRING, nid_dir); +} + +void opcua_init_params(struct cyclic_task *task) +{ + int i; + char *id; + UA_String role; + struct tsn_config_nid *nid = &tsn_config_nid; + struct net_socket_stats_nid *nid_net = net_socket_stats_nid; + + /* Configuration */ + if (task->id == CONTROLLER_0) + id = "Controller 0"; + else if (task->id == IO_DEVICE_0) + id = "IO device 0"; + else if (task->id == IO_DEVICE_1) + id = "IO device 1"; + else + id = "Unknown"; + + role = UA_STRING(id); + + opcua_set_value(&role, OP_STRING, nid->role); + opcua_set_value(&task->num_peers, OP_UINT64, nid->num_peers); + + for (i = 0; i < task->num_peers; i++) + opcua_update_dir(task->rx_socket[i].net_sock->dir, nid_net[i].direction); + + opcua_update_dir(task->tx_socket.net_sock->dir, nid_net[2].direction); +} + +static void opcua_update_stats(struct stats *stats, struct stats_nid *nid) +{ + opcua_set_value(&stats->min, OP_INT32, nid->min); + opcua_set_value(&stats->mean, OP_INT32, nid->mean); + opcua_set_value(&stats->max, OP_INT32, nid->max); + opcua_set_value(&stats->abs_min, OP_INT32, nid->abs_min); + opcua_set_value(&stats->abs_max, OP_INT32, nid->abs_max); + opcua_set_value(&stats->ms, OP_UINT64, nid->ms); + opcua_set_value(&stats->variance, OP_UINT64, nid->variance); +} + +static void opcua_update_histogram(struct hist *hist, struct hist_nid *nid) +{ + UA_Variant value; + + opcua_set_value(&hist->n_slots, OP_UINT32, nid->n_slots); + opcua_set_value(&hist->slot_size, OP_UINT32, nid->slot_size); + + UA_Variant_setArray(&value, hist->slots, hist->n_slots, OP_UINT32); + UA_Server_writeValue(op_server->server, UA_NODEID_NUMERIC(2, nid->slots), value); +} + +void opcua_update_task_stats(struct tsn_task_stats *task) +{ + struct tsn_task_stats_nid *nid = &tsn_task_stats_nid; + + opcua_set_value(&task->sched, OP_UINT32, nid->sched); + opcua_set_value(&task->sched_early, OP_UINT32, nid->sched_early); + opcua_set_value(&task->sched_late, OP_UINT32, nid->sched_late); + opcua_set_value(&task->sched_missed, OP_UINT32, nid->sched_missed); + opcua_set_value(&task->sched_timeout, OP_UINT32, nid->sched_timeout); + opcua_set_value(&task->clock_discont, OP_UINT32, nid->clock_discont); + opcua_set_value(&task->clock_err, OP_UINT32, nid->clock_err); + + opcua_update_stats(&task->sched_err, &nid->sched_err); + opcua_update_histogram(&task->sched_err_hist, &nid->sched_err_hist); + + opcua_update_stats(&task->proc_time, &nid->proc_time); + opcua_update_histogram(&task->proc_time_hist, &nid->proc_time_hist); + + opcua_update_stats(&task->total_time, &nid->total_time); + opcua_update_histogram(&task->total_time_hist, &nid->total_time_hist); +} + +void opcua_update_cyclic_socket(struct socket *sock) +{ + char *link; + UA_String opcua_link; + struct cyclic_socket_stats_nid *nid = &cyclic_socket_stats_nid[sock->id]; + + if (sock->stats_snap.link_status == 1) + link = "up"; + else + link = "down"; + + opcua_link = UA_STRING(link); + + opcua_set_value(&sock->stats_snap.err_id, OP_UINT32, nid->err_id); + opcua_set_value(&sock->stats_snap.err_ts, OP_UINT32, nid->err_ts); + opcua_set_value(&sock->stats_snap.err_underflow, OP_UINT32, nid->err_underflow); + opcua_set_value(&opcua_link, OP_STRING, nid->link); + opcua_set_value(&sock->peer_id, OP_INT32, nid->peer_id); + opcua_set_value(&sock->stats_snap.valid_frames, OP_UINT32, nid->valid_frames); + + opcua_update_stats(&sock->stats_snap.traffic_latency, &nid->traffic_latency); + opcua_update_histogram(&sock->stats_snap.traffic_latency_hist, &nid->traffic_latency_hist); +} + +void opcua_update_net_socket_stats(struct net_socket *sock) +{ + int index = sock->id; + struct net_socket_stats *stats = &sock->stats_snap; + struct net_socket_stats_nid *nid; + + if (sock->dir == TX) + index += MAX_RX_SOCKET; + + nid = &net_socket_stats_nid[index]; + + opcua_set_value(&stats->frames, OP_UINT32, nid->frames); + opcua_set_value(&stats->err, OP_UINT32, nid->frames_err); + opcua_set_value(&sock->id, OP_INT32, nid->id); +} + +static void *opcua_thread_handle(void *param) +{ + intptr_t rc = 0; + struct sched_param thread_param = { + .sched_priority = OPCUA_THREAD_PRIORITY, + }; + cpu_set_t cpu_set; + UA_StatusCode retval; + + /* Priority */ + if (sched_setscheduler(0, SCHED_FIFO, &thread_param) < 0) { + ERR("sched_setscheduler failed with error %d - %s", errno, strerror(errno)); + rc = -1; + goto exit; + } + + /* Affinity */ + CPU_ZERO(&cpu_set); + CPU_SET(OPCUA_THREAD_CPU_CORE, &cpu_set); + if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) == -1) { + ERR("sched_setaffinity failed with error %d - %s", errno, strerror(errno)); + } + + INF("Starting OPCUA server"); + + retval = UA_Server_run(op_server->server, &running); + if (retval != UA_STATUSCODE_GOOD) { + ERR("UA_Server_run failed"); + rc = -1; + goto exit; + } + +exit: + return (void *)rc; +} + +int opcua_server_init(void) +{ + UA_ServerConfig *config; + UA_StatusCode retval; + int rc = 0; + + if (op_server) { + rc = -1; + goto err; + } + + op_server = malloc(sizeof(struct opcua_server)); + if (!op_server) { + ERR("malloc() failed"); + rc = -1; + goto err; + } + + op_server->server = UA_Server_new(); + if (!op_server->server) { + ERR("UA_Server_new() failed"); + rc = -1; + goto err_free; + } + + config = UA_Server_getConfig(op_server->server); + UA_ServerConfig_setDefault(config); + config->logger = UA_Log_Syslog_withLevel(OPCUA_LOG_LEVEL); + + retval = tsn_app_model(op_server->server); + if (retval != UA_STATUSCODE_GOOD) { + ERR("Information Model failed"); + rc = -1; + goto err_del_server; + } + + INF("Starting OPCUA server thread"); + + rc = pthread_create(&op_server->opcua_thread, NULL, &opcua_thread_handle, NULL); + if (rc != 0) { + ERR("OPCUA thread create failed, error %s", strerror(rc)); + goto err_del_server; + } + + return 0; + +err_del_server: + UA_Server_delete(op_server->server); + +err_free: + free(op_server); + +err: + return rc; +}; + +void opcua_server_exit(void) +{ + running = false; + pthread_join(op_server->opcua_thread, NULL); + UA_Server_delete(op_server->server); +} diff --git a/apps/linux/tsn-app/opcua/opcua_server.h b/apps/linux/tsn-app/opcua/opcua_server.h new file mode 100644 index 0000000..8b94abf --- /dev/null +++ b/apps/linux/tsn-app/opcua/opcua_server.h @@ -0,0 +1,33 @@ +/* + * Copyright 2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _OPCUA_SERVER_H_ +#define _OPCUA_SERVER_H_ + +#include "../cyclic_task.h" + +struct opcua_server; + +#ifdef OPCUA_SUPPORT +void opcua_init_params(struct cyclic_task *task); +void opcua_update_cyclic_socket(struct socket *sock); +void opcua_update_net_socket_stats(struct net_socket *sock); +void opcua_update_task_stats(struct tsn_task_stats *task); +int opcua_server_init(void); +void opcua_server_exit(void); +#else +static inline int opcua_server_init(void) +{ + return 0; +}; +static inline void opcua_init_params(struct cyclic_task *task) { return; }; +static inline void opcua_update_cyclic_socket(struct socket *sock) { return; }; +static inline void opcua_update_net_socket_stats(struct net_socket *sock) { return; }; +static inline void opcua_update_task_stats(struct tsn_task_stats *task) { return; }; +static inline void opcua_server_exit(void) { return; }; +#endif + +#endif /* _OPCUA_SERVER_H_ */ diff --git a/apps/linux/tsn-app/serial_controller.c b/apps/linux/tsn-app/serial_controller.c new file mode 100644 index 0000000..610178a --- /dev/null +++ b/apps/linux/tsn-app/serial_controller.c @@ -0,0 +1,267 @@ +/* + * Copyright 2020-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include "../common/log.h" +#include "../common/thread.h" +#include "../common/time.h" +#include "../common/timer.h" + +#include "serial_controller.h" +#include "cyclic_task.h" + +#define STAT_PERIOD_SEC 5 + +void serial_controller_stats_print(struct serial_controller_ctx *ctx) +{ + struct serial_controller_stats *stats_snap = &ctx->stats_snap; + + if (stats_snap->pending) { + INF("net:"); + INF(" rx cmds : %u", stats_snap->net_cmds); + INF(" rx null cmds : %u", stats_snap->net_null_cmds); + INF("pt:"); + INF(" read bytes : %u", stats_snap->pt_read_bytes); + INF(" read cmds : %u", stats_snap->pt_read_cmds); + INF(" read aborts : %u", stats_snap->pt_read_abort_cmds); + INF(" write errors : %u", stats_snap->pt_write_errors); + INF(" write incomplete : %u", stats_snap->pt_write_incomplete); + + stats_snap->pending = false; + } +} + +static void serial_controller_stats_dump(struct serial_controller_ctx *ctx) +{ + if (ctx->stats_snap.pending) + return; + + memcpy(&ctx->stats_snap, &ctx->stats, sizeof(struct serial_controller_stats)); + ctx->stats_snap.pending = true; +} + +void serial_controller_stats_handler(void *data) +{ + struct serial_controller_ctx *ctx = data; + struct cyclic_task *c_task = ctx->c_task; + + cyclic_stats_print(c_task); + serial_controller_stats_print(ctx); +} + +static int serial_controller_pt_handler(void *data, unsigned int events) +{ + struct serial_controller_ctx *ctx = data; + struct pt_cmd *cmd = &ctx->pt_cmd; + ssize_t s; + + /* If the other end hanged-up relax handling + * as epoll will trigger continuously until it resumes. + */ + if (events & EPOLLHUP) { + sleep_ms(10); + return 0; + } + + /* Shift possible remaining data after the previous command */ + if (cmd->rem) { + memcpy(cmd->buf, cmd->buf + cmd->len, cmd->rem); + cmd->len = cmd->rem; + cmd->rem = 0; + } + + s = read(ctx->pt_fd, cmd->buf + cmd->len, cmd->buf_size - cmd->len); + if (s == -1) { + ERR("read() failed: %s\n", strerror(errno)); + return -1; + } + + if (s) { + int i, rc; + + for (i = 0; i < s; i++) { + char c = cmd->buf[cmd->len++]; + + if (c == '\n' || c == '\r') { + /* Aborted command or empty */ + if (cmd->abort || (cmd->len == 1)) { + cmd->len = 0; + cmd->abort = 0; + continue; + } + + /* Received more data than the current command */ + if (s > cmd->len) + cmd->rem = s - cmd->len; + + ctx->stats.pt_read_cmds++; + + DBG("cmd: %.*s", cmd->len, cmd->buf); + + /* Notify network loop and wait that it processes it */ + cmd->avail = 1; + rc = sem_wait(&ctx->cmd_sem); + if (rc < 0) + ERR("sem_wait() failed : %s\n", strerror(errno)); + + if (!cmd->rem) + cmd->len = 0; + } + } + + if (cmd->len == cmd->buf_size) { + cmd->abort = 1; + cmd->len = 0; + ctx->stats.pt_read_abort_cmds++; + } + + ctx->stats.pt_read_bytes += s; + } else { + /* pt slave hanged-up */ + cmd->len = 0; + cmd->rem = 0; + cmd->abort = 0; + } + + return 0; +} + +//FIXME do the write in the pseudo-tty thread +static void serial_controller_net_recv(void *data, int msg_id, int src_id, void *buf, int len) +{ + struct serial_controller_ctx *ctx = data; + struct msg_serial *msg_recv = buf; + + if (msg_id != MSG_SERIAL) { + ctx->stats.net_invalid_msg_id++; + return; + } + + if (msg_recv->cmd_len) { + ssize_t written; + + written = write(ctx->pt_fd, msg_recv->cmd, msg_recv->cmd_len); + if (written < 0) + ctx->stats.pt_write_errors++; + + if (written < msg_recv->cmd_len) + ctx->stats.pt_write_incomplete++; + + ctx->stats.net_cmds++; + } else { + ctx->stats.net_null_cmds++; + } +} + +static void serial_controller_loop(void *data, int timer_status) +{ + struct serial_controller_ctx *ctx = data; + struct cyclic_task *c_task = ctx->c_task; + struct pt_cmd *cmd = &ctx->pt_cmd; + struct msg_serial msg_to_send; + unsigned int num_sched_stats = STAT_PERIOD_SEC * (NSECS_PER_SEC / ctx->c_task->task->params->task_period_ns); + + msg_to_send.cmd_len = 0; + + if (cmd->avail) { + int rc; + + memcpy(msg_to_send.cmd, cmd->buf, cmd->len); + msg_to_send.cmd_len = cmd->len; + cmd->avail = 0; + + /* Unblock pt side */ + rc = sem_post(&ctx->cmd_sem); + if (rc < 0) + ERR("sem_wait() failed : %s\n", strerror(errno)); + } else { + memset(msg_to_send.cmd, 0, MAX_SERIAL_COMMAND_LEN); + } + + cyclic_net_transmit(c_task, MSG_SERIAL, &msg_to_send, sizeof(msg_to_send)); + + if (ctx->c_task->task->stats.sched % num_sched_stats == 0) + serial_controller_stats_dump(ctx); +} + +struct serial_controller_ctx *serial_controller_init(unsigned int period_ns, unsigned int num_peers, int pt_fd, unsigned int timer_type) +{ + struct cyclic_task *c_task = NULL; + struct serial_controller_ctx *ctx; + + ctx = malloc(sizeof(struct serial_controller_ctx)); + if (!ctx) { + ERR("malloc() failed\n"); + goto err; + } + + memset(ctx, 0, sizeof(*ctx)); + + c_task = tsn_conf_get_cyclic_task(CONTROLLER_0); + if (!c_task) { + ERR("tsn_conf_get_cyclic_task() failed\n"); + goto err_free; + } + + cyclic_task_set_period(c_task, period_ns); + +#ifdef TRACE_SNAPSHOT + cyclic_task_init_trace_snapshot(c_task); +#endif + + if (cyclic_task_set_num_peers(c_task, num_peers) < 0) { + ERR("cyclic_task_set_period() failed\n"); + goto err_free; + } + + c_task->params.timer_type = timer_type; + + if (cyclic_task_init(c_task, serial_controller_net_recv, serial_controller_loop, ctx) < 0) { + ERR("cyclic_task_init() failed\n"); + goto err_free; + } + + ctx->c_task = c_task; + ctx->pt_cmd.buf_size = MAX_SERIAL_COMMAND_LEN; + ctx->pt_fd = pt_fd; + + if (sem_init(&ctx->cmd_sem, 0, 0) < 0) { + ERR("sem_init() failed %s\n", strerror(errno)); + goto err_cyclic_exit; + } + + if (thread_slot_add(THR_CAP_TSN_PT, pt_fd, EPOLLIN, ctx, serial_controller_pt_handler, + NULL, 0, (thr_thread_slot_t **)&ctx->pt_thread) < 0) { + ERR("thread_slot_add() failed\n"); + goto err_cyclic_exit; + } + + cyclic_task_start(c_task); + + return ctx; + +err_cyclic_exit: + cyclic_task_exit(c_task); + +err_free: + free(ctx); +err: + return NULL; +} + +void serial_controller_exit(void *data) +{ + struct serial_controller_ctx *ctx = data; + + cyclic_task_exit(ctx->c_task); + sem_destroy(&ctx->cmd_sem); + free(ctx); +} diff --git a/apps/linux/tsn-app/serial_controller.h b/apps/linux/tsn-app/serial_controller.h new file mode 100644 index 0000000..9e9a2d0 --- /dev/null +++ b/apps/linux/tsn-app/serial_controller.h @@ -0,0 +1,57 @@ +/* + * Copyright 2020-2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _SERIAL_CONTROLLER_H_ +#define _SERIAL_CONTROLLER_H_ + +#include +#include + +#define MAX_SERIAL_COMMAND_LEN 50 + +struct pt_cmd { + int avail; + int abort; + unsigned int len; + unsigned int rem; + char buf[MAX_SERIAL_COMMAND_LEN]; + unsigned int buf_size; +}; + +struct serial_controller_stats { + unsigned int net_cmds; + unsigned int net_null_cmds; + unsigned int net_invalid_msg_id; + unsigned int pt_read_bytes; + unsigned int pt_read_cmds; + unsigned int pt_read_abort_cmds; + unsigned int pt_write_errors; + unsigned int pt_write_incomplete; + bool pending; +}; + +struct serial_controller_ctx { + struct cyclic_task *c_task; + void *pt_thread; + + int pt_fd; + struct pt_cmd pt_cmd; + sem_t cmd_sem; + + struct serial_controller_stats stats; + struct serial_controller_stats stats_snap; +}; + +struct msg_serial { + uint16_t cmd_len; + uint8_t cmd[MAX_SERIAL_COMMAND_LEN]; +}; + +struct serial_controller_ctx *serial_controller_init(unsigned int period_ns, unsigned int num_peers, int pt_fd, unsigned int timer_type); +void serial_controller_exit(void *data); +void serial_controller_stats_handler(void *data); + +#endif /* _SERIAL_CONTROLLER_H_ */ diff --git a/apps/linux/tsn-app/thread_config.c b/apps/linux/tsn-app/thread_config.c new file mode 100644 index 0000000..95afb16 --- /dev/null +++ b/apps/linux/tsn-app/thread_config.c @@ -0,0 +1,38 @@ +/* + * Copyright 2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "../common/thread_config.h" + +/* Slots are allocated by thread array index order */ +thr_thread_t g_thread_array[MAX_THREADS] = { + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 60, + .max_slots = 1, + .thread_capabilities = THR_CAP_TSN_LOOP, + .slots = {{0}}, + }, + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 10, + .max_slots = 1, + .thread_capabilities = THR_CAP_TSN_PT, + .slots = {{0}}, + }, + { + .poll_fd = -1, + .cpu_core = 2, + .exit_flag = 0, + .priority = 2, + .max_slots = 1, + .thread_capabilities = THR_CAP_STATS, + .slots = {{0}}, + }, +}; diff --git a/apps/linux/tsn-app/tsn_task.c b/apps/linux/tsn-app/tsn_task.c new file mode 100644 index 0000000..bf93ab7 --- /dev/null +++ b/apps/linux/tsn-app/tsn_task.c @@ -0,0 +1,687 @@ +/* + * Copyright 2019-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include +#include +#include +#include + +#include "tsn_task.h" +#include "../common/log.h" +#include "../common/time.h" +#include "../common/helpers.h" +#include "opcua/opcua_server.h" + +void tsn_task_stats_init(struct tsn_task *task) +{ + stats_init(&task->stats.sched_err, 31, "sched err", NULL); + hist_init(&task->stats.sched_err_hist, 100, 10000); + + stats_init(&task->stats.proc_time, 31, "processing time", NULL); + hist_init(&task->stats.proc_time_hist, 100, 1000); + + stats_init(&task->stats.total_time, 31, "total time", NULL); + hist_init(&task->stats.total_time_hist, 100, 1000); + + task->stats.sched_err_max = 0; +} + +void tsn_task_stats_reset(struct tsn_task *task) +{ + stats_init(&task->stats.sched_err, 31, "sched err", NULL); + hist_reset(&task->stats.sched_err_hist); + + stats_init(&task->stats.proc_time, 31, "processing time", NULL); + hist_reset(&task->stats.proc_time_hist); + + stats_init(&task->stats.total_time, 31, "total time", NULL); + hist_reset(&task->stats.total_time_hist); + + task->stats.sched = 0; + task->stats.sched_early = 0; + task->stats.sched_late = 0; + task->stats.sched_missed = 0; + task->stats.sched_timeout = 0; + task->stats.clock_discont = 0; + task->stats.clock_err = 0; + task->stats.sched_err_max = 0; +} + +void tsn_task_stats_start(struct tsn_task *task, int count, uint64_t now) +{ + int32_t sched_err; + + task->sched_time += (count * task->params->task_period_ns); + + task->stats.sched += count; + + if (task->stats.stats_valid) { + sched_err = now - task->sched_time; + + if (sched_err > SCHEDULE_LATENCY_THRESHOLD) + task->stats.sched_late++; + + if (count > 1) + task->stats.sched_missed += (count - 1); + + if (sched_err < 0) { + task->stats.sched_early++; + sched_err = -sched_err; + } + + stats_update(&task->stats.sched_err, sched_err); + hist_update(&task->stats.sched_err_hist, sched_err); + + if (sched_err > task->stats.sched_err_max) + task->stats.sched_err_max = sched_err; + } else + task->stats.stats_valid = true; + + task->sched_now = now; +} + +void tsn_task_stats_end(struct tsn_task *task) +{ + uint64_t now = 0; + int32_t proc_time; + int32_t total_time; + + if (task->stats.stats_valid) { + genavb_clock_gettime64(task->params->clk_id, &now); + + proc_time = now - task->sched_now; + total_time = now - task->sched_time; + + stats_update(&task->stats.proc_time, proc_time); + hist_update(&task->stats.proc_time_hist, proc_time); + + stats_update(&task->stats.total_time, total_time); + hist_update(&task->stats.total_time_hist, total_time); + } +} + +void tsn_task_stats_print(struct tsn_task *task) +{ + struct tsn_task_stats *stats = &task->stats_snap; + + if (stats->pending) { + stats_compute(&stats->sched_err); + stats_compute(&stats->proc_time); + stats_compute(&stats->total_time); + + INF("tsn task(%p)", task); + INF("sched : %u", stats->sched); + INF("sched early : %u", stats->sched_early); + INF("sched late : %u", stats->sched_late); + INF("sched missed : %u", stats->sched_missed); + INF("sched timeout : %u", stats->sched_timeout); + INF("clock discont : %u", stats->clock_discont); + INF("clock err : %u", stats->clock_err); + + stats_print(&stats->sched_err); + hist_print(&stats->sched_err_hist); + + stats_print(&stats->proc_time); + hist_print(&stats->proc_time_hist); + + stats_print(&stats->total_time); + hist_print(&stats->total_time_hist); + + stats->pending = false; + + opcua_update_task_stats(stats); + } +} + +static void tsn_task_stats_dump(struct tsn_task *task) +{ + if (task->stats_snap.pending) + return; + + memcpy(&task->stats_snap, &task->stats, sizeof(struct tsn_task_stats)); + task->stats_snap.pending = true; + + stats_reset(&task->stats.sched_err); + stats_reset(&task->stats.proc_time); + stats_reset(&task->stats.total_time); +} + +void net_socket_stats_print(struct net_socket *sock) +{ + struct net_socket_stats *stats = &sock->stats_snap; + + if (stats->pending) { + INF("net %s socket(%p) %d", sock->dir ? "tx" : "rx", sock, sock->id); + INF("frames : %u", stats->frames); + INF("err : %u", stats->err); + + opcua_update_net_socket_stats(sock); + + stats->pending = false; + } +} + +static void net_socket_stats_dump(struct net_socket *sock) +{ + if (sock->stats_snap.pending) + return; + + memcpy(&sock->stats_snap, &sock->stats, sizeof(struct net_socket_stats)); + sock->stats_snap.pending = true; +} + +void tsn_stats_dump(struct tsn_task *task) +{ + int i; + + tsn_task_stats_dump(task); + + for (i = 0; i < task->params->num_rx_socket; i++) + net_socket_stats_dump(&task->sock_rx[i]); + + for (i = 0; i < task->params->num_tx_socket; i++) + net_socket_stats_dump(&task->sock_tx[i]); +} + +int tsn_net_receive_sock(struct net_socket *sock) +{ + struct tsn_task *task = container_of(sock, struct tsn_task, sock_rx[sock->id]); + int len; + int status; + + len = genavb_socket_rx(sock->genavb_rx, sock->buf, task->params->rx_buf_size, &sock->ts); + if (len > 0) { + status = NET_OK; + sock->len = len; + sock->stats.frames++; + } else if (len == -GENAVB_ERR_SOCKET_AGAIN) { + status = NET_NO_FRAME; + } else { + status = NET_ERR; + sock->stats.err++; + } + + return status; +} + +int tsn_net_transmit_sock(struct net_socket *sock) +{ + int rc; + int status; + + rc = genavb_socket_tx(sock->genavb_tx, sock->buf, sock->len); + if (rc == GENAVB_SUCCESS) { + status = NET_OK; + sock->stats.frames++; + } else { + status = NET_ERR; + sock->stats.err++; + } + + return status; +} + +#if 0 +int tsn_net_receive_set_cb(struct net_socket *sock, void (*net_rx_cb)(void *)) +{ + int rc; + + rc = genavb_socket_rx_set_callback(sock->genavb_rx, net_rx_cb, sock); + if (rc != GENAVB_SUCCESS) + return -1; + + return 0; +} + +int tsn_net_receive_enable_cb(struct net_socket *sock) +{ + int rc; + + rc = genavb_socket_rx_enable_callback(sock->genavb_rx); + if (rc != GENAVB_SUCCESS) + return -1; + + return 0; +} +/* + * Returns the complete transmit time including MAC framing and physical + * layer overhead (802.3). + * \return transmit time in nanoseconds + * \param frame_size frame size without any framing + * \param speed_mbps link speed in Mbps + */ +static unsigned int frame_tx_time_ns(unsigned int frame_size, int speed_mbps) +{ + unsigned int eth_size; + + eth_size = sizeof(struct eth_hdr) + frame_size + ETHER_FCS; + + if (eth_size < ETHER_MIN_FRAME_SIZE) + eth_size = ETHER_MIN_FRAME_SIZE; + + eth_size += ETHER_IFG + ETHER_PREAMBLE; + + return (((1000 / speed_mbps) * eth_size * 8) + ST_TX_TIME_MARGIN); +} + +static void tsn_net_st_config_enable(struct tsn_task *task, uint64_t now) +{ + struct genavb_st_config config; + struct genavb_st_gate_control_entry gate_list[ST_LIST_LEN]; + struct net_address *addr = &task->params->tx_params[0].addr; + unsigned int cycle_time = task->params->task_period_ns; + uint8_t iso_traffic_prio = addr->priority; + uint8_t tclass = priority_to_traffic_class_map(CFG_TRAFFIC_CLASS_MAX, CFG_SR_CLASS_MAX)[iso_traffic_prio]; + unsigned int iso_tx_time = frame_tx_time_ns(task->params->tx_buf_size, 1000) * ST_TX_TIME_FACTOR; + unsigned int guard_band = frame_tx_time_ns(ETHER_MTU, 1000); + + gate_list[0].operation = GENAVB_ST_SET_GATE_STATES; + gate_list[0].gate_states = 1 << tclass; + gate_list[0].time_interval = iso_tx_time; + + gate_list[1].operation = GENAVB_ST_SET_GATE_STATES; + gate_list[1].gate_states = ~(1 << tclass); + gate_list[1].time_interval = cycle_time - iso_tx_time - guard_band; + + gate_list[2].operation = GENAVB_ST_SET_GATE_STATES; + gate_list[2].gate_states = 0; + gate_list[2].time_interval = guard_band; + + config.enable = 1; + config.base_time = (now / cycle_time) * cycle_time; + config.base_time += task->params->task_period_offset_ns + task->params->sched_traffic_offset; + config.cycle_time_p = cycle_time; + config.cycle_time_q = NSECS_PER_SEC; + config.cycle_time_ext = 0; + config.list_length = ST_LIST_LEN; + config.control_list = gate_list; + + if (genavb_st_set_admin_config(addr->port, task->params->clk_id, &config) < 0) + ERR("genavb_st_set_admin_config() error\n"); + else + INF("scheduled traffic config enabled\n"); +} + +static void tsn_net_st_config_disable(struct tsn_task *task) +{ + struct genavb_st_config config; + struct net_address *addr = &task->params->tx_params[0].addr; + + config.enable = 0; + + if (genavb_st_set_admin_config(addr->port, task->params->clk_id, &config) < 0) + ERR("genavb_qos_st_set_admin_config() error\n"); + else + INF("scheduled traffic config disabled\n"); +} + +static void tsn_net_st_oper_config_print(struct tsn_task *task) +{ + int i; + struct genavb_st_config config; + struct genavb_st_gate_control_entry gate_list[ST_LIST_LEN]; + struct net_address *addr = &task->params->tx_params[0].addr; + + config.control_list = gate_list; + + if (genavb_st_get_config(addr->port, GENAVB_ST_OPER, &config, ST_LIST_LEN) < 0) { + ERR("genavb_qos_st_get_config() error\n"); + return; + } + + INF("base time : %llu\n", config.base_time); + INF("cycle time : %u / %u\n", config.cycle_time_p, config.cycle_time_q); + INF("ext time : %u\n", config.cycle_time_ext); + + for (i = 0; i < config.list_length; i++) + INF("%u op: %u, interval: %u, gates: %b\n", + i, gate_list[i].operation, gate_list[i].time_interval, gate_list[i].gate_states); +} +#endif +int tsn_task_start(struct tsn_task *task) +{ + uint64_t now, start_time; + + if (genavb_clock_gettime64(task->params->clk_id, &now) != GENAVB_SUCCESS) { + ERR("genavb_clock_gettime64() error\n"); + goto err; + } + + /* Start time = rounded up second + 1 second */ + start_time = ((now + NSECS_PER_SEC / 2) / NSECS_PER_SEC + 1) * NSECS_PER_SEC; + + /* Align on cycle time and add offset */ + start_time = (start_time / task->params->task_period_ns) * task->params->task_period_ns + task->params->task_period_offset_ns; + + if (tsn_timer_start(task->timer, start_time, task->params->task_period_ns) < 0) { + ERR("tsn timer start error\n"); + goto err; + } + + /* Sched time is updated at the start of the app cycle to take account of + * number of expirations. So here it's set one period before */ + task->sched_time = start_time - task->params->task_period_ns; + + DBG("now(%" PRIu64 ") sched_time(%" PRIu64 ") sched_next%" PRIu64 ")", now, task->sched_time, task->nsleep.next); + //tsn_net_st_config_enable(task, now); + + return 0; + +err: + return -1; +} + +void tsn_task_stop(struct tsn_task *task) +{ + tsn_timer_stop(task->timer); +} + +#ifdef SRP_RESERVATION +static struct genavb_control_handle *s_msrp_handle = NULL; + +static uint8_t tsn_stream_id[8] = {0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0x00}; + +static int msrp_init(struct genavb_handle *s_avb_handle) +{ + int genavb_result; + int rc; + + genavb_result = genavb_control_open(s_avb_handle, &s_msrp_handle, GENAVB_CTRL_MSRP); + if (genavb_result != GENAVB_SUCCESS) { + ERR("avb_control_open() failed: %s\n", genavb_strerror(genavb_result)); + rc = -1; + goto err_control_open; + } + + return 0; + +err_control_open: + return rc; +} + +static int msrp_exit(void) +{ + genavb_control_close(s_msrp_handle); + + s_msrp_handle = NULL; + + return 0; +} + +static int tsn_net_rx_srp_register(struct genavb_socket_rx_params *params) +{ + struct genavb_msg_listener_register listener_register; + struct genavb_msg_listener_response listener_response; + struct net_address *addr = ¶ms->addr; + unsigned int msg_type, msg_len; + int rc; + + listener_register.port = addr->port; + memcpy(listener_register.stream_id, tsn_stream_id, 8); + listener_register.stream_id[7] = addr->u.l2.dst_mac[5]; + + INF("stream_params: %p\n", listener_register.stream_id); + + msg_type = GENAVB_MSG_LISTENER_REGISTER; + msg_len = sizeof(listener_response); + rc = genavb_control_send_sync(s_msrp_handle, (genavb_msg_type_t *)&msg_type, &listener_register, sizeof(listener_register), &listener_response, &msg_len, 1000); + if ((rc != GENAVB_SUCCESS) || (msg_type != GENAVB_MSG_LISTENER_RESPONSE) || (listener_response.status != GENAVB_SUCCESS)) { + ERR(STREAM_STR_FMT " failed: %s\n", STREAM_STR(listener_register.stream_id), genavb_strerror(rc)); + return -1; + } + + return 0; +} + +static int tsn_net_rx_srp_deregister(struct genavb_socket_rx_params *params) +{ + struct genavb_msg_listener_deregister listener_deregister; + struct genavb_msg_listener_response listener_response; + struct net_address *addr = ¶ms->addr; + unsigned int msg_type, msg_len; + int rc; + + listener_deregister.port = addr->port; + memcpy(listener_deregister.stream_id, tsn_stream_id, 8); + listener_deregister.stream_id[7] = addr->u.l2.dst_mac[5]; + + INF("stream_params: %p\n", listener_deregister.stream_id); + + msg_type = GENAVB_MSG_LISTENER_DEREGISTER; + msg_len = sizeof(listener_response); + rc = genavb_control_send_sync(s_msrp_handle, (genavb_msg_type_t *)&msg_type, &listener_deregister, sizeof(listener_deregister), &listener_response, &msg_len, 1000); + if ((rc != GENAVB_SUCCESS) || (msg_type != GENAVB_MSG_LISTENER_RESPONSE) || (listener_response.status != GENAVB_SUCCESS)) { + ERR(STREAM_STR_FMT " failed: %s\n", STREAM_STR(listener_deregister.stream_id), genavb_strerror(rc)); + return -1; + } + + return 0; +} + +static int tsn_net_tx_srp_register(struct genavb_socket_tx_params *params) +{ + struct genavb_msg_talker_register talker_register; + struct genavb_msg_talker_response talker_response; + struct net_address *addr = ¶ms->addr; + unsigned int msg_type, msg_len; + int rc; + + talker_register.port = addr->port; + memcpy(talker_register.stream_id, tsn_stream_id, 8); + talker_register.stream_id[7] = addr->u.l2.dst_mac[5]; + + talker_register.params.stream_class = SR_CLASS_A; + memcpy(talker_register.params.destination_address, addr->u.l2.dst_mac, 6); + talker_register.params.vlan_id = ntohs(addr->vlan_id); + + talker_register.params.max_frame_size = 200; + talker_register.params.max_interval_frames = 1; + talker_register.params.accumulated_latency = 0; + talker_register.params.rank = NORMAL; + + msg_type = GENAVB_MSG_TALKER_REGISTER; + msg_len = sizeof(talker_response); + + rc = genavb_control_send_sync(s_msrp_handle, (genavb_msg_type_t *)&msg_type, &talker_register, sizeof(talker_register), &talker_response, &msg_len, 1000); + if ((rc != GENAVB_SUCCESS) || (msg_type != GENAVB_MSG_TALKER_RESPONSE) || (talker_response.status != GENAVB_SUCCESS)) { + ERR(STREAM_STR_FMT " failed: %s\n", STREAM_STR(talker_register.stream_id), genavb_strerror(rc)); + return -1; + } + + return 0; +} + +static int tsn_net_tx_srp_deregister(struct genavb_socket_tx_params *params) +{ + struct genavb_msg_talker_deregister talker_deregister; + struct genavb_msg_talker_response talker_response; + struct net_address *addr = ¶ms->addr; + unsigned int msg_type, msg_len; + int rc; + + talker_deregister.port = addr->port; + memcpy(talker_deregister.stream_id, tsn_stream_id, 8); + talker_deregister.stream_id[7] = addr->u.l2.dst_mac[5]; + + msg_type = GENAVB_MSG_TALKER_DEREGISTER; + msg_len = sizeof(talker_response); + rc = genavb_control_send_sync(s_msrp_handle, (genavb_msg_type_t *)&msg_type, &talker_deregister, sizeof(talker_deregister), &talker_response, &msg_len, 1000); + if ((rc != GENAVB_SUCCESS) || (msg_type != GENAVB_MSG_TALKER_RESPONSE) || (talker_response.status != GENAVB_SUCCESS)) { + ERR(STREAM_STR_FMT " failed: %s\n", STREAM_STR(talker_deregister.stream_id), genavb_strerror(rc)); + + return -1; + } + + return 0; +} +#endif + +static int tsn_task_net_init(struct tsn_task *task) +{ + int i, j, k, rc; + struct net_socket *sock; + +#ifdef SRP_RESERVATION + if (msrp_init(get_genavb_handle()) < 0) + return -1; +#endif + + for (i = 0; i < task->params->num_rx_socket; i++) { + sock = &task->sock_rx[i]; + sock->id = i; + sock->dir = RX; + + rc = genavb_socket_rx_open(&sock->genavb_rx, GENAVB_SOCKF_NONBLOCK, + &task->params->rx_params[i]); + if (rc != GENAVB_SUCCESS) { + ERR("genavb_socket_rx_open error: %s\n", genavb_strerror(rc)); + goto close_sock_rx; + } + + sock->buf = malloc(task->params->rx_buf_size); + if (!sock->buf) { + genavb_socket_rx_close(sock->genavb_rx); + ERR("error allocating rx_buff\n"); + goto close_sock_rx; + } + +#ifdef SRP_RESERVATION + tsn_net_rx_srp_register(&task->params->rx_params[i]); +#endif + } + + for (j = 0; j < task->params->num_tx_socket; j++) { + sock = &task->sock_tx[j]; + sock->id = j; + sock->dir = TX; + + rc = genavb_socket_tx_open(&sock->genavb_tx, 0, &task->params->tx_params[j]); + if (rc != GENAVB_SUCCESS) { + ERR("genavb_socket_tx_open error: %s\n", genavb_strerror(rc)); + goto close_sock_tx; + } + + sock->buf = malloc(task->params->tx_buf_size); + if (!sock->buf) { + genavb_socket_tx_close(sock->genavb_tx); + ERR("error allocating tx_buff\n"); + goto close_sock_tx; + } + +#ifdef SRP_RESERVATION + tsn_net_tx_srp_register(&task->params->tx_params[j]); +#endif + } + +#ifdef SRP_RESERVATION + msrp_exit(); +#endif + + return 0; + +close_sock_tx: + for (k = 0; k < j; k++) { + sock = &task->sock_tx[k]; +#ifdef SRP_RESERVATION + tsn_net_tx_srp_deregister(&task->params->tx_params[k]); +#endif + free(sock->buf); + genavb_socket_tx_close(sock->genavb_tx); + } + +close_sock_rx: + for (k = 0; k < i; k++) { + sock = &task->sock_rx[k]; +#ifdef SRP_RESERVATION + tsn_net_rx_srp_deregister(&task->params->rx_params[k]); +#endif + free(sock->buf); + genavb_socket_rx_close(sock->genavb_rx); + } + + return -1; +} + +static void tsn_task_net_exit(struct tsn_task *task) +{ + int i; + struct net_socket *sock; + + for (i = 0; i < task->params->num_rx_socket; i++) { + sock = &task->sock_rx[i]; +#ifdef SRP_RESERVATION + tsn_net_rx_srp_deregister(&task->params->rx_params[i]); +#endif + free(sock->buf); + genavb_socket_rx_close(sock->genavb_rx); + } + + for (i = 0; i < task->params->num_tx_socket; i++) { + sock = &task->sock_tx[i]; +#ifdef SRP_RESERVATION + tsn_net_tx_srp_deregister(&task->params->tx_params[i]); +#endif + free(sock->buf); + genavb_socket_tx_close(sock->genavb_tx); + } +} + +//note only cyclic mode supported +int tsn_task_register(struct tsn_task **task, struct tsn_task_params *params, + int id, int (*main_loop)(void *, unsigned int), void *ctx) +{ + char task_name[16]; + char timer_name[16]; + + *task = malloc(sizeof(struct tsn_task)); + if (!(*task)) + goto err; + + memset(*task, 0, sizeof(struct tsn_task)); + + (*task)->id = id; + (*task)->params = params; + (*task)->ctx = ctx; + + sprintf(task_name, "tsn task%1d", (*task)->id); + sprintf(timer_name, "task%1d timer", (*task)->id); + + if (tsn_task_net_init(*task) < 0) { + ERR("tsn_task_net_init error\n"); + goto err_free; + } + + tsn_task_stats_init(*task); + + (*task)->timer = tsn_timer_init((*task)->params->timer_type, THR_CAP_TSN_LOOP, ctx, main_loop); + if ((*task)->timer == NULL) { + ERR("timer registration() failed\n"); + goto net_exit; + } + + return 0; + +net_exit: + tsn_task_net_exit(*task); + +err_free: + free(*task); + +err: + return -1; +} + +void tsn_task_deregister(struct tsn_task *task) +{ + tsn_timer_exit(task->timer); + + tsn_task_net_exit(task); + + free(task); +} diff --git a/apps/linux/tsn-app/tsn_task.h b/apps/linux/tsn-app/tsn_task.h new file mode 100644 index 0000000..fca8e8c --- /dev/null +++ b/apps/linux/tsn-app/tsn_task.h @@ -0,0 +1,166 @@ +/* + * Copyright 2019-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TSN_TASK_H_ +#define _TSN_TASK_H_ + +#include "../common/stats.h" +#include "../common/thread.h" +#include "tsn_timer.h" + +#include +#include + +#define MAX_RX_SOCKET 2 +#define MAX_TX_SOCKET 2 + +#define RX 0 +#define TX 1 + +#define ST_TX_TIME_MARGIN 1000 /* Additional margin to account for drift between MAC and gPTP clocks */ +#define ST_TX_TIME_FACTOR 2 /* Factor applied to critical time interval, to avoid frames getting stuck */ +#define ST_LIST_LEN 3 + +#define SCHEDULE_LATENCY_THRESHOLD (250000) /* packets will not be sent if app is woken up more than threshold ns after expected time */ + +enum net_flags { + NET_OK, + NET_NO_FRAME, + NET_ERR, +}; + +struct tsn_task_params { + genavb_clock_id_t clk_id; + unsigned int task_period_ns; + unsigned int task_period_offset_ns; //modulo 1 second + unsigned int transfer_time_ns; + int sched_traffic_offset; + + unsigned int timer_type; + + int num_rx_socket; + int rx_buf_size; + struct genavb_socket_rx_params rx_params[MAX_RX_SOCKET]; + + int num_tx_socket; + int tx_buf_size; + struct genavb_socket_tx_params tx_params[MAX_TX_SOCKET]; +}; + +struct net_socket_stats { + unsigned int frames; + unsigned int err; + bool pending; +}; + +struct tsn_task_stats { + struct stats sched_err; + struct hist sched_err_hist; + struct stats proc_time; + struct hist proc_time_hist; + struct stats total_time; + struct hist total_time_hist; + bool stats_valid; + unsigned int sched; + unsigned int sched_early; + unsigned int sched_late; + unsigned int sched_missed; + unsigned int sched_timeout; + unsigned int clock_discont; + unsigned int clock_err; + unsigned int sched_err_max; + bool pending; +}; + +struct net_socket { + int id; + int dir; + union { + struct genavb_socket_rx *genavb_rx; + struct genavb_socket_tx *genavb_tx; + }; + void *buf; + int len; + uint64_t ts; + + struct net_socket_stats stats; + struct net_socket_stats stats_snap; +}; + +struct tsn_task { + int id; + struct tsn_timer *timer; + struct tsn_task_params *params; + void *ctx; + unsigned int clock_discont; + + struct net_socket sock_rx[MAX_RX_SOCKET]; + struct net_socket sock_tx[MAX_TX_SOCKET]; + + struct tsn_task_stats stats; + struct tsn_task_stats stats_snap; + + uint64_t sched_time; + uint64_t sched_now; +}; + +enum msg_type_id { + MSG_MOTOR_SET_IQ = 0x0, + MSG_MOTOR_FEEDBACK = 0x1, + MSG_SERIAL = 0x2, +}; + +struct tsn_common_hdr { + uint16_t msg_id; + uint16_t len; + uint16_t src_id; + uint64_t sched_time; +}; + +static inline uint64_t tsn_task_get_time(struct tsn_task *task) +{ + return task->sched_time; +} + +static inline uint64_t tsn_task_get_now(struct tsn_task *task) +{ + return task->sched_now; +} + +static inline struct net_socket *tsn_net_sock_rx(struct tsn_task *task, int id) +{ + return &task->sock_rx[id]; +} + +static inline struct net_socket *tsn_net_sock_tx(struct tsn_task *task, int id) +{ + return &task->sock_tx[id]; +} + +static inline void *tsn_net_sock_buf(struct net_socket *socket) +{ + return socket->buf; +} + +int tsn_task_register(struct tsn_task **task, struct tsn_task_params *params, + int id, int (*main_loop)(void *, unsigned int), void *ctx); +void tsn_task_deregister(struct tsn_task *task); +int tsn_task_start(struct tsn_task *task); +void tsn_task_stop(struct tsn_task *task); +int tsn_net_receive_set_cb(struct net_socket *sock, + void (*net_rx_cb)(void *)); +int tsn_net_receive_enable_cb(struct net_socket *sock); +int tsn_net_receive_sock(struct net_socket *sock); +int tsn_net_transmit_sock(struct net_socket *sock); +void tsn_stats_dump(struct tsn_task *task); +void tsn_task_stats_start(struct tsn_task *task, int count, uint64_t now); +void tsn_task_stats_end(struct tsn_task *task); +void tsn_task_stats_get_monitoring(struct tsn_task *task, uint32_t *sched_err_max, uint32_t *transmit_time_max, uint32_t nb_socket_rx); +void tsn_task_stats_print(struct tsn_task *task); +void tsn_task_stats_reset(struct tsn_task *task); +void net_socket_stats_print(struct net_socket *sock); + +#endif /* _TSN_TASK_H_ */ diff --git a/apps/linux/tsn-app/tsn_tasks_config.c b/apps/linux/tsn-app/tsn_tasks_config.c new file mode 100644 index 0000000..094b762 --- /dev/null +++ b/apps/linux/tsn-app/tsn_tasks_config.c @@ -0,0 +1,191 @@ +/* + * Copyright 2019-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "cyclic_task.h" +#include "tsn_tasks_config.h" + +struct tsn_stream tsn_streams[MAX_TSN_STREAMS] = { + [0] = { + .address = { + .ptype = PTYPE_L2, + .vlan_id = htons(VLAN_ID), + .priority = ISOCHRONOUS_DEFAULT_PRIORITY, + .u.l2 = { + .dst_mac = {0x91, 0xe0, 0xf0, 0x00, 0xfe, 0x70}, + .protocol = htons(ETHERTYPE_MOTOROLA), + }, + }, + }, + [1] = { + .address = { + .ptype = PTYPE_L2, + .vlan_id = htons(VLAN_ID), + .priority = ISOCHRONOUS_DEFAULT_PRIORITY, + .u.l2 = { + .dst_mac = {0x91, 0xe0, 0xf0, 0x00, 0xfe, 0x71}, + .protocol = htons(ETHERTYPE_MOTOROLA), + }, + }, + }, + [2] = { + .address = { + .ptype = PTYPE_L2, + .vlan_id = htons(VLAN_ID), + .priority = ISOCHRONOUS_DEFAULT_PRIORITY, + .u.l2 = { + .dst_mac = {0x91, 0xe0, 0xf0, 0x00, 0xfe, 0x80}, + .protocol = htons(ETHERTYPE_MOTOROLA), + }, + }, + }, + [3] = { + .address = { + .ptype = PTYPE_L2, + .vlan_id = htons(VLAN_ID), + .priority = EVENTS_DEFAULT_PRIORITY, + .u.l2 = { + .dst_mac = {0x91, 0xe0, 0xf0, 0x00, 0xfe, 0x90}, + .protocol = htons(ETHERTYPE_MOTOROLA), + }, + }, + }, + [4] = { + .address = { + .ptype = PTYPE_L2, + .vlan_id = htons(VLAN_ID), + .priority = EVENTS_DEFAULT_PRIORITY, + .u.l2 = { + .dst_mac = {0x91, 0xe0, 0xf0, 0x00, 0xfe, 0xa0}, + .protocol = htons(ETHERTYPE_MOTOROLA), + }, + }, + }, +}; + +#define CYCLIC_TASK_DEFAULT_PARAMS(offset) \ + { \ + .clk_id = GENAVB_CLOCK_GPTP_0_0, \ + .task_period_ns = APP_PERIOD, \ + .task_period_offset_ns = offset, \ + .sched_traffic_offset = SCHED_TRAFFIC_OFFSET, \ + .transfer_time_ns = NET_DELAY_OFFSET, \ + .rx_buf_size = PACKET_SIZE, \ + .tx_buf_size = PACKET_SIZE, \ + } + +struct cyclic_task cyclic_tasks[MAX_TASK_CONFIGS] = { + [0] = { + .type = CYCLIC_CONTROLLER, + .id = CONTROLLER_0, + .params = CYCLIC_TASK_DEFAULT_PARAMS(0), + .num_peers = 1, + .rx_socket = { + [0] = { + .peer_id = IO_DEVICE_0, + .stream_id = 1, + }, + [1] = { + .peer_id = IO_DEVICE_1, + .stream_id = 2, + }, + }, + .tx_socket = { + .stream_id = 0, + }, + }, + [1] = { + .type = CYCLIC_IO_DEVICE, + .id = IO_DEVICE_0, + .params = CYCLIC_TASK_DEFAULT_PARAMS(NET_DELAY_OFFSET), + .num_peers = 1, + .rx_socket = { + [0] = { + .peer_id = CONTROLLER_0, + .stream_id = 0, + }, + }, + .tx_socket = { + .stream_id = 1, + }, + }, + [2] = { + .type = CYCLIC_IO_DEVICE, + .id = IO_DEVICE_1, + .params = CYCLIC_TASK_DEFAULT_PARAMS(NET_DELAY_OFFSET), + .num_peers = 1, + .rx_socket = { + [0] = { + .peer_id = CONTROLLER_0, + .stream_id = 0, + }, + }, + .tx_socket = { + .stream_id = 2, + }, + }, +}; +#if 0 +#define ALARM_TASK_DEFAULT_PARAMS \ + { \ + .clk_id = GENAVB_CLOCK_GPTP_0_0, \ + .priority = TASK_DEFAULT_PRIORITY - 1, \ + .stack_depth = TASK_DEFAULT_STACK_SIZE, \ + .rx_buf_size = PACKET_SIZE, \ + .tx_buf_size = PACKET_SIZE, \ + } + +struct alarm_task alarm_tasks[MAX_TASK_CONFIGS] = { + [0] = { + .type = ALARM_MONITOR, + .id = CONTROLLER_0, + .params = ALARM_TASK_DEFAULT_PARAMS, + .num_peers = 2, + .stream_id = 4, + .queue = { + .length = TASK_DEFAULT_QUEUE_LENGTH, + }, + }, + [1] = { + .type = ALARM_IO_DEVICE, + .id = IO_DEVICE_0, + .params = ALARM_TASK_DEFAULT_PARAMS, + .num_peers = 1, + .stream_id = 4, + }, + [2] = { + .type = ALARM_IO_DEVICE, + .id = IO_DEVICE_1, + .params = ALARM_TASK_DEFAULT_PARAMS, + .num_peers = 1, + .stream_id = 4, + }, +}; +#endif + +struct tsn_stream *tsn_conf_get_stream(int index) +{ + if (index >= (sizeof(tsn_streams) / sizeof(struct tsn_stream))) + return NULL; + + return &tsn_streams[index]; +} + +struct cyclic_task *tsn_conf_get_cyclic_task(int index) +{ + if (index >= (sizeof(cyclic_tasks) / sizeof(struct cyclic_task))) + return NULL; + + return &cyclic_tasks[index]; +} +#if 0 +struct alarm_task *tsn_conf_get_alarm_task(int index) +{ + if (index >= (sizeof(alarm_tasks) / sizeof(struct alarm_task))) + return NULL; + + return &alarm_tasks[index]; +} +#endif diff --git a/apps/linux/tsn-app/tsn_tasks_config.h b/apps/linux/tsn-app/tsn_tasks_config.h new file mode 100644 index 0000000..d0c4623 --- /dev/null +++ b/apps/linux/tsn-app/tsn_tasks_config.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2020 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TSN_TASKS_CONFIG_H_ +#define _TSN_TASKS_CONFIG_H_ + +#include "genavb/tsn.h" +#include "genavb/qos.h" + +#define MAX_PEERS 2 +#define MAX_TASK_CONFIGS 4 +#define MAX_TSN_STREAMS 8 +#define ETHERTYPE_MOTOROLA 0x818D +#define VLAN_ID 2 +#define PACKET_SIZE 80 + +#define SCHED_TRAFFIC_OFFSET 40000 + +#define APP_PERIOD (2000000) +#define NET_DELAY_OFFSET (APP_PERIOD / 2) + +enum task_id { + CONTROLLER_0, + IO_DEVICE_0, + IO_DEVICE_1, + MAX_TASKS_ID +}; + +enum task_type { + CYCLIC_CONTROLLER, + CYCLIC_IO_DEVICE, + ALARM_MONITOR, + ALARM_IO_DEVICE, +}; + +struct tsn_stream { + struct net_address address; +}; + +struct tsn_stream *tsn_conf_get_stream(int index); +struct cyclic_task *tsn_conf_get_cyclic_task(int index); +struct alarm_task *tsn_conf_get_alarm_task(int index); + +#endif /* _TSN_TASKS_CONFIG_H_ */ diff --git a/apps/linux/tsn-app/tsn_timer.c b/apps/linux/tsn-app/tsn_timer.c new file mode 100644 index 0000000..74d0f26 --- /dev/null +++ b/apps/linux/tsn-app/tsn_timer.c @@ -0,0 +1,237 @@ +/* + * Copyright 2021-2022 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "tsn_timer.h" +#include "../common/log.h" +#include "../common/time.h" +#include "../common/timer.h" + +struct thread_sleep_epoll_timer_ctx { + thr_thread_slot_t *slot; // pointer to the slot used for the timer + int timer_fd; +}; + +static void *tsn_timer_epoll_init(int capabilities, void *ctx, + int (*handler)(void *data, unsigned int events), + thr_thread_slot_t **slot) +{ + struct thread_sleep_epoll_timer_ctx *timer_ctx; + + timer_ctx = malloc(sizeof(struct thread_sleep_epoll_timer_ctx)); + if (!timer_ctx) { + ERR("malloc() failed\n"); + goto err; + } + + timer_ctx->timer_fd = create_timerfd_periodic_abs(CLOCK_REALTIME); + if (timer_ctx->timer_fd < 0) { + ERR("create_timerfd_periodic_abs() failed\n"); + goto err_free; + } + + if (thread_slot_add(capabilities, timer_ctx->timer_fd, EPOLLIN, ctx, handler, NULL, 0, slot) < 0) { + ERR("thread_slot_add() failed\n"); + goto close_timerfd; + } + + timer_ctx->slot = *slot; + + return timer_ctx; + +close_timerfd: + close(timer_ctx->timer_fd); + +err_free: + free(timer_ctx); + +err: + return NULL; +} + +static void tsn_timer_epoll_exit(void *ctx) +{ + if (ctx) { + struct thread_sleep_epoll_timer_ctx *timer_ctx = (struct thread_sleep_epoll_timer_ctx *)ctx; + thread_slot_free(timer_ctx->slot); + + close(timer_ctx->timer_fd); + free(timer_ctx); + } +} + +static int tsn_timer_epoll_start(void *ctx, uint64_t start_time, unsigned int period_ns) +{ + struct thread_sleep_epoll_timer_ctx *timer_ctx = (struct thread_sleep_epoll_timer_ctx *)ctx; + time_t start_secs = start_time / (NSECS_PER_SEC); + unsigned int start_nsecs = start_time - ((uint64_t)start_secs * NSECS_PER_SEC); + + if (start_timerfd_periodic_abs(timer_ctx->timer_fd, start_secs, start_nsecs, 0, period_ns) < 0) { + ERR("start_timerfd_periodic_abs() error\n"); + return -1; + } + + return 0; +} + +static void tsn_timer_epoll_stop(void *ctx) +{ + if (ctx) { + struct thread_sleep_epoll_timer_ctx *timer_ctx = (struct thread_sleep_epoll_timer_ctx *)ctx; + + stop_timerfd(timer_ctx->timer_fd); + } +} + +static int tsn_timer_epoll_check(void *ctx, uint64_t now, uint64_t *n_time) +{ + struct thread_sleep_epoll_timer_ctx *timer_ctx = (struct thread_sleep_epoll_timer_ctx *)ctx; + + return read(timer_ctx->timer_fd, &n_time, sizeof(n_time)); +} + +struct thread_sleep_nanosleep_timer_ctx { + struct thread_sleep_nanosleep_data nsleep; + thr_thread_slot_t *slot; // pointer to the slot used for the timer + unsigned int period_ns; +}; + +static void *tsn_timer_nanosleep_init(int capabilities, void *ctx, + int (*handler)(void *data, unsigned int events), + thr_thread_slot_t **slot) +{ + struct thread_sleep_nanosleep_timer_ctx *timer_ctx; + + timer_ctx = malloc(sizeof(struct thread_sleep_nanosleep_timer_ctx)); + if (!timer_ctx) { + ERR("malloc() failed\n"); + goto err; + } + + timer_ctx->nsleep.is_armed = 0; + if (__thread_slot_add(capabilities, 0, 0, ctx, handler, NULL, 0, slot, thread_sleep_nanosleep, &timer_ctx->nsleep) < 0) { + ERR("__thread_slot_add() failed\n"); + goto err_free; + } + + timer_ctx->slot = *slot; + + return timer_ctx; + +err_free: + free(timer_ctx); + +err: + return NULL; +} + +static int tsn_timer_nanosleep_start(void *ctx, uint64_t start_time, unsigned int period_ns) +{ + struct thread_sleep_nanosleep_timer_ctx *timer_ctx = (struct thread_sleep_nanosleep_timer_ctx *)ctx; + + timer_ctx->nsleep.next = start_time; + timer_ctx->nsleep.is_armed = 1; + timer_ctx->period_ns = period_ns; + + return 0; +} + +static void tsn_timer_nanosleep_stop(void *ctx) +{ + if (ctx) { + struct thread_sleep_nanosleep_timer_ctx *timer_ctx = (struct thread_sleep_nanosleep_timer_ctx *)ctx; + + timer_ctx->nsleep.is_armed = 0; + } +} + +static void tsn_timer_nanosleep_exit(void *ctx) +{ + if (ctx) { + struct thread_sleep_nanosleep_timer_ctx *timer_ctx = (struct thread_sleep_nanosleep_timer_ctx *)ctx; + + thread_slot_free(timer_ctx->slot); + free(timer_ctx); + } +} + +static int tsn_timer_nanosleep_check(void *ctx, uint64_t now, uint64_t *n_time) +{ + struct thread_sleep_nanosleep_timer_ctx *timer_ctx = (struct thread_sleep_nanosleep_timer_ctx *)ctx; + + if ((now < timer_ctx->nsleep.next) || ((now - timer_ctx->nsleep.next) > 10000000000UL)) { + return -1; + } + + *n_time = ((now - timer_ctx->nsleep.next) / timer_ctx->period_ns) + 1; + + timer_ctx->nsleep.next += *n_time * timer_ctx->period_ns; + timer_ctx->nsleep.is_armed = 1; + DBG("now(%" PRIu64 ") sched_next(%" PRIu64 ") n_time(%" PRIu64 ") sched_time(%" PRIu64 ")", now, task->nsleep.next, *n_time, task->sched_time); + + return 0; +} + +static struct tsn_timer_ops tsn_timer_ops[TSN_TIMER_MAX] = { + [0] = { + .init = tsn_timer_epoll_init, + .exit = tsn_timer_epoll_exit, + .start = tsn_timer_epoll_start, + .stop = tsn_timer_epoll_stop, + .check = tsn_timer_epoll_check, + .type = "epoll", + }, + [1] = { + .init = tsn_timer_nanosleep_init, + .exit = tsn_timer_nanosleep_exit, + .start = tsn_timer_nanosleep_start, + .stop = tsn_timer_nanosleep_stop, + .check = tsn_timer_nanosleep_check, + .type = "nanosleep", + }}; + +struct tsn_timer *tsn_timer_init(unsigned int timer_type, int capabilities, void *ctx, + int (*handler)(void *data, unsigned int events)) +{ + struct tsn_timer *timer; + + if (timer_type >= TSN_TIMER_MAX) { + ERR("Unknown timer type %d", timer_type); + goto err; + } + + timer = malloc(sizeof(struct tsn_timer)); + if (!timer) { + ERR("malloc() failed\n"); + goto err; + } + + timer->ops = &tsn_timer_ops[timer_type]; + + timer->ctx = timer->ops->init(capabilities, ctx, handler, &timer->slot); + + if (!timer->ctx) { + ERR("timer registration failed"); + goto err_register; + } + + INF("sleep handler : %s", timer->ops->type); + + return timer; + +err_register: + free(timer); + +err: + return NULL; +} + +void tsn_timer_exit(struct tsn_timer *timer) +{ + timer->ops->exit(timer->ctx); + + free(timer); +} diff --git a/apps/linux/tsn-app/tsn_timer.h b/apps/linux/tsn-app/tsn_timer.h new file mode 100644 index 0000000..1f2285c --- /dev/null +++ b/apps/linux/tsn-app/tsn_timer.h @@ -0,0 +1,55 @@ +/* + * Copyright 2021 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TSN_TIMER_H_ +#define _TSN_TIMER_H_ + +#include +#include "../common/thread.h" + +#define TSN_TIMER_EPOLL 0 +#define TSN_TIMER_NANOSLEEP 1 +#define TSN_TIMER_MAX 2 + +struct tsn_timer_ops { + void *(*init)(int capabilities, void *ctx, + int (*handler)(void *data, unsigned int events), + thr_thread_slot_t **slot); + void (*exit)(void *timer_ctx); + int (*start)(void *timer_ctx, uint64_t start_time, unsigned int period_ns); + void (*stop)(void *timer_ctx); + int (*check)(void *timer_ctx, uint64_t now, uint64_t *n_time); + + char *type; +}; + +struct tsn_timer { + thr_thread_slot_t *slot; + struct tsn_timer_ops *ops; + void *ctx; +}; + +struct tsn_timer *tsn_timer_init(unsigned int timer_type, int capabilities, void *ctx, + int (*handler)(void *data, unsigned int events)); + +void tsn_timer_exit(struct tsn_timer *timer); + +static inline int tsn_timer_start(struct tsn_timer *timer, uint64_t start_time, unsigned int period_ns) +{ + return timer->ops->start(timer->ctx, start_time, period_ns); +} + +static inline void tsn_timer_stop(struct tsn_timer *timer) +{ + timer->ops->stop(timer->ctx); +} + +static inline int tsn_timer_check(struct tsn_timer *timer, uint64_t now, uint64_t *n_time) +{ + return timer->ops->check(timer->ctx, now, n_time); +} + +#endif /* _TSN_TIMER_H_ */ diff --git a/apps/rtos/aem-manager/aem-manager-rtos.cmake b/apps/rtos/aem-manager/aem-manager-rtos.cmake new file mode 100644 index 0000000..8e095be --- /dev/null +++ b/apps/rtos/aem-manager/aem-manager-rtos.cmake @@ -0,0 +1,15 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../../common/aem-manager/aem-manager-helpers.cmake) + +message("aem-manager-rtos and common aem-manager-helpers headers are included for target ${AEM_MANAGER_RTOS_TARGET}") + +target_include_directories(${AEM_MANAGER_RTOS_TARGET} PRIVATE + ${AEM_MANAGER_HELPERS_HEADER_DIR} + ${CMAKE_CURRENT_LIST_DIR} +) + +message("aem-manager-rtos and common aem-manager-helpers sources are included for target ${AEM_MANAGER_RTOS_TARGET}") + +target_sources(${AEM_MANAGER_RTOS_TARGET} PRIVATE + ${AEM_MANAGER_HELPERS_SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/aem_manager_rtos.c +) diff --git a/apps/rtos/aem-manager/aem_manager_rtos.c b/apps/rtos/aem-manager/aem_manager_rtos.c new file mode 100644 index 0000000..21787aa --- /dev/null +++ b/apps/rtos/aem-manager/aem_manager_rtos.c @@ -0,0 +1,152 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "rtos_abstraction_layer.h" +#include "log.h" + +#include "aem_manager_helpers.h" + +#include "genavb/aem.h" +#include "genavb/aem_helpers.h" + +#include "genavb_sdk.h" + +void aem_print_level(int level, const char *format, ...) +{ + int i; + va_list ap; + + for (i = 0; i < level; i++) + sdk_printf("\t"); + + va_start(ap, format); + + sdk_vprintf(format, ap); + + va_end(ap); +} + +struct aem_desc_hdr *aem_entity_load_from_reference_entity(struct aem_desc_hdr *aem_src) +{ + struct aem_desc_hdr *aem_desc; + unsigned int offset = 0, len; + size_t total_desc_size = 0; + int i; + + for (i = 0; i < AEM_NUM_DESC_TYPES; i++) + total_desc_size += aem_src[i].total * aem_src[i].size; + + aem_desc = rtos_malloc(AEM_NUM_DESC_TYPES * sizeof(struct aem_desc_hdr) + total_desc_size); + if (!aem_desc) { + aem_print_level(0, "entity() malloc() failed\n"); + goto err_malloc; + } + + memset(aem_desc, 0, AEM_NUM_DESC_TYPES * sizeof(struct aem_desc_hdr) + total_desc_size); + + for (i = 0; i < AEM_NUM_DESC_TYPES; i++) { + memcpy(&aem_desc[i].total, &aem_src[i].total, sizeof(avb_u16)); + + if (aem_desc[i].total > AEM_DESC_MAX_NUM) { + aem_print_level(0, "entity() desc header(%d): invalid total desc number: %d\n", i, aem_desc[i].total); + goto err_read; + } + + memcpy(&aem_desc[i].size, &aem_src[i].size, sizeof(avb_u16)); + + if (aem_desc[i].size > AEM_DESC_MAX_LENGTH) { + aem_print_level(0, "entity() desc header(%d): invalid length: %d\n", i, aem_desc[i].size); + goto err_read; + } + } + + offset = AEM_NUM_DESC_TYPES * sizeof(struct aem_desc_hdr); + + for (i = 0; i < AEM_NUM_DESC_TYPES; i++) { + aem_desc[i].ptr = (char *)aem_desc + offset; + + len = aem_desc[i].size * aem_desc[i].total; + + memcpy(aem_desc[i].ptr, aem_src[i].ptr, len); + + offset += len; + } + + aem_print_level(0, "Loaded AVDECC entity()\n"); + + return aem_desc; + +err_read: + rtos_free(aem_desc); + +err_malloc: + return NULL; +} + +struct aem_desc_handler desc_handler[AEM_NUM_DESC_TYPES] = { + [AEM_DESC_TYPE_ENTITY] = { + .fixup = aem_entity_desc_fixup, + .check = entity_desc_check, + .print = entity_desc_print, + .update_name = entity_desc_update_name, + }, + + [AEM_DESC_TYPE_CONFIGURATION] = { + .fixup = aem_configuration_desc_fixup, + .check = configuration_desc_check, + .print = configuration_desc_print, + }, + + [AEM_DESC_TYPE_AUDIO_UNIT] = { + .print = audio_unit_desc_print, + }, + + [AEM_DESC_TYPE_VIDEO_UNIT] = { + .print = video_unit_desc_print, + }, + + [AEM_DESC_TYPE_STREAM_INPUT] = { + .print = stream_input_desc_print, + .update_name = stream_input_desc_update_name, + }, + + [AEM_DESC_TYPE_STREAM_OUTPUT] = { + .print = stream_output_desc_print, + .update_name = stream_output_desc_update_name, + }, + + [AEM_DESC_TYPE_VIDEO_CLUSTER] = { + .fixup = aem_video_cluster_desc_fixup, + }, + + [AEM_DESC_TYPE_CONTROL] = { + .print = control_desc_print, + }, + +}; + +int aem_entity_create(struct aem_desc_hdr *aem_desc, void (*entity_init)(struct aem_desc_hdr *aem_desc)) +{ + int rc = 0; + + entity_init(aem_desc); + + aem_entity_fixup(desc_handler, aem_desc); + + if (aem_entity_check(desc_handler, aem_desc) < 0) { + aem_print_level(0, "aem_desc(%p) failed to create entity\n", aem_desc); + rc = -1; + goto out; + } + +out: + return rc; +} + +void aem_entity_free(struct aem_desc_hdr *aem_desc) +{ + rtos_free(aem_desc); +} diff --git a/apps/rtos/aem-manager/aem_manager_rtos.h b/apps/rtos/aem-manager/aem_manager_rtos.h new file mode 100644 index 0000000..2bbebeb --- /dev/null +++ b/apps/rtos/aem-manager/aem_manager_rtos.h @@ -0,0 +1,17 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _AEM_MANAGER_COMMON_RTOS_H_ +#define _AEM_MANAGER_COMMON_RTOS_H_ + +#include "genavb/aem_helpers.h" + +struct aem_desc_hdr *aem_entity_load_from_reference_entity(struct aem_desc_hdr *aem_src); + +int aem_entity_create(struct aem_desc_hdr *aem_desc, void (*entity_init)(struct aem_desc_hdr *aem_desc)); +void aem_entity_free(struct aem_desc_hdr *aem_desc); + +#endif /* _AEM_MANAGER_COMMON_RTOS_H_ */ diff --git a/avdecc/acmp.c b/avdecc/acmp.c new file mode 100644 index 0000000..2782118 --- /dev/null +++ b/avdecc/acmp.c @@ -0,0 +1,1076 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ACMP common code + @details Handles ACMP common stack functions between IEEE 1722.1 and AVnu MILAN +*/ + +#include "os/stdlib.h" + +#include "common/log.h" +#include "common/ether.h" +#include "common/hash.h" + +#include "acmp.h" +#include "acmp_ieee.h" +#include "acmp_milan.h" +#include "avdecc.h" + + +static const u8 acmp_dst_mac[6] = MC_ADDR_AVDECC_ADP_ACMP; + +int acmp_inflight_timeout(struct inflight_ctx *entry); + +static const char *acmp_msgtype2string(struct acmp_ctx *acmp, acmp_message_type_t msg_type) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + + if (!avdecc->milan_mode) + return acmp_ieee_msgtype2string(msg_type); + else + return acmp_milan_msgtype2string(msg_type); +} + +static int acmp_get_command_timeout_ms(struct acmp_ctx *acmp, u8 msg_type) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + + if (!avdecc->milan_mode) + return acmp_ieee_get_command_timeout_ms(msg_type); + else + return acmp_milan_get_command_timeout_ms(msg_type); +} + +/** Sends an ACMP Response message to an IPC + * \return 0 on success, -1 otherwise + * \param acmp pointer to the ACMP context + * \param pdu pointer to the ACMP PDU command + * \param message_type ACMP message type + * \param ipc IPC the command came through (will be stored in the inflight entry to send the response through the same channel). + * \param ipc_dst Slot, within the ipc channel, the message was received from (will be stored in the inflight entry to send the response to the same slot). + */ +static int acmp_ipc_tx_rsp(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 message_type, u8 status, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + struct ipc_desc *desc; + int rc = -1; + + desc = ipc_alloc(ipc, sizeof(struct genavb_acmp_response)); + if (desc) { + desc->dst = ipc_dst; + desc->type = GENAVB_MSG_ACMP_RESPONSE; + desc->len = sizeof(struct genavb_acmp_response); + os_memset(&desc->u.acmp_response, 0 , sizeof(struct genavb_acmp_response)); + + desc->u.acmp_response.message_type = message_type; + desc->u.acmp_response.status = status; + copy_64(&desc->u.acmp_response.stream_id, &pdu->stream_id); + copy_64(&desc->u.acmp_response.talker_entity_id, &pdu->talker_entity_id); + copy_64(&desc->u.acmp_response.listener_entity_id, &pdu->listener_entity_id); + desc->u.acmp_response.talker_unique_id = pdu->talker_unique_id; + desc->u.acmp_response.listener_unique_id = pdu->listener_unique_id; + os_memcpy(desc->u.acmp_response.stream_dest_mac, &pdu->stream_dest_mac, 6); + desc->u.acmp_response.connection_count = pdu->connection_count; + desc->u.acmp_response.flags = pdu->flags; + desc->u.acmp_response.stream_vlan_id = pdu->stream_vlan_id; + + if (ipc_tx(ipc, desc) < 0) { + os_log(LOG_ERR, "acmp(%p) ipc_tx() through ipc(%p) failed for message_type(%d) status(%d)\n", + acmp, ipc, message_type, status); + goto err_ipc_tx; + } + + rc = 0; + } else { + os_log(LOG_ERR, "acmp(%p) ipc_alloc() failed for message_type(%d) status(%d)\n", + acmp, message_type, status); + } + + return rc; + +err_ipc_tx: + ipc_free(ipc, desc); + return rc; +} +/** Allocate a network tx descriptor for ACMP PDU and init all fields to 0. + * \return the allocated and initialized network descriptor, or NULL on allocation failure + * \param acmp pointer to the ACMP context + * \param port pointer to the avdecc port on which the packet will be sent + */ +struct net_tx_desc *acmp_net_tx_alloc(struct acmp_ctx *acmp, struct avdecc_port *port) +{ + struct net_tx_desc *desc; + struct acmp_pdu *new_pdu; + + desc = net_tx_alloc(&port->net_tx, ACMP_NET_DATA_SIZE); + if (!desc) { + os_log(LOG_ERR, "acmp(%p) Cannot alloc tx descriptor\n", acmp); + goto exit; + } + + new_pdu = (struct acmp_pdu *)((char *)NET_DATA_START(desc) + OFFSET_TO_ACMP); + + os_memset(new_pdu, 0 , sizeof(struct acmp_pdu)); + +exit: + return desc; +} + +/** Allocate a network tx descriptor for ACMP command or response based on fields from a received ACMP PDU + * Only controller_entity_id, talker_entity_id, talker_unique_id, listener_entity_id, listener_unique_id + * and sequence_id (for response only) are copied, other fields are set to 0. + * \return the allocated and initialized network descriptor, or NULL on allocation failure + * \param acmp pointer to the ACMP context + * \param port pointer to the avdecc port on which the packet will be sent + * \param pdu pointer to the ACMP PDU + * \param is_resp true for ACMP response (copy the sequence_id) or false to keep sequence_id to 0 + */ +struct net_tx_desc *acmp_net_tx_init(struct acmp_ctx *acmp, struct avdecc_port *port, struct acmp_pdu *pdu, bool is_resp) +{ + struct net_tx_desc *desc; + struct acmp_pdu *new_pdu; + + desc = acmp_net_tx_alloc(acmp, port); + if (!desc) + goto exit; + + new_pdu = (struct acmp_pdu *)((char *)NET_DATA_START(desc) + OFFSET_TO_ACMP); + + copy_64(&new_pdu->controller_entity_id, &pdu->controller_entity_id); + copy_64(&new_pdu->talker_entity_id, &pdu->talker_entity_id); + copy_64(&new_pdu->listener_entity_id, &pdu->listener_entity_id); + + new_pdu->talker_unique_id = pdu->talker_unique_id; + new_pdu->listener_unique_id = pdu->listener_unique_id; + + if (is_resp) + new_pdu->sequence_id = pdu->sequence_id; + +exit: + return desc; +} + +/** Sends an ACMP PDU packet to the network. + * \return 0 on success, -1 otherwise + * \param acmp pointer to the ACMP context + * \param port pointer to the avdecc port on which the packet will be sent + * \param desc pointer to the network TX descriptor + * \param msg_type ACMP message type + * \param status ACMP status + */ +static int acmp_send_packet(struct acmp_ctx *acmp, struct avdecc_port *port, struct net_tx_desc *desc, u8 msg_type, u16 status) +{ + struct acmp_pdu *pdu; + void *buf; + + buf = NET_DATA_START(desc); + pdu = (struct acmp_pdu *)((char *)buf + OFFSET_TO_ACMP); + + desc->len += net_add_eth_header(buf, acmp_dst_mac, ETHERTYPE_AVTP); + desc->len += avdecc_add_common_header((char *)buf + desc->len, AVTP_SUBTYPE_ACMP, msg_type, ACMP_PDU_LEN, status); + desc->len += sizeof(struct acmp_pdu); + + os_log(LOG_INFO, "acmp(%p) port(%u) %s: controller(%016"PRIx64") talker(%016"PRIx64", %u) listener(%016"PRIx64", %u)\n", + acmp, port->port_id, acmp_msgtype2string(acmp, msg_type), ntohll(pdu->controller_entity_id), + ntohll(pdu->talker_entity_id), ntohs(pdu->talker_unique_id), + ntohll(pdu->listener_entity_id), ntohs(pdu->listener_unique_id)); + + if (avdecc_net_tx(port, desc) < 0) { + os_log(LOG_ERR, "acmp(%p) port(%u) send failed\n", acmp, port->port_id); + goto err; + } + + return 0; + +err: + return -1; +} + +/** Sends an ACMP PDU command to the network. + * An inflight context is created to manage timeout and answer from the remote entity. + * \return 0 on success, -1 otherwise + * \param acmp pointer to the ACMP context + * \param port pointer to the avdecc port on which the packet will be sent + * \param pdu pointer to the ACMP PDU command + * \param desc pointer to the network TX descriptor + * \param msg_type ACMP message type + * \param retried boolean, true if this command is a retry + * \param orig_seq_id the sequence ID of the command which initiated this command + * \param ipc IPC channel the command came through (will be stored in the inflight entry to send the response through the same channel). + * \param ipc_dst Slot, within the ipc channel, the message was received from (will be stored in the inflight entry to send the response to the same slot). + */ +int acmp_send_cmd(struct acmp_ctx *acmp, struct avdecc_port *port, struct acmp_pdu *pdu, struct net_tx_desc *desc, + u8 msg_type, u8 retried, u16 orig_seq_id, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + + if (!retried) { + struct inflight_ctx *entry = avdecc_inflight_get(entity); + + pdu->sequence_id = htons(acmp->sequence_id); + if (entry){ + int cmd_timeout = acmp_get_command_timeout_ms(acmp, msg_type); + if (cmd_timeout < 0) { + os_log(LOG_ERR, "acmp(%p) Invalid timeout for command %u\n", acmp, msg_type); + } else { + + entry->cb = acmp_inflight_timeout; + entry->data.msg_type = msg_type; + entry->data.retried = 0; + entry->data.sequence_id = acmp->sequence_id; + entry->data.orig_seq_id = orig_seq_id; + entry->data.port_id = port->port_id; + entry->data.pdu.acmp = *pdu; + entry->data.priv[0] = (uintptr_t)ipc; + entry->data.priv[1] = (uintptr_t)ipc_dst; + + if (avdecc_inflight_start(&acmp->inflight, entry, cmd_timeout) < 0) + os_log(LOG_ERR, "acmp(%p) Could not start inflight\n", acmp); + } + } + acmp->sequence_id++; + } + + return acmp_send_packet(acmp, port, desc, msg_type, ACMP_STAT_SUCCESS); +} + +/** Sends an ACMP PDU response to the network. + * \return 0 on success, -1 otherwise + * \param acmp pointer to the ACMP context + * \param port pointer to the avdecc port on which the packet will be sent + * \param desc pointer to the network TX descriptor + * \param msg_type ACMP message type + * \param status ACMP status + */ +int acmp_send_rsp(struct acmp_ctx *acmp, struct avdecc_port *port, struct net_tx_desc *desc, u8 msg_type, u16 status) +{ + return acmp_send_packet(acmp, port, desc, msg_type, status); +} + +/** Copy common (1722.1 and Milan) listener params to PDU + * \return none + * \param pdu pointer to the ACMP PDU + * \param listener_params pointer to the stream_input_dynamic_desc structure + */ +void acmp_listener_copy_common_params(struct acmp_pdu *pdu, struct stream_input_dynamic_desc *listener_params) +{ + copy_64(&pdu->stream_id, &listener_params->stream_id); + + copy_64(&pdu->talker_entity_id, &listener_params->talker_entity_id); + pdu->talker_unique_id = listener_params->talker_unique_id; + + os_memcpy(pdu->stream_dest_mac, listener_params->stream_dest_mac, 6); + + pdu->flags = listener_params->flags; + pdu->stream_vlan_id = listener_params->stream_vlan_id; +} + +/** Perform SRP and AVTP listener connection + * Sends an indication to AVTP / media application and initiates, if enabled, the SRP + * listener registration. + * \return ACMP status + * \param entity pointer to the entity context + * \param listener_unique_id valid listener unique id (in host order) + * \param flags ACMP flags (in host order) + */ +u8 acmp_listener_stack_connect(struct entity *entity, u16 listener_unique_id, u16 flags) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + struct stream_descriptor *stream_input; + struct stream_input_dynamic_desc *stream_input_dynamic; + struct ipc_desc *desc; + u8 rc = ACMP_STAT_SUCCESS; + unsigned int port_id; + + /* FIXME do we need to wait for a status ? */ + + /* Send listener connect to AVTP */ + stream_input = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + if (!stream_input) { + os_log(LOG_ERR, "acmp(%p) cannot find descriptor type %d index %u\n", + &entity->acmp, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id); + rc = ACMP_STAT_LISTENER_MISBEHAVING; + goto exit; + } + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + if (!stream_input_dynamic) { + os_log(LOG_ERR, "acmp(%p) cannot find dynamic descriptor type %d index %u\n", + &entity->acmp, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id); + rc = ACMP_STAT_LISTENER_MISBEHAVING; + goto exit; + } + + port_id = ntohs(stream_input->avb_interface_index); + + desc = ipc_alloc(&avdecc->ipc_tx_media_stack, sizeof(struct genavb_msg_media_stack_connect)); + if (desc) { + desc->dst = IPC_DST_ALL; + desc->type = GENAVB_MSG_MEDIA_STACK_CONNECT; + desc->len = sizeof(struct genavb_msg_media_stack_connect); + + os_memcpy(desc->u.media_stack_connect.stream_params.dst_mac, stream_input_dynamic->stream_dest_mac, 6); + copy_64(&desc->u.media_stack_connect.stream_params.stream_id, &stream_input_dynamic->stream_id); + desc->u.media_stack_connect.stream_params.port = avdecc_port_to_logical(avdecc, port_id); + copy_64(&desc->u.media_stack_connect.stream_params.format, &stream_input->current_format); + desc->u.media_stack_connect.stream_params.subtype = desc->u.media_stack_connect.stream_params.format.u.s.subtype; + desc->u.media_stack_connect.stream_params.stream_class = (flags & ACMP_FLAG_CLASS_B) ? sr_class_low() : sr_class_high(); + desc->u.media_stack_connect.stream_params.direction = AVTP_DIRECTION_LISTENER; + desc->u.media_stack_connect.entity_index = entity->index; + desc->u.media_stack_connect.configuration_index = ntohs(entity->desc->current_configuration); + desc->u.media_stack_connect.stream_index = listener_unique_id; + + desc->u.media_stack_connect.flags = flags; + + /* FIXME */ + desc->u.media_stack_connect.stream_params.flags = IPC_AVTP_FLAGS_MCR; + desc->u.media_stack_connect.stream_params.clock_domain = GENAVB_MEDIA_CLOCK_DOMAIN_STREAM; + /* FIXME */ + + if (ipc_tx(&avdecc->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + ipc_free(&avdecc->ipc_tx_media_stack, desc); + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + } + + + if (avdecc->srp_enabled) { + /* Send listener connect to MSRP */ + desc = ipc_alloc(&avdecc->port[port_id].ipc_tx_srp, sizeof(struct ipc_msrp_listener_register)); + if (desc) { + desc->type = GENAVB_MSG_LISTENER_REGISTER; + desc->len = sizeof(struct ipc_msrp_listener_register); + + desc->u.msrp_listener_register.port = avdecc_port_to_logical(avdecc, port_id); + copy_64(&desc->u.msrp_listener_register.stream_id, &stream_input_dynamic->stream_id); + + if (ipc_tx(&avdecc->port[port_id].ipc_tx_srp, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + ipc_free(&avdecc->port[port_id].ipc_tx_srp, desc); + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + } + } + +exit: + return rc; +} + +/** Disconnects the stream from the AVDECC listener (8.2.2.5.2.7). + * Send an indication to AVTP / media application and initiates the SRP + * listener de-registration. + * \return ACMP status + * \param entity pointer to the entity context + * \param listener_unique_id valid listener unique ID (in host order) + */ +u8 acmp_listener_stack_disconnect(struct entity *entity, u16 listener_unique_id) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + struct stream_descriptor *stream_input; + struct stream_input_dynamic_desc *stream_input_dynamic; + struct ipc_desc *desc; + u8 rc = ACMP_STAT_SUCCESS; + unsigned int port_id; + + /* FIXME do we need to wait for a status ? */ + + stream_input = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + if (!stream_input) { + os_log(LOG_ERR, "acmp(%p) cannot find descriptor type %d index %u\n", + &entity->acmp, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id); + rc = ACMP_STAT_LISTENER_MISBEHAVING; + goto exit; + } + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + if (!stream_input_dynamic) { + os_log(LOG_ERR, "acmp(%p) cannot find dynamic descriptor type %d index %u\n", + &entity->acmp, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id); + rc = ACMP_STAT_LISTENER_MISBEHAVING; + goto exit; + } + + port_id = ntohs(stream_input->avb_interface_index); + + if (avdecc->srp_enabled) { + /* Send listener disconnect to MSRP */ + desc = ipc_alloc(&avdecc->port[port_id].ipc_tx_srp, sizeof(struct ipc_msrp_listener_deregister)); + if (desc) { + desc->type = GENAVB_MSG_LISTENER_DEREGISTER; + desc->len = sizeof(struct ipc_msrp_listener_deregister); + + desc->u.msrp_listener_deregister.port = avdecc_port_to_logical(avdecc, port_id); + copy_64(&desc->u.msrp_listener_deregister.stream_id, &stream_input_dynamic->stream_id); + + if (ipc_tx(&avdecc->port[port_id].ipc_tx_srp, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + ipc_free(&avdecc->port[port_id].ipc_tx_srp, desc); + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + } + } + + /* Send listener disconnect to AVTP */ + desc = ipc_alloc(&avdecc->ipc_tx_media_stack, sizeof(struct genavb_msg_media_stack_disconnect)); + if (desc) { + desc->dst = IPC_DST_ALL; + desc->type = GENAVB_MSG_MEDIA_STACK_DISCONNECT; + desc->len = sizeof(struct genavb_msg_media_stack_disconnect); + + desc->u.media_stack_disconnect.stream_index = listener_unique_id; + copy_64(&desc->u.media_stack_disconnect.stream_id, &stream_input_dynamic->stream_id); + desc->u.media_stack_disconnect.port = avdecc_port_to_logical(avdecc, port_id); + desc->u.media_stack_disconnect.stream_class = (ntohs(stream_input_dynamic->flags) & ACMP_FLAG_CLASS_B) ? sr_class_low() : sr_class_high(); + desc->u.media_stack_disconnect.direction = AVTP_DIRECTION_LISTENER; + + if (ipc_tx(&avdecc->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + ipc_free(&avdecc->ipc_tx_media_stack, desc); + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + } + + os_log(LOG_INFO, "avdecc(%p) stream_id(%016"PRIx64")\n", avdecc, ntohll(stream_input_dynamic->stream_id)); + +exit: + return rc; +} + +/** Checks if the provided listener unique ID is valid for the AVDECC entity (8.2.2.5.2.1). + * \return 1 if unique id is valid, 0 otherwise + * \param acmp pointer to the ACMP context + * \param unique_id unique ID + */ +int acmp_listener_unique_valid(struct acmp_ctx *acmp, u16 unique_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + + if (aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT, unique_id, NULL)) + return 1; + else + return 0; +} + +/** Checks if the provided talker unique ID is valid for the AVDECC entity (8.2.2.6.2.1). + * \return 1 if unique id is valid, 0 otherwise + * \param acmp pointer to the ACMP context + * \param unique_id unique ID + */ +int acmp_talker_unique_valid(struct acmp_ctx *acmp, u16 unique_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + + if (aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, unique_id, NULL)) + return 1; + else + return 0; +} + +/** Copy common (1722.1 and Milan) talker params to PDU + * \return none + * \param pdu pointer to the ACMP PDU + * \param talker_params pointer to the stream_output_dynamic_desc structure + */ +void acmp_talker_copy_common_params(struct acmp_pdu *pdu, struct stream_output_dynamic_desc *talker_params) +{ + copy_64(&pdu->stream_id, &talker_params->stream_id); + os_memcpy(pdu->stream_dest_mac, talker_params->stream_dest_mac, 6); + pdu->stream_vlan_id = talker_params->stream_vlan_id; +} + +/** Perform SRP talker connection + * \return 0 on success, otherwise -1 + * \param avdecc pointer to the avdecc context + * \param port_id port / AVB interface index + * \param stream_output pointer to the stream output descriptor + * \param stream_output_dynamic pointer to the stream output dynamic descriptor + * \param flags ACMP flags (in host order) + */ +int acmp_talker_declare(struct avdecc_ctx *avdecc, unsigned int port_id, struct stream_descriptor *stream_output, struct stream_output_dynamic_desc *stream_output_dynamic, u16 flags) +{ + struct ipc_desc *desc; + int rc = 0; + + if (avdecc->srp_enabled) { + /* Send talker connect to MSRP */ + desc = ipc_alloc(&avdecc->port[port_id].ipc_tx_srp, sizeof(struct ipc_msrp_talker_register)); + if (desc) { + unsigned int max_frame_size, max_interval_frames; + + desc->type = GENAVB_MSG_TALKER_REGISTER; + desc->len = sizeof(struct ipc_msrp_talker_register); + + desc->u.msrp_talker_register.port = avdecc_port_to_logical(avdecc, port_id); + copy_64(&desc->u.msrp_talker_register.stream_id, &stream_output_dynamic->stream_id); + desc->u.msrp_talker_register.params.stream_class = (flags & ACMP_FLAG_CLASS_B) ? sr_class_low() : sr_class_high(); + os_memcpy(desc->u.msrp_talker_register.params.destination_address, stream_output_dynamic->stream_dest_mac, 6); + + if (stream_output_dynamic->stream_vlan_id) + desc->u.msrp_talker_register.params.vlan_id = ntohs(stream_output_dynamic->stream_vlan_id); + else + desc->u.msrp_talker_register.params.vlan_id = VLAN_VID_DEFAULT; + + avdecc_fmt_tspec((struct avdecc_format *)&stream_output->current_format, desc->u.msrp_talker_register.params.stream_class, &max_frame_size, &max_interval_frames); + + desc->u.msrp_talker_register.params.max_frame_size = max_frame_size; + desc->u.msrp_talker_register.params.max_interval_frames = max_interval_frames; + desc->u.msrp_talker_register.params.accumulated_latency = 0; + desc->u.msrp_talker_register.params.rank = NORMAL; + + if (ipc_tx(&avdecc->port[port_id].ipc_tx_srp, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx_srp() failed\n", avdecc); + ipc_free(&avdecc->port[port_id].ipc_tx_srp, desc); + rc = -1; + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + rc = -1; + } + } + + os_log(LOG_INFO, "avdecc(%p) stream_id(%016"PRIx64") sent connect notification to SRP\n", avdecc, ntohll(stream_output_dynamic->stream_id)); + + return rc; +} + +/** Perform AVTP talker connection + * \return 0 on success, otherwise -1 + * \param entity pointer to the entity context + * \param port_id port / AVB interface index + * \param stream_output pointer to the stream output descriptor + * \param stream_output_dynamic pointer to the stream output dynamic descriptor + * \param talker_unique_id valid talker unique id (in host order) + * \param flags ACMP flags (in host order) + */ +int acmp_talker_avtp_stack_connect(struct entity *entity, unsigned int port_id, u16 talker_unique_id, struct stream_descriptor *stream_output, struct stream_output_dynamic_desc *stream_output_dynamic, u16 flags) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + struct ipc_desc *desc; + int rc = 0; + + /* Send talker connect to AVTP */ + desc = ipc_alloc(&avdecc->ipc_tx_media_stack, sizeof(struct genavb_msg_media_stack_connect)); + if (desc) { + desc->dst = IPC_DST_ALL; + desc->type = GENAVB_MSG_MEDIA_STACK_CONNECT; + desc->len = sizeof(struct genavb_msg_media_stack_connect); + + os_memcpy(desc->u.media_stack_connect.stream_params.dst_mac, stream_output_dynamic->stream_dest_mac, 6); + copy_64(&desc->u.media_stack_connect.stream_params.stream_id, &stream_output_dynamic->stream_id); + desc->u.media_stack_connect.stream_params.port = avdecc_port_to_logical(avdecc, port_id); + copy_64(&desc->u.media_stack_connect.stream_params.format, &stream_output->current_format); + desc->u.media_stack_connect.stream_params.subtype = desc->u.media_stack_connect.stream_params.format.u.s.subtype; + desc->u.media_stack_connect.stream_params.stream_class = (stream_output_dynamic->stream_class == SR_CLASS_B) ? sr_class_low() : sr_class_high(); + desc->u.media_stack_connect.stream_params.clock_domain = GENAVB_MEDIA_CLOCK_DOMAIN_PTP; + desc->u.media_stack_connect.stream_params.direction = AVTP_DIRECTION_TALKER; + desc->u.media_stack_connect.entity_index = entity->index; + desc->u.media_stack_connect.configuration_index = ntohs(entity->desc->current_configuration); + desc->u.media_stack_connect.stream_index = talker_unique_id; + desc->u.media_stack_connect.stream_params.talker.latency = max(CFG_AVTP_DEFAULT_LATENCY, sr_class_interval_p(desc->u.media_stack_connect.stream_params.stream_class) / sr_class_interval_q(desc->u.media_stack_connect.stream_params.stream_class)); + + if (stream_output_dynamic->presentation_time_offset == STREAM_PRESENTATION_TIME_OFFSET_INVALID) + desc->u.media_stack_connect.stream_params.talker.max_transit_time = sr_class_max_transit_time(desc->u.media_stack_connect.stream_params.stream_class); + else + desc->u.media_stack_connect.stream_params.talker.max_transit_time = stream_output_dynamic->presentation_time_offset; + + desc->u.media_stack_connect.stream_params.flags = IPC_AVTP_FLAGS_MAX_TRANSIT_TIME_VALID; + + desc->u.media_stack_connect.flags = flags; + + if (stream_output_dynamic->stream_vlan_id == htons(0)) + desc->u.media_stack_connect.stream_params.talker.vlan_id = htons(VLAN_VID_DEFAULT); + else + desc->u.media_stack_connect.stream_params.talker.vlan_id = stream_output_dynamic->stream_vlan_id; + + if (ipc_tx(&avdecc->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx_avtp() failed\n", avdecc); + ipc_free(&avdecc->ipc_tx_media_stack, desc); + rc = -1; + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + rc = -1; + } + + os_log(LOG_INFO, "avdecc(%p) stream_id(%016"PRIx64") sent connect notification to AVTP\n", avdecc, ntohll(stream_output_dynamic->stream_id)); + + return rc; +} + +/** Perform SRP talker disconnect + * \return 0 on success, otherwise -1 + * \param avdecc pointer to the avdecc context + * \param port_id port / AVB interface index + * \param stream_output_dynamic pointer to the stream output dynamic descriptor + */ +int acmp_talker_withdraw(struct avdecc_ctx *avdecc, unsigned int port_id, struct stream_output_dynamic_desc *stream_output_dynamic) +{ + struct ipc_desc *desc; + int rc = 0; + + if (avdecc->srp_enabled) { + /* Send talker disconnect to MSRP */ + desc = ipc_alloc(&avdecc->port[port_id].ipc_tx_srp, sizeof(struct ipc_msrp_talker_deregister)); + if (desc) { + desc->type = GENAVB_MSG_TALKER_DEREGISTER; + desc->len = sizeof(struct ipc_msrp_talker_deregister); + + desc->u.msrp_talker_deregister.port = avdecc_port_to_logical(avdecc, port_id); + copy_64(&desc->u.msrp_talker_deregister.stream_id, &stream_output_dynamic->stream_id); + + if (ipc_tx(&avdecc->port[port_id].ipc_tx_srp, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + ipc_free(&avdecc->port[port_id].ipc_tx_srp, desc); + rc = -1; + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + rc = -1; + } + } + + os_log(LOG_INFO, "avdecc(%p) stream_id(%016"PRIx64") sent disconnect notification to SRP\n", avdecc, ntohll(stream_output_dynamic->stream_id)); + + return rc; +} + +/** Perform AVTP talker disconnect + * \return 0 on success, otherwise -1 + * \param avdecc pointer to the avdecc context + * \param port_id port / AVB interface index + * \param talker_unique_id talker stream unique id (in host order) + * \param stream_output_dynamic pointer to the stream output dynamic descriptor + */ +int acmp_talker_avtp_stack_disconnect(struct avdecc_ctx *avdecc, unsigned int port_id, u16 talker_unique_id, struct stream_output_dynamic_desc *stream_output_dynamic) +{ + struct ipc_desc *desc; + int rc = 0; + + desc = ipc_alloc(&avdecc->ipc_tx_media_stack, sizeof(struct genavb_msg_media_stack_disconnect)); + if (desc) { + desc->dst = IPC_DST_ALL; + desc->type = GENAVB_MSG_MEDIA_STACK_DISCONNECT; + desc->len = sizeof(struct genavb_msg_media_stack_disconnect); + + desc->u.media_stack_disconnect.stream_index = talker_unique_id; + desc->u.media_stack_disconnect.port = avdecc_port_to_logical(avdecc, port_id); + desc->u.media_stack_disconnect.stream_class = stream_output_dynamic->stream_class; + desc->u.media_stack_disconnect.direction = AVTP_DIRECTION_TALKER; + + copy_64(&desc->u.media_stack_disconnect.stream_id, &stream_output_dynamic->stream_id); + + if (ipc_tx(&avdecc->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + ipc_free(&avdecc->ipc_tx_media_stack, desc); + rc = -1; + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + rc = -1; + } + + os_log(LOG_INFO, "avdecc(%p) stream_id(%016"PRIx64") sent disconnect notification to AVTP\n", avdecc, ntohll(stream_output_dynamic->stream_id)); + + return rc; +} + +/** Helper function to check if a stream input or output is running + * \return bool, true if stream is running, false otherwise + * \param entity + * \param stream_desc_type, AEM_DESC_TYPE_STREAM_INPUT or AEM_DESC_TYPE_STREAM_OUTPUT + * \param stream_desc_index, index of the descriptor + */ +bool acmp_is_stream_running(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + + if (!avdecc->milan_mode) + return acmp_ieee_is_stream_running(entity, stream_desc_type, stream_desc_index); + else + return acmp_milan_is_stream_running(entity, stream_desc_type, stream_desc_index); +} + +/** Update streaming_wait flag and send a GENAVB_MSG_MEDIA_STACK_BIND to update the status of the stream + * \return positive value (0 if nothing changed, 1 if stream descriptor was updated) if successful, negative otherwise + * \param entity + * \param stream_desc_type, AEM_DESC_TYPE_STREAM_INPUT or AEM_DESC_TYPE_STREAM_OUTPUT + * \param stream_desc_index, index of the descriptor + */ +int acmp_start_streaming(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + int rc = 0; + + if (avdecc->milan_mode) + rc = acmp_milan_start_streaming(entity, stream_desc_type, stream_desc_index); + else + os_log(LOG_DEBUG, "entity(%p) tried to start stream(%u, %u) on ieee side.\n", entity, stream_desc_type, stream_desc_index); + + return rc; +} + +/** Update streaming_wait flag and send a GENAVB_MSG_MEDIA_STACK_BIND to update the status of the stream + * \return positive value (0 if nothing changed, 1 if stream descriptor was updated) if successful, negative otherwise + * \param entity + * \param stream_desc_type, AEM_DESC_TYPE_STREAM_INPUT or AEM_DESC_TYPE_STREAM_OUTPUT + * \param stream_desc_index, index of the descriptor + */ +int acmp_stop_streaming(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + int rc = 0; + + if (avdecc->milan_mode) + rc = acmp_milan_stop_streaming(entity, stream_desc_type, stream_desc_index); + else + os_log(LOG_DEBUG, "entity(%p) tried to stop stream(%u, %u) on ieee side.\n", entity, stream_desc_type, stream_desc_index); + + return rc; +} + +/** Allocate a network tx descriptor for ACMP and init all fields with the PDU + * \return the allocated and initialized network descriptor + * \param pdu pointer to the ACMP PDU + * \param port pointer to the avdecc port on which the packet will be sent + */ +static struct net_tx_desc *acmp_net_tx_clone(struct acmp_pdu *acmp, struct avdecc_port *port) +{ + struct net_tx_desc *desc; + void *buf; + struct acmp_pdu *pdu; + + desc = net_tx_alloc(&port->net_tx, ACMP_NET_DATA_SIZE); + if (!desc) { + os_log(LOG_ERR, "acmp(%p) Cannot alloc tx descriptor\n", acmp); + return desc; + } + + buf = NET_DATA_START(desc); + pdu = (struct acmp_pdu *)((char *)buf + OFFSET_TO_ACMP); + *pdu = *acmp; + + return desc; +} + +/** ACMP timeout handling callback. + * Called by the generic inflight handling layer of AVDECC upon + * expiration of the timer associated to an ACMP command. + * Corresponds to the timeout states of ACMP listener state machines (8.2.2.5.3). + * \return 0 if timer needs to be restarted, 1 if timer needs to be stopped + * \param entry pointer to the inflight context + */ +int acmp_inflight_timeout(struct inflight_ctx *entry) +{ + struct acmp_ctx *acmp = container_of(entry->list_head, struct acmp_ctx, inflight); + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct avdecc_port *port; + struct net_tx_desc *desc; + int rc; + u8 msg_type = 0; + + os_log(LOG_DEBUG, "acmp(%p) inflight_ctx(%p) sequence id : %x\n", acmp, entry, entry->data.pdu.acmp.sequence_id); + + if (entry->data.retried) { + rc = AVDECC_INFLIGHT_TIMER_STOP; + switch (entry->data.msg_type) { + case ACMP_CONNECT_TX_COMMAND: + case ACMP_DISCONNECT_TX_COMMAND: + if (!avdecc->milan_mode) { + /* We're a Listener, so we send a timeout response back to the controller */ + if ((entry->data.pdu.acmp.flags & htons(ACMP_FLAG_FAST_CONNECT)) == 0) { /* Do not send timeout messages in fast-connect */ + port = &avdecc->port[entry->data.port_id]; + + desc = acmp_net_tx_clone(&entry->data.pdu.acmp, port); + if (!desc) + goto exit; + + if (entry->data.msg_type == ACMP_CONNECT_TX_COMMAND) + msg_type = ACMP_CONNECT_RX_RESPONSE; + else + msg_type = ACMP_DISCONNECT_RX_RESPONSE; + + acmp_send_rsp(acmp, port, desc, msg_type, ACMP_STAT_LISTENER_TALKER_TIMEOUT); + } + } else { + acmp_milan_listener_sink_event(entity, ntohs(entry->data.pdu.acmp.listener_unique_id), ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_RESP); + } + break; + default: /* We're a Controller, so we send an IPC back to the controller app */ + /* FIXME Technically, this is not a Listener-Talker timeout */ + acmp_ipc_tx_rsp(acmp, &entry->data.pdu.acmp, entry->data.msg_type + 1, ACMP_STAT_LISTENER_TALKER_TIMEOUT, + (void *)entry->data.priv[0], (unsigned int)entry->data.priv[1]); + break; + } + } else { + if (avdecc->milan_mode) + acmp_milan_listener_sink_event(entity, ntohs(entry->data.pdu.acmp.listener_unique_id), ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_RESP); + + rc = AVDECC_INFLIGHT_TIMER_RESTART; + + port = &avdecc->port[entry->data.port_id]; + + desc = acmp_net_tx_clone(&entry->data.pdu.acmp, port); + if (!desc) + goto exit; + + entry->data.retried = 1; + + acmp_send_cmd(acmp, port, NULL, desc, entry->data.msg_type, 1, 0, NULL, 0); + } +exit: + return rc; +} + +/** Main ACMP controller receive function. + * Implementation of the ACMP controller state machine (8.2.2.4.3). + * \return 0 on success, negative otherwise + * \param acmp pointer to the ACMP context + * \param pdu pointer to the ACMP PDU + * \param msg_type ACMP message type (8.2.1.5) + * \param status ACMP message status (8.2.1.6) + */ +int acmp_controller_rcv(struct acmp_ctx *acmp,struct acmp_pdu *pdu, u8 msg_type, u8 status) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct ipc_tx *ipc; + uintptr_t ipc_dst; + int rc; + + os_log(LOG_INFO, "acmp(%p) %s\n", acmp, acmp_msgtype2string(acmp, msg_type)); + + rc = avdecc_inflight_cancel(entity, &acmp->inflight, ntohs(pdu->sequence_id), NULL, (void **)&ipc, (void **)&ipc_dst); + + if ((rc < 0) || !ipc) { + os_log(LOG_ERR, "acmp(%p) Received an ACMP message msg_type(%d) status(%d) with no valid inflight entry\n", + acmp, msg_type, status); + goto exit; + } + + acmp_ipc_tx_rsp(acmp, pdu, msg_type, status, ipc, ipc_dst); + +exit: + return rc; +} + +/** Main ACMP network receive function. + * \return 0 on success, negative otherwise + * \param avdecc pointer to the AVDECC port + * \param pdu pointer to the ACMP PDU + * \param msg_type ACMP message type (8.2.1.5) + * \param status status from AVTP control header (8.2.1.6) + */ +int acmp_net_rx(struct avdecc_port *port, struct acmp_pdu *pdu, u8 msg_type, u8 status) +{ + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + struct entity *entity; + int rc = 0; + + switch (msg_type) { + case ACMP_CONNECT_TX_RESPONSE: + case ACMP_DISCONNECT_TX_RESPONSE: + case ACMP_CONNECT_RX_COMMAND: + case ACMP_DISCONNECT_RX_COMMAND: + case ACMP_GET_RX_STATE_COMMAND: + entity = avdecc_get_entity(avdecc, pdu->listener_entity_id); + if (entity && avdecc_entity_port_valid(entity, port->port_id)) { + if (!avdecc->milan_mode) + rc = acmp_ieee_listener_rcv(&entity->acmp, pdu, msg_type, status, port->port_id); + else + rc = acmp_milan_listener_rcv(&entity->acmp, pdu, msg_type, status, port->port_id); + } + + break; + + case ACMP_CONNECT_TX_COMMAND: + case ACMP_DISCONNECT_TX_COMMAND: + case ACMP_GET_TX_STATE_COMMAND: + case ACMP_GET_TX_CONNECTION_COMMAND: + entity = avdecc_get_entity(avdecc, pdu->talker_entity_id); + if (entity && avdecc_entity_port_valid(entity, port->port_id)) { + if (!avdecc->milan_mode) + rc = acmp_ieee_talker_rcv(&entity->acmp, pdu, msg_type, status, port->port_id); + else + rc = acmp_milan_talker_rcv(&entity->acmp, pdu, msg_type, status, port->port_id); + } + + break; + + case ACMP_CONNECT_RX_RESPONSE: + case ACMP_DISCONNECT_RX_RESPONSE: + case ACMP_GET_RX_STATE_RESPONSE: + case ACMP_GET_TX_STATE_RESPONSE: + case ACMP_GET_TX_CONNECTION_RESPONSE: + entity = avdecc_get_entity(avdecc, pdu->controller_entity_id); + if (entity && avdecc_entity_port_valid(entity, port->port_id)) + rc = acmp_controller_rcv(&entity->acmp, pdu, msg_type, status); + + break; + + default: + os_log(LOG_ERR, "port(%u) Unknown message type (%d) \n", port->port_id, msg_type); + rc = -1; + break; + } + + return rc; +} + +/** Main ACMP IPC receive function + * \return 0 on success or negative value otherwise. + * \param entity Controller entity the IPC was received for. + * \param rx_desc Pointer to the received ACMP command. + * \param len Length of the received IPC message payload. + * \param ipc IPC the message was received through. + * \param ipc_dst Slot, within the ipc channel, the message was received from. + */ +int acmp_ipc_rx(struct entity *entity, struct ipc_acmp_command *acmp_command, u32 len, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + struct acmp_ctx *acmp = &entity->acmp; + struct net_tx_desc *desc_cmd; + struct acmp_pdu *acmp_cmd; + int rc = 0; + struct avdecc_ctx *avdecc = entity->avdecc; + struct avdecc_port *port; + struct entity_discovery *entity_disc = NULL; + u64 targetted_entity_id; + unsigned int num_interfaces; + + if (len < sizeof(struct ipc_acmp_command)) { + os_log(LOG_ERR, "acmp(%p) Invalid IPC ACMP message size (%d instead of expected %zd)\n", + acmp, len, sizeof(struct ipc_acmp_command)); + rc = -1; + goto exit; + } + + os_log(LOG_DEBUG, "acmp(%p) ACMP message_type (%d)\n", acmp, acmp_command->message_type); + + switch (acmp_command->message_type) { + case ACMP_CONNECT_RX_COMMAND: + case ACMP_DISCONNECT_RX_COMMAND: + case ACMP_GET_RX_STATE_COMMAND: + copy_64(&targetted_entity_id, &acmp_command->listener_entity_id); + break; + case ACMP_GET_TX_STATE_COMMAND: + case ACMP_GET_TX_CONNECTION_COMMAND: + copy_64(&targetted_entity_id, &acmp_command->talker_entity_id); + break; + default: + os_log(LOG_ERR, "acmp(%p) Received invalid command type %d\n", acmp, acmp_command->message_type); + rc = -1; + goto exit; + } + + /* Get number of supported interfaces for the controller entity. */ + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + entity_disc = adp_find_entity_discovery_any(avdecc, targetted_entity_id, num_interfaces); + if (!entity_disc) { + os_log(LOG_ERR, "acmp(%p) cannot send command %s, targetted entity(%"PRIx64") not visible on the network.\n", + acmp, acmp_msgtype2string(acmp, acmp_command->message_type), htonll(targetted_entity_id)); + rc = -1; + goto exit; + } + + /* Send command on the port on which we discovered the entity. */ + port = discovery_to_avdecc_port(entity_disc->disc); + + desc_cmd = acmp_net_tx_alloc(acmp, port); + if (!desc_cmd) { + rc = -1; + goto exit; + } + + acmp_cmd = (struct acmp_pdu *)((char *)NET_DATA_START(desc_cmd) + OFFSET_TO_ACMP); + + copy_64(&acmp_cmd->controller_entity_id, &entity->desc->entity_id); + copy_64(&acmp_cmd->talker_entity_id, &acmp_command->talker_entity_id); + copy_64(&acmp_cmd->listener_entity_id, &acmp_command->listener_entity_id); + acmp_cmd->talker_unique_id = acmp_command->talker_unique_id; + acmp_cmd->listener_unique_id = acmp_command->listener_unique_id; + acmp_cmd->connection_count = acmp_command->connection_count; + acmp_cmd->flags = acmp_command->flags; + + rc = acmp_send_cmd(acmp, port, acmp_cmd, desc_cmd, acmp_command->message_type, 0, acmp->sequence_id, ipc, ipc_dst); + if (rc < 0) + os_log(LOG_ERR, "acmp(%p) Cannot send ACMP command %s\n", + acmp, acmp_msgtype2string(acmp, acmp_command->message_type)); + +exit: + return rc; +} + +__init unsigned int acmp_data_size(struct avdecc_entity_config *cfg) +{ + if (!cfg->milan_mode) + return acmp_ieee_data_size(cfg); + else + return 0; +} + +__init int acmp_init(struct acmp_ctx *acmp, void *data, struct avdecc_entity_config *cfg) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + int rc = 0; + + acmp->max_listener_streams = aem_get_listener_streams(entity->aem_descs); + acmp->max_talker_streams = aem_get_talker_streams(entity->aem_descs); + + if (acmp->max_listener_streams > cfg->max_listener_streams) { + os_log(LOG_ERR, "AVDECC listener streams (%u) above configured max (%u)\n", + acmp->max_listener_streams, cfg->max_listener_streams); + rc = -1; + goto exit; + } + + if (acmp->max_talker_streams > cfg->max_talker_streams) { + os_log(LOG_ERR, "AVDECC talker streams (%u) above configured max (%u)\n", + acmp->max_talker_streams, cfg->max_talker_streams); + rc = -1; + goto exit; + } + + list_head_init(&acmp->inflight); + + if (!avdecc->milan_mode) + rc = acmp_ieee_init(acmp, data, cfg); + else + rc = acmp_milan_init(acmp); + + if (rc < 0) + goto exit; + + os_log(LOG_INIT, "acmp(%p) done\n", acmp); + +exit: + return rc; +} + +__exit int acmp_exit(struct acmp_ctx *acmp) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + + if (!avdecc->milan_mode) + acmp_ieee_exit(acmp); + else + acmp_milan_exit(acmp); + + os_log(LOG_INIT, "done\n"); + + return 0; +} diff --git a/avdecc/acmp.h b/avdecc/acmp.h new file mode 100644 index 0000000..95570b7 --- /dev/null +++ b/avdecc/acmp.h @@ -0,0 +1,66 @@ +/* + * Copyright 2014-2015 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2020-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ACMP common definitions +*/ + +#ifndef _ACMP_H_ +#define _ACMP_H_ + +#include "common/types.h" +#include "common/acmp.h" +#include "common/timer.h" +#include "common/ipc.h" + +#include "genavb/aem.h" + +#include "acmp_ieee.h" +#include "acmp_milan.h" + +struct acmp_ctx { + u16 sequence_id; + struct list_head inflight; + unsigned int max_listener_streams; /* total number of listener streams in the entity */ + unsigned int max_talker_streams; /* total number of talker streams in the entity */ + + union { + struct { + struct listener_stream_info *listener_info; /* AVDECC 1722.1 use: array of listeners stream information */ + struct talker_stream_info *talker_info; /* AVDECC 1722.1 use: array of talkers stream information */ + unsigned int max_listener_pairs; /* AVDECC 1722.1 use: Maximum number of connected listeners per talker */ + } ieee; + } u; +}; + +struct avdecc_port; +struct entity; + +int acmp_init(struct acmp_ctx *acmp, void *data, struct avdecc_entity_config *cfg); +int acmp_exit(struct acmp_ctx *acmp); +unsigned int acmp_data_size(struct avdecc_entity_config *cfg); +struct net_tx_desc *acmp_net_tx_init(struct acmp_ctx *acmp, struct avdecc_port *port, struct acmp_pdu *pdu, bool is_resp); +struct net_tx_desc *acmp_net_tx_alloc(struct acmp_ctx *acmp, struct avdecc_port *port); +int acmp_net_rx(struct avdecc_port *port, struct acmp_pdu *pdu, u8 msg_type, u8 status); +int acmp_ipc_rx(struct entity *entity, struct ipc_acmp_command *acmp_command, u32 len,struct ipc_tx *ipc, unsigned int ipc_dst); +void acmp_listener_copy_common_params(struct acmp_pdu *pdu, struct stream_input_dynamic_desc *listener_params); +void acmp_talker_copy_common_params(struct acmp_pdu *pdu, struct stream_output_dynamic_desc *talker_params); +u8 acmp_listener_stack_connect(struct entity *entity, u16 listener_unique_id, u16 flags); +u8 acmp_listener_stack_disconnect(struct entity *entity, u16 unique_id); +int acmp_talker_declare(struct avdecc_ctx *avdecc, unsigned int port_id, struct stream_descriptor *stream_output, struct stream_output_dynamic_desc *stream_output_dynamic, u16 flags); +int acmp_talker_withdraw(struct avdecc_ctx *avdecc, unsigned int port_id, struct stream_output_dynamic_desc *stream_output_dynamic); +int acmp_talker_avtp_stack_connect(struct entity *entity, unsigned int port_id, u16 talker_unique_id, struct stream_descriptor *stream_output, struct stream_output_dynamic_desc *stream_output_dynamic, u16 flags); +int acmp_talker_avtp_stack_disconnect(struct avdecc_ctx *avdecc, unsigned int port_id, u16 talker_unique_id, struct stream_output_dynamic_desc *stream_output_dynamic); +int acmp_send_cmd(struct acmp_ctx *acmp, struct avdecc_port *port, struct acmp_pdu *pdu, struct net_tx_desc *desc, u8 msg_type, u8 retried, u16 orig_seq_id, struct ipc_tx *ipc, unsigned int ipc_dst); +int acmp_send_rsp(struct acmp_ctx *acmp, struct avdecc_port *port, struct net_tx_desc *desc, u8 msg_type, u16 status); +bool acmp_is_stream_running(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index); +int acmp_start_streaming(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index); +int acmp_stop_streaming(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index); +int acmp_talker_unique_valid(struct acmp_ctx *acmp, u16 unique_id); +int acmp_listener_unique_valid(struct acmp_ctx *acmp, u16 unique_id); + +#endif /* _ACMP_H_ */ diff --git a/avdecc/acmp_ieee.c b/avdecc/acmp_ieee.c new file mode 100644 index 0000000..d1a7b44 --- /dev/null +++ b/avdecc/acmp_ieee.c @@ -0,0 +1,1035 @@ +/* +* Copyright 2014 Freescale Semiconductor, Inc. +* Copyright 2020-2024 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + @brief ACMP IEEE 1722.1 code + @details Handles ACMP IEEE 1722.1 stack +*/ + +#include "os/stdlib.h" + +#include "common/log.h" +#include "common/ether.h" +#include "common/hash.h" + +#include "acmp.h" +#include "avdecc.h" +#include "acmp_ieee.h" + + +static const u8 avtp_base_dst_mac[6] = MC_ADDR_MAAP_BASE; + +int acmp_ieee_get_command_timeout_ms(acmp_message_type_t msg_type) +{ + switch (msg_type) { + case ACMP_CONNECT_TX_COMMAND: + return 2000; + case ACMP_CONNECT_RX_COMMAND: + return 4500; + case ACMP_DISCONNECT_RX_COMMAND: + return 500; + case ACMP_DISCONNECT_TX_COMMAND: + case ACMP_GET_TX_STATE_COMMAND: + case ACMP_GET_RX_STATE_COMMAND: + case ACMP_GET_TX_CONNECTION_COMMAND: + return 200; + default: + return -1; + } +} + +const char *acmp_ieee_msgtype2string(acmp_message_type_t msg_type) +{ + switch (msg_type) { + case2str(ACMP_CONNECT_TX_COMMAND); + case2str(ACMP_CONNECT_TX_RESPONSE); + case2str(ACMP_DISCONNECT_TX_COMMAND); + case2str(ACMP_DISCONNECT_TX_RESPONSE); + case2str(ACMP_GET_TX_STATE_COMMAND); + case2str(ACMP_GET_TX_STATE_RESPONSE); + case2str(ACMP_CONNECT_RX_COMMAND); + case2str(ACMP_CONNECT_RX_RESPONSE); + case2str(ACMP_DISCONNECT_RX_COMMAND); + case2str(ACMP_DISCONNECT_RX_RESPONSE); + case2str(ACMP_GET_RX_STATE_COMMAND); + case2str(ACMP_GET_RX_STATE_RESPONSE); + case2str(ACMP_GET_TX_CONNECTION_COMMAND); + case2str(ACMP_GET_TX_CONNECTION_RESPONSE); + default: + return (char *) "Unknown ACMP 1722.1 message type"; + } +} + +/** Copy listener stream info params to PDU + * \return none + * \param pdu pointer to the ACMP PDU + * \param listener_info pointer to the listener_stream_info structure + */ +static void acmp_ieee_listener_copy_info(struct acmp_pdu *pdu, struct listener_stream_info *listener_info) +{ + acmp_listener_copy_common_params(pdu, listener_info); + + if (listener_info->u.ieee.connected) + pdu->connection_count = htons(1); + else + pdu->connection_count = htons(0); + +} + +/** Connects the AVDECC listener to a stream (8.2.2.5.2.6). + * Sends an indication to AVTP / media application and initiates the SRP + * listener registration. + * \return ACMP status + * \param entity pointer to the entity context + * \param listener_info pointer to the stream information context + * \param pdu pointer to the ACMP PDU + */ +static u8 acmp_ieee_listener_connect(struct entity *entity, struct listener_stream_info *listener_info, struct acmp_pdu *pdu) +{ + u8 rc = ACMP_STAT_SUCCESS; + + /* + * Already connected, success. + */ + if (listener_info->u.ieee.connected) + goto exit; + + /* Save Listener stream info parameters */ + copy_64(&listener_info->controller_entity_id, &pdu->controller_entity_id); + listener_info->flags = pdu->flags; + os_memcpy(listener_info->stream_dest_mac, pdu->stream_dest_mac, 6); + copy_64(&listener_info->stream_id, &pdu->stream_id); + listener_info->stream_vlan_id = pdu->stream_vlan_id; + copy_64(&listener_info->talker_entity_id, &pdu->talker_entity_id); + listener_info->talker_unique_id = pdu->talker_unique_id; + + rc = acmp_listener_stack_connect(entity, ntohs(pdu->listener_unique_id), ntohs(pdu->flags)); + if (rc != ACMP_STAT_SUCCESS) + goto exit; + + listener_info->u.ieee.connected = 1; + + os_log(LOG_INFO, "controller(%016"PRIx64") stream_id(%016"PRIx64") talker(%016"PRIx64", %u)\n", + ntohll(listener_info->controller_entity_id), ntohll(listener_info->stream_id), + ntohll(listener_info->talker_entity_id), ntohs(listener_info->talker_unique_id)); + + /* FIXME + if (acmp->fast_connect) { + save current connection to a file + } + FIXME */ +exit: + return rc; +} + +/** Disconnects the stream from the AVDECC listener (8.2.2.5.2.7). + * Send an indication to AVTP / media application and initiates the SRP + * listener de-registration. + * \return ACMP status + * \param entity pointer to the entity context + * \param listener_info pointer to the stream information context + * \param unique_id listener unique ID + */ +static u8 acmp_ieee_listener_disconnect(struct entity *entity, struct listener_stream_info *listener_info, u16 unique_id) +{ + u8 rc; + + rc = acmp_listener_stack_disconnect(entity, unique_id); + + // what happens if we fail the disconnect ? still connected ? + listener_info->u.ieee.connected = 0; + listener_info->flags = 0; + + return rc; +} + +/** Returns true if the listener is connected to another stream source as provided + * in the ACMP PDU or if it is not connected (8.2.2.5.2.2). + * \return 0 or 1 + * \param listener_info pointer to the stream information context + * \param pdu pointer to the ACMP PDU + */ +static int acmp_ieee_listener_connected(struct listener_stream_info *listener_info, struct acmp_pdu *pdu) +{ + if (listener_info->u.ieee.connected) { + if (cmp_64(&listener_info->talker_entity_id, &pdu->talker_entity_id) + && listener_info->talker_unique_id == pdu->talker_unique_id) + return 0; + else + return 1; + } + else + return 0; +} + +/** Returns true if the listener is connected to the stream source as provided + * in the ACMP PDU (8.2.2.5.2.3). + * \return 0 or 1 + * \param listener_info pointer to the stream information context + * \param pdu pointer to the ACMP PDU + */ +static int acmp_ieee_listener_connected_to(struct listener_stream_info *listener_info, struct acmp_pdu *pdu) +{ + if ((listener_info->u.ieee.connected) && cmp_64(&listener_info->talker_entity_id, &pdu->talker_entity_id) + && listener_info->talker_unique_id == pdu->talker_unique_id) + return 1; + else + return 0; +} + +/** Helper function to check if a stream input or output is running + * \return bool, true if stream is running, false otherwise + * \param entity + * \param stream_desc_type, AEM_DESC_TYPE_STREAM_INPUT or AEM_DESC_TYPE_STREAM_OUTPUT + * \param stream_desc_index, index of the descriptor + */ +bool acmp_ieee_is_stream_running(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index) +{ + bool ret = false; + void *desc; + + if (stream_desc_type != AEM_DESC_TYPE_STREAM_INPUT && stream_desc_type != AEM_DESC_TYPE_STREAM_OUTPUT) { + os_log(LOG_ERR, "entity(%p) descriptor type (%u) not supported\n", entity, stream_desc_type); + goto out; + } + + desc = aem_get_descriptor(entity->aem_dynamic_descs, stream_desc_type, stream_desc_index, NULL); + if (!desc) { + os_log(LOG_ERR, "entity(%p) stream descriptor(%u, %u) not found.\n", entity, stream_desc_type, stream_desc_index); + goto out; + } + + if (stream_desc_type == AEM_DESC_TYPE_STREAM_INPUT) { + ret = ((struct stream_input_dynamic_desc *)desc)->u.ieee.connected; + } else if (stream_desc_type == AEM_DESC_TYPE_STREAM_OUTPUT) { + ret = (((struct stream_output_dynamic_desc *)desc)->u.ieee.connection_count > 0); + } + +out: + return ret; +} + +/** For demo purposes. + * Disconnects the listener if its talker departed. + * \return none + * \param acmp ACMP context + * \param entity_id pointer to 64 bits talker entity ID + */ +void acmp_ieee_listener_talker_left(struct acmp_ctx *acmp, u64 entity_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct listener_stream_info *listener_info; + int i; + + if (entity->flags & AVDECC_FAST_CONNECT_MODE) { + for (i = 0; i < acmp->max_listener_streams; i++) { + listener_info = &acmp->u.ieee.listener_info[i]; + + if ((listener_info->u.ieee.connected && (listener_info->u.ieee.flags_priv & ACMP_LISTENER_FL_FAST_CONNECT)) + && cmp_64(&entity_id, &listener_info->talker_entity_id)) { + acmp_ieee_listener_disconnect(entity, listener_info, i); + + if (listener_info->u.ieee.flags_priv & ACMP_LISTENER_FL_FAST_CONNECT_BTB) + listener_info->u.ieee.flags_priv &= ~ACMP_LISTENER_FL_FAST_CONNECT_PENDING; + } + } + } +} + +/** Used for back-to-back demo configuration + * Configures a saved state into the listener_info context with the provided + * talker entity ID. + * \return none + * \param acmp ACMP context + * \param entity_id pointer to 64 bits talker entity ID + * \param port_id port on which the talker entity was discovered + */ +void acmp_ieee_listener_fast_connect_btb(struct acmp_ctx *acmp, u64 entity_id, unsigned int port_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct listener_stream_info *listener_info; + struct stream_descriptor *stream_input; + int i; + + if ((entity->flags & (AVDECC_FAST_CONNECT_MODE | AVDECC_FAST_CONNECT_BTB)) != (AVDECC_FAST_CONNECT_MODE | AVDECC_FAST_CONNECT_BTB)) + return; + + /* Search for streams enabled for fast connect back to back, not connected and missing talker information */ + for (i = 0; i < acmp->max_listener_streams; i++) { + stream_input = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + + if (port_id != ntohs(stream_input->avb_interface_index)) + continue; + + listener_info = &acmp->u.ieee.listener_info[i]; + + if (!(listener_info->u.ieee.flags_priv & ACMP_LISTENER_FL_FAST_CONNECT_BTB)) + continue; + + if (listener_info->u.ieee.connected) + continue; + + if (listener_info->u.ieee.flags_priv & ACMP_LISTENER_FL_FAST_CONNECT_PENDING) + continue; + + copy_64(&listener_info->talker_entity_id, &entity_id); + listener_info->u.ieee.flags_priv |= ACMP_LISTENER_FL_FAST_CONNECT_PENDING; + + os_log(LOG_INIT, "acmp(%p) has a saved state for listener(%016"PRIx64", %u) -> talker(%016"PRIx64", %u)\n", + acmp, ntohll(entity->desc->entity_id), i, + ntohll(listener_info->talker_entity_id), ntohs(listener_info->talker_unique_id)); + } +} + +/** Fast-connects to the provided entity ID (8.2.2.1.1). + * Parses the list of listener_info contexts and check if a connection was previously + * saved and matches the provided talker entity ID. On success, try to connect + * directly to the talker. + * \return none + * \param acmp ACMP context + * \param entity_id pointer to 64 bits talker entity ID + * \param port_id port on which to try the connection. + */ +void acmp_ieee_listener_fast_connect(struct acmp_ctx *acmp, u64 entity_id, unsigned int port_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct listener_stream_info *listener_info; + struct net_tx_desc *desc_rsp; + struct acmp_pdu *acmp_cmd; + struct stream_descriptor *stream_input; + void *buf_rsp; + struct avdecc_port *port; + int i; + + if (!(entity->flags & AVDECC_FAST_CONNECT_MODE)) + return; + + /* Search for streams enabled for fast connect, not connected and with full talker information */ + for (i = 0; i < acmp->max_listener_streams; i++) { + listener_info = &acmp->u.ieee.listener_info[i]; + + if (!(listener_info->u.ieee.flags_priv & ACMP_LISTENER_FL_FAST_CONNECT)) + continue; + + if (listener_info->u.ieee.connected) + continue; + + if (!(listener_info->u.ieee.flags_priv & ACMP_LISTENER_FL_FAST_CONNECT_PENDING)) + continue; + + stream_input = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + + if (entity_id == listener_info->talker_entity_id && port_id == ntohs(stream_input->avb_interface_index)) { + port = &avdecc->port[port_id]; + + desc_rsp = net_tx_alloc(&port->net_tx, ACMP_NET_DATA_SIZE); + if (!desc_rsp) { + os_log(LOG_ERR, "acmp(%p) Cannot alloc tx descriptor\n", acmp); + return; + } + + buf_rsp = NET_DATA_START(desc_rsp); + acmp_cmd = (struct acmp_pdu *)((char *)buf_rsp + OFFSET_TO_ACMP); + + os_memset(acmp_cmd, 0 , sizeof(struct acmp_pdu)); + copy_64(&acmp_cmd->listener_entity_id, &entity->desc->entity_id); + acmp_cmd->listener_unique_id = htons(i); + copy_64(&acmp_cmd->controller_entity_id, &listener_info->controller_entity_id); + + acmp_ieee_listener_copy_info(acmp_cmd, listener_info); + acmp_cmd->flags |= htons(ACMP_FLAG_FAST_CONNECT/* | ACMP_FLAG_CLASS_B */); + + if (acmp_send_cmd(acmp, port, acmp_cmd, desc_rsp, ACMP_CONNECT_TX_COMMAND, 0, acmp->sequence_id, NULL, 0) < 0) + os_log(LOG_ERR, "acmp(%p) Cannot send fast-connect command\n", acmp); + } + } +} + +__init static void acmp_ieee_listener_fast_connect_init(struct acmp_ctx *acmp, struct avdecc_entity_config *cfg) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct listener_stream_info *listener_info; + int i; + + if (cfg->flags & AVDECC_FAST_CONNECT_MODE) { + os_log(LOG_INIT, "acmp(%p) fast-connect enabled\n", acmp); + + /* Provide default fast connect settings for first stream only */ + if (!cfg->talker_unique_id_n) { + cfg->talker_unique_id_n = 1; + cfg->talker_unique_id[0] = 0; + } + + if (!cfg->listener_unique_id_n) { + cfg->listener_unique_id_n = 1; + cfg->listener_unique_id[0] = 0; + } + + /* Init with user configuration */ + for (i = 0; (i < cfg->listener_unique_id_n) && (i < cfg->talker_unique_id_n); i++) { + if (cfg->listener_unique_id[i] < acmp->max_listener_streams) { + listener_info = &acmp->u.ieee.listener_info[cfg->listener_unique_id[i]]; + + listener_info->talker_unique_id = htons(cfg->talker_unique_id[i]); + listener_info->u.ieee.flags_priv |= ACMP_LISTENER_FL_FAST_CONNECT; + + if (i < cfg->talker_entity_id_n) { + listener_info->talker_entity_id = htonll(cfg->talker_entity_id[i]); + listener_info->u.ieee.flags_priv |= ACMP_LISTENER_FL_FAST_CONNECT_PENDING; + } else if (cfg->flags & AVDECC_FAST_CONNECT_BTB) { + listener_info->u.ieee.flags_priv |= ACMP_LISTENER_FL_FAST_CONNECT_BTB; + } + } + } + + for (i = 0; i < acmp->max_listener_streams; i++) { + listener_info = &acmp->u.ieee.listener_info[i]; + + if (listener_info->u.ieee.flags_priv & ACMP_LISTENER_FL_FAST_CONNECT_PENDING) + os_log(LOG_INIT, "acmp(%p) has a saved state for listener(%016"PRIx64", %u) -> talker(%016"PRIx64", %u)\n", + acmp, ntohll(entity->desc->entity_id), i, + ntohll(listener_info->talker_entity_id), ntohs(listener_info->talker_unique_id)); + } + } +} + +/** Fast disconnect listener streams (8.2.2.1.2). + * + * Parses the list of listener_info contexts and checks if they are currently connected, + * in which case a disconnect command is sent to the talker entity. + * The function doesn't follow the specification exactly, we should wait for a talker + * response and retransmit the command in case of timeout. + * + * \return none + * \param acmp ACMP context + */ +static void acmp_ieee_listener_fast_disconnect(struct acmp_ctx *acmp) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + void *buf_rsp; + struct net_tx_desc *desc_rsp; + struct acmp_pdu *acmp_rsp; + struct listener_stream_info *listener_info; + struct avdecc_port *port; + struct stream_descriptor *stream_input; + int i; + + for (i = 0; i < acmp->max_listener_streams; i++) { + listener_info = &acmp->u.ieee.listener_info[i]; + + if (!listener_info->u.ieee.connected) + continue; + + stream_input = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + port = &avdecc->port[ntohs(stream_input->avb_interface_index)]; + + /* AVTP disconnect and SRP deregister. */ + acmp_ieee_listener_disconnect(entity, listener_info, i); + + desc_rsp = net_tx_alloc(&port->net_tx, ACMP_NET_DATA_SIZE); + if (!desc_rsp) { + os_log(LOG_ERR, "acmp(%p) Cannot alloc tx descriptor\n", acmp); + + break; + } + + buf_rsp = NET_DATA_START(desc_rsp); + acmp_rsp = (struct acmp_pdu *)((char *)buf_rsp + OFFSET_TO_ACMP); + + os_memset(acmp_rsp, 0 , sizeof(struct acmp_pdu)); + copy_64(&acmp_rsp->controller_entity_id, &listener_info->controller_entity_id); + copy_64(&acmp_rsp->talker_entity_id, &listener_info->talker_entity_id); + acmp_rsp->talker_unique_id = listener_info->talker_unique_id; + copy_64(&acmp_rsp->listener_entity_id, &entity->desc->entity_id); + acmp_rsp->listener_unique_id = htons(i); + + acmp_send_cmd(acmp, port, acmp_rsp, desc_rsp, ACMP_DISCONNECT_TX_COMMAND, 0, 0, NULL, 0); + } +} + +/** Copy talker stream info params to PDU + * \return none + * \param pdu pointer to the ACMP PDU + * \param stream_info pointer to the talker_stream_info structure + */ +static void acmp_ieee_talker_copy_stream_info(struct acmp_pdu *pdu, struct talker_stream_info *stream_info) +{ + /* Do not provide any stream info if it is not created */ + if (stream_info->u.ieee.connection_count) { + acmp_talker_copy_common_params(pdu, stream_info); + pdu->connection_count = htons(stream_info->u.ieee.connection_count); + } +} + +/** Checks if the listener information from the ACMP PDU is connected to the provided + * talker stream (stream_info). + * \return listener_pair pointer if connected, NULL otherwise + * \param acmp ACMP context + * \param stream_info pointer to the talker stream context + * \param pdu pointer to the ACMP PDU + */ +static struct listener_pair *acmp_ieee_talker_connected_to(struct acmp_ctx *acmp, struct talker_stream_info *stream_info, struct acmp_pdu *pdu) +{ + int i; + + for (i = 0; i < acmp->u.ieee.max_listener_pairs; i++) { + if (stream_info->u.ieee.listeners[i].connected + && cmp_64(&stream_info->u.ieee.listeners[i].listener_entity_id, &pdu->listener_entity_id) + && stream_info->u.ieee.listeners[i].listener_unique_id == pdu->listener_unique_id) + return &stream_info->u.ieee.listeners[i]; + } + return NULL; +} + +/** Get a free listener_pair struct from talker stream context + * talker stream (stream_info). + * \return listener_pair pointer if available, NULL otherwise + * \param acmp ACMP context + * \param stream_info pointer to the talker stream context + */ +static struct listener_pair *acmp_ieee_talker_get_free_listener(struct acmp_ctx *acmp, struct talker_stream_info *stream_info) +{ + int i; + + if (stream_info->u.ieee.connection_count >= acmp->u.ieee.max_listener_pairs) + return NULL; + + for (i = 0; i < acmp->u.ieee.max_listener_pairs; i++) + if (!stream_info->u.ieee.listeners[i].connected) + return &stream_info->u.ieee.listeners[i]; + + return NULL; +} + +/** Connects a stream to an AVDECC listener (8.2.2.6.2.2). + * If this is the first listener, SRP talker registration is requested. + * \return ACMP status + * \param entity pointer to the entity context + * \param stream_info pointer to the stream information context + * \param pdu pointer to the ACMP PDU + */ +static u8 acmp_ieee_talker_connect(struct entity *entity, struct talker_stream_info *stream_info, struct acmp_pdu *pdu) +{ + struct stream_output_dynamic_desc *stream_output_dynamic; + u16 talker_unique_id = ntohs(pdu->talker_unique_id); + struct avdecc_ctx *avdecc = entity->avdecc; + struct avb_interface_descriptor *avb_itf; + struct stream_descriptor *stream_output; + struct listener_pair *listener; + u8 rc = ACMP_STAT_SUCCESS; + sr_class_t req_class; + unsigned int port_id; + + listener = acmp_ieee_talker_get_free_listener(&entity->acmp, stream_info); + if (!listener) { + os_log(LOG_ERR, "avdecc(%p) reached maximum number of listeners\n", avdecc); + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + if (!stream_output) { + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + if (!stream_output_dynamic) { + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + port_id = ntohs(stream_output->avb_interface_index); + + avb_itf = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + if (!avb_itf) { + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + if (pdu->flags & htons(ACMP_FLAG_CLASS_B)) + req_class = SR_CLASS_B; + else + req_class = SR_CLASS_A; + + /* Stream class check */ + if (req_class == SR_CLASS_B) { + if(!(stream_output->stream_flags & htons(AEM_STREAM_FLAG_CLASS_B))) { + os_log(LOG_ERR, "avdecc(%p) incompatible request from listener(%016"PRIx64")\n", avdecc, ntohll(pdu->listener_entity_id)); + rc = ACMP_STAT_INCOMPATIBLE_REQUEST; + goto exit; + } + } else { + if(!(stream_output->stream_flags & htons(AEM_STREAM_FLAG_CLASS_A))) { + os_log(LOG_ERR, "avdecc(%p) incompatible request from listener(%016"PRIx64")\n", avdecc, ntohll(pdu->listener_entity_id)); + rc = ACMP_STAT_INCOMPATIBLE_REQUEST; + goto exit; + } + } + + /* Only perform stack stream connection for the first connection */ + if (!stream_info->u.ieee.connection_count) { + + /* Set talker stream info parameters */ + stream_info->stream_class = req_class; + /* FIXME, will be allocated by MAAP one day. + * For now, use a hash of entity id and stream index + */ + os_memcpy(stream_info->stream_dest_mac, avtp_base_dst_mac, 6); + stream_info->stream_dest_mac[5] = rotating_hash_u8((u8 *)&entity->desc->entity_id, 8, 0); + stream_info->stream_dest_mac[5] = rotating_hash_u8((u8 *)&stream_output->descriptor_index, 2, stream_info->stream_dest_mac[5]); + + stream_info->stream_vlan_id = htons(0); // FIXME, this value indicates to use the default VLAN ID from the SRP domain being used + os_memset(&stream_info->stream_id, 0, sizeof(stream_info->stream_id)); + os_memcpy(&stream_info->stream_id, avb_itf->mac_address, 6); + *(((u16 *)&stream_info->stream_id) + 3) = stream_output->descriptor_index; + + if (acmp_talker_declare(avdecc, port_id, stream_output, stream_output_dynamic, ntohs(pdu->flags)) < 0) { + os_log(LOG_ERR, "acmp(%p) cannot connect SRP\n", &entity->acmp); + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + if (acmp_talker_avtp_stack_connect(entity, port_id, talker_unique_id, stream_output, stream_output_dynamic, ntohs(pdu->flags)) < 0) { + os_log(LOG_ERR, "acmp(%p) cannot connect AVTP\n", &entity->acmp); + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + } else { + /* Check new listener compatibility */ + if (req_class != stream_info->stream_class) { + rc = ACMP_STAT_INCOMPATIBLE_REQUEST; + os_log(LOG_ERR, "incompatible request on stream_id(%016"PRIx64")\n", ntohll(stream_info->stream_id)); + goto exit; + } + } + + /* Set listener info */ + copy_64(&listener->listener_entity_id, &pdu->listener_entity_id); + listener->listener_unique_id = pdu->listener_unique_id; + listener->connected = 1; + + stream_info->u.ieee.connection_count++; + + os_log(LOG_INFO, "success\n"); +exit: + return rc; +} + +/** Disconnects the talker stream from an AVDECC listener (8.2.2.6.2.4). + * If there are no more listeners the stream is disconnected. + * \return ACMP status + * \param entity pointer to the entity context + * \param stream_info pointer to the stream information context + * \param listener pointer to listener information context + * \param talker_unique_id talker stream unique id + */ +static u8 acmp_ieee_talker_disconnect(struct entity *entity, struct talker_stream_info *stream_info, struct listener_pair *listener, u16 talker_unique_id) +{ + struct stream_output_dynamic_desc *stream_output_dynamic; + struct avdecc_ctx *avdecc = entity->avdecc; + struct stream_descriptor *stream_output; + u8 rc = ACMP_STAT_SUCCESS; + unsigned int port_id; + + listener->connected = 0; + + if (--stream_info->u.ieee.connection_count == 0) { + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + if (!stream_output) { + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + if (!stream_output_dynamic) { + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + port_id = ntohs(stream_output->avb_interface_index); + + if (acmp_talker_withdraw(avdecc, port_id, stream_output_dynamic) < 0) { + os_log(LOG_ERR, "acmp(%p) cannot disconnect SRP\n", &entity->acmp); + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + if (acmp_talker_avtp_stack_disconnect(avdecc, port_id, talker_unique_id, stream_output_dynamic) < 0) { + os_log(LOG_ERR, "acmp(%p) cannot disconnect AVTP\n", &entity->acmp); + rc = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + } + +exit: + return rc; +} + +/** Disconnect talker streams locally. + * + * Parses the list of talker_info contexts and checks if they have connected listeners, + * in which case a local disconnect is executed for each of the listeners. + * The function is not standard but allows for cleanup of talker resources when the entity is + * being stopped. + * + * \return none + * \param acmp ACMP context + */ +static void acmp_ieee_talker_local_disconnect(struct acmp_ctx *acmp) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct talker_stream_info *stream_info; + struct listener_pair *listener; + int i, j; + + for (i = 0; i < acmp->max_talker_streams; i++) { + if (!acmp_talker_unique_valid(acmp, i)) + continue; + + stream_info = &acmp->u.ieee.talker_info[i]; + + for (j = 0; j < acmp->u.ieee.max_listener_pairs; j++) { + if (!stream_info->u.ieee.listeners[j].connected) + continue; + + listener = &stream_info->u.ieee.listeners[j]; + + acmp_ieee_talker_disconnect(entity, stream_info, listener, i); + } + } +} + +/** Main ACMP listener receive function. + * Implementation of the ACMP listener state machine (8.2.2.5.3). + * \return 0 on success, negative otherwise + * \param acmp pointer to the ACMP context + * \param pdu pointer to the ACMP PDU + * \param msg_type ACMP message type (8.2.1.5) + * \param status status from AVTP control header (8.2.1.6) + * \param port_id avdecc port / interface index on which we received the PDU + */ +int acmp_ieee_listener_rcv(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 msg_type, u8 status, unsigned int port_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct net_tx_desc *desc_rsp; + struct acmp_pdu *acmp_rsp; + u16 status_rsp = 0; + int rc = 0; + u16 unique_id = ntohs(pdu->listener_unique_id); + u16 orig_sequence_id; + struct listener_stream_info *listener_info; + struct avdecc_port *port_cmd, *port_rsp = &avdecc->port[port_id]; + + if (ACMP_IS_COMMAND(msg_type)) + os_log(LOG_INFO, "acmp(%p) %s: controller(%016"PRIx64") listener(%016"PRIx64", %u) talker(%016"PRIx64", %u)\n", + acmp, acmp_ieee_msgtype2string(msg_type), ntohll(pdu->controller_entity_id), + ntohll(entity->desc->entity_id), unique_id, + ntohll(pdu->talker_entity_id), ntohs(pdu->talker_unique_id)); + else + os_log(LOG_INFO, "acmp(%p) %s: controller(%016"PRIx64") listener(%016"PRIx64", %u) talker(%016"PRIx64", %u) status(%d)\n", + acmp, acmp_ieee_msgtype2string(msg_type), ntohll(pdu->controller_entity_id), + ntohll(entity->desc->entity_id), unique_id, + ntohll(pdu->talker_entity_id), ntohs(pdu->talker_unique_id), status); + + /* Allocate/init net descriptor for either command or response */ + desc_rsp = acmp_net_tx_init(acmp, port_rsp, pdu, false); + if (!desc_rsp) { + rc = -1; + goto exit; + } + + acmp_rsp = (struct acmp_pdu *)((char *)NET_DATA_START(desc_rsp) + OFFSET_TO_ACMP); + + if (!acmp_listener_unique_valid(acmp, unique_id)) { + if (ACMP_IS_LISTENER_COMMAND(msg_type)) { + acmp_rsp->sequence_id = pdu->sequence_id; + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_LISTENER_UNKNOWN_ID); + } else + net_tx_free(desc_rsp); + + goto exit; + } + + listener_info = &acmp->u.ieee.listener_info[unique_id]; + + switch (msg_type) { + case ACMP_CONNECT_RX_COMMAND: + if (!acmp_ieee_listener_connected(listener_info, pdu)) { + struct stream_descriptor *stream_input = aem_get_descriptor(entity->aem_descs, + AEM_DESC_TYPE_STREAM_INPUT, unique_id, NULL); + + acmp_rsp->flags = pdu->flags; + + /* The CONNECT_TX_COMMAND should be sent on the port attached to the STREAM_INPUT to be connected */ + port_cmd = &avdecc->port[ntohs(stream_input->avb_interface_index)]; + + rc = acmp_send_cmd(acmp, port_cmd, acmp_rsp, desc_rsp, ACMP_CONNECT_TX_COMMAND, 0, pdu->sequence_id, NULL, 0); + + /* Disable fast-connect as we received a controller command on this stream input */ + listener_info->u.ieee.flags_priv &= ~ACMP_LISTENER_FL_FAST_CONNECT; + } else { + acmp_rsp->sequence_id = pdu->sequence_id; + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_CONNECT_RX_RESPONSE, ACMP_STAT_LISTENER_EXCLUSIVE); + } + + break; + + case ACMP_CONNECT_TX_RESPONSE: + if (status == ACMP_STAT_SUCCESS) { + status_rsp = acmp_ieee_listener_connect(entity, listener_info, pdu); + } + else + status_rsp = status; + + acmp_ieee_listener_copy_info(acmp_rsp, listener_info); + + if (avdecc_inflight_cancel(entity, &acmp->inflight, ntohs(pdu->sequence_id), &orig_sequence_id, NULL, NULL) < 0) + os_log(LOG_ERR, "acmp(%p) Could not cancel inflight seq %d\n", acmp, ntohs(pdu->sequence_id)); + else + acmp_rsp->sequence_id = orig_sequence_id; + + /* Do not send controller response in fast-connect */ + if (!(listener_info->u.ieee.flags_priv & ACMP_LISTENER_FL_FAST_CONNECT)) + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_CONNECT_RX_RESPONSE, status_rsp); + else + net_tx_free(desc_rsp); + + break; + + case ACMP_DISCONNECT_RX_COMMAND: + if (acmp_ieee_listener_connected_to(listener_info, pdu)) { + if ((status_rsp = acmp_ieee_listener_disconnect(entity, listener_info, unique_id)) == ACMP_STAT_SUCCESS) { + struct stream_descriptor *stream_input = aem_get_descriptor(entity->aem_descs, + AEM_DESC_TYPE_STREAM_INPUT, unique_id, NULL); + + acmp_rsp->flags = pdu->flags; + + /* The DISCONNECT_TX_COMMAND should be sent on the port attached to the STREAM_INPUT to be disconnected */ + port_cmd = &avdecc->port[ntohs(stream_input->avb_interface_index)]; + + rc = acmp_send_cmd(acmp, port_cmd, acmp_rsp, desc_rsp, ACMP_DISCONNECT_TX_COMMAND, 0, pdu->sequence_id, NULL, 0); + + goto exit; + } + } + else + status_rsp = ACMP_STAT_NOT_CONNECTED; + + acmp_rsp->sequence_id = pdu->sequence_id; + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_DISCONNECT_RX_RESPONSE, status_rsp); + + break; + + case ACMP_DISCONNECT_TX_RESPONSE: + if (avdecc_inflight_cancel(entity, &acmp->inflight, ntohs(pdu->sequence_id), &orig_sequence_id, NULL, NULL) < 0) + os_log(LOG_ERR, "acmp(%p) Could not cancel inflight seq %d\n", acmp, ntohs(pdu->sequence_id)); + else + acmp_rsp->sequence_id = orig_sequence_id; + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_DISCONNECT_RX_RESPONSE, status); + + break; + + case ACMP_GET_RX_STATE_COMMAND: + acmp_ieee_listener_copy_info(acmp_rsp, listener_info); + + acmp_rsp->sequence_id = pdu->sequence_id; + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_GET_RX_STATE_RESPONSE, ACMP_STAT_SUCCESS); + + break; + + default: + os_log(LOG_ERR, "acmp(%p) message type (%x) not supported\n", acmp, msg_type); + net_tx_free(desc_rsp); + rc = -1; + + break; + } + +exit: + return rc; +} + +/** Main ACMP talker receive function. + * Implementation of the ACMP talker state machine (8.2.2.6.3). + * \return 0 on success, negative otherwise + * \param acmp pointer to the ACMP context + * \param pdu pointer to the ACMP PDU + * \param msg_type ACMP message type (8.2.1.5) + * \param status status from AVTP control header (8.2.1.6) + * \param port_id avdecc port / interface index on which we received the PDU + */ +int acmp_ieee_talker_rcv(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 msg_type, u8 status, unsigned int port_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct net_tx_desc *desc_rsp; + struct acmp_pdu *acmp_rsp; + u16 status_rsp = 0; + int rc = 0; + u16 unique_id = ntohs(pdu->talker_unique_id); + u16 connection_count; + struct listener_pair *listener; + struct talker_stream_info *stream_info; + struct avdecc_port *port_rsp = &avdecc->port[port_id]; + + os_log(LOG_INFO, "acmp(%p) port(%u) %s: controller(%016"PRIx64") talker(%016"PRIx64", %u) listener(%016"PRIx64", %u)\n", + acmp, port_id, acmp_ieee_msgtype2string(msg_type), ntohll(pdu->controller_entity_id), + ntohll(entity->desc->entity_id), unique_id, + ntohll(pdu->listener_entity_id), ntohs(pdu->listener_unique_id)); + + /* Only responses are sent by talker */ + desc_rsp = acmp_net_tx_init(acmp, port_rsp, pdu, true); + if (!desc_rsp) { + rc = -1; + goto exit; + } + + acmp_rsp = (struct acmp_pdu *)((char *)NET_DATA_START(desc_rsp) + OFFSET_TO_ACMP); + + if (!acmp_talker_unique_valid(acmp, unique_id)) { + if (ACMP_IS_TALKER_COMMAND(msg_type)) { + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_TALKER_UNKNOWN_ID); + } else + net_tx_free(desc_rsp); + + goto exit; + } + + stream_info = &acmp->u.ieee.talker_info[unique_id]; + + switch (msg_type) { + case ACMP_CONNECT_TX_COMMAND: + if ((listener = acmp_ieee_talker_connected_to(acmp, stream_info, pdu)) == NULL) { + status_rsp = acmp_ieee_talker_connect(entity, stream_info, pdu); + + os_log(LOG_INFO, "acmp(%p) listener(%016"PRIx64", %d) unique id(%x) is connected: connection_count %u/%u\n", + acmp, ntohll(pdu->listener_entity_id), ntohs(pdu->listener_unique_id), unique_id, stream_info->u.ieee.connection_count, acmp->u.ieee.max_listener_pairs); + } + else + status_rsp = ACMP_STAT_SUCCESS; + + acmp_ieee_talker_copy_stream_info(acmp_rsp, stream_info); + + acmp_rsp->flags = pdu->flags; + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_CONNECT_TX_RESPONSE, status_rsp); + + break; + + case ACMP_DISCONNECT_TX_COMMAND: + if ((listener = acmp_ieee_talker_connected_to(acmp, stream_info, pdu)) != NULL) { + status_rsp = acmp_ieee_talker_disconnect(entity, stream_info, listener, unique_id); + + acmp_ieee_talker_copy_stream_info(acmp_rsp, stream_info); + + os_log(LOG_INFO, "acmp(%p) listener(%016"PRIx64", %d) unique id(%x) has disconnected: connection_count %u/%u\n", + acmp, ntohll(pdu->listener_entity_id), ntohs(pdu->listener_unique_id), unique_id, stream_info->u.ieee.connection_count, acmp->u.ieee.max_listener_pairs); + } + else + status_rsp = ACMP_STAT_NO_SUCH_CONNECTION; + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_DISCONNECT_TX_RESPONSE, status_rsp); + + break; + + case ACMP_GET_TX_STATE_COMMAND: + acmp_ieee_talker_copy_stream_info(acmp_rsp, stream_info); + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_GET_TX_STATE_RESPONSE, ACMP_STAT_SUCCESS); + + break; + + case ACMP_GET_TX_CONNECTION_COMMAND: + connection_count = ntohs(pdu->connection_count); + + if ((connection_count >= acmp->u.ieee.max_listener_pairs) || !stream_info->u.ieee.listeners[connection_count].connected) + status_rsp = ACMP_STAT_NO_SUCH_CONNECTION; + else { + copy_64(&acmp_rsp->listener_entity_id, &stream_info->u.ieee.listeners[connection_count].listener_entity_id); + acmp_rsp->listener_unique_id = stream_info->u.ieee.listeners[connection_count].listener_unique_id; + + acmp_ieee_talker_copy_stream_info(acmp_rsp, stream_info); + + status_rsp = ACMP_STAT_SUCCESS; + } + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, ACMP_GET_TX_CONNECTION_RESPONSE, status_rsp); + + break; + + default: + os_log(LOG_ERR, "acmp(%p) message type (%x) not supported\n", acmp, msg_type); + net_tx_free(desc_rsp); + rc = -1; + + break; + } + +exit: + return rc; +} + +__init unsigned int acmp_ieee_data_size(struct avdecc_entity_config *cfg) +{ + return cfg->max_talker_streams * cfg->max_listener_pairs * sizeof(struct listener_pair); +} + +__init int acmp_ieee_init(struct acmp_ctx *acmp, void *data, struct avdecc_entity_config *cfg) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct listener_pair *listener_pairs; + int i; + int rc = 0; + + if (!data) { + os_log(LOG_ERR, "acmp(%p) No allocated memory for ListenerPairs array\n", acmp); + rc = -1; + goto exit; + } + + acmp->u.ieee.max_listener_pairs = cfg->max_listener_pairs; + + acmp->u.ieee.listener_info = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, 0, NULL); + acmp->u.ieee.talker_info = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, 0, NULL); + + listener_pairs = (struct listener_pair *)data; + + for (i = 0; i < acmp->max_talker_streams; i++) { + acmp->u.ieee.talker_info[i].u.ieee.listeners = &listener_pairs[i * cfg->max_listener_pairs]; + + /* Init presentation_time_offset to invalid value. + * If not user defined by connection time, the ACMP code will default it to the SR class max transit time. + */ + acmp->u.ieee.talker_info[i].presentation_time_offset = STREAM_PRESENTATION_TIME_OFFSET_INVALID; + } + + if (acmp->max_listener_streams) + acmp_ieee_listener_fast_connect_init(acmp, cfg); + +exit: + return rc; +} + +__exit int acmp_ieee_exit(struct acmp_ctx *acmp) +{ + acmp_ieee_listener_fast_disconnect(acmp); + + acmp_ieee_talker_local_disconnect(acmp); + + os_log(LOG_INIT, "done\n"); + + return 0; +} diff --git a/avdecc/acmp_ieee.h b/avdecc/acmp_ieee.h new file mode 100644 index 0000000..fd09bac --- /dev/null +++ b/avdecc/acmp_ieee.h @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2020-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ACMP IEEE 1722.1 common definitions +*/ + +#ifndef _ACMP_IEEE_H_ +#define _ACMP_IEEE_H_ + +#include "common/types.h" +#include "common/acmp.h" +#include "common/timer.h" +#include "common/ipc.h" + +#define ACMP_LISTENER_FL_FAST_CONNECT (1 << 0) /* Stream is configured for fast connect */ +#define ACMP_LISTENER_FL_FAST_CONNECT_BTB (1 << 1) /* Stream is configured for fast connect back-to-back */ +#define ACMP_LISTENER_FL_FAST_CONNECT_PENDING (1 << 2) /* All fast connect information is available */ + +/** + * Listener stream information context + * 8.2.2.2.2 + */ +#define listener_stream_info stream_input_dynamic_desc + +/** + * Listener pair context + * 8.2.2.2.3 + */ +struct listener_pair { + u64 listener_entity_id; + u16 listener_unique_id; + u8 connected; +}; + +/** + * Talker stream information context + * 8.2.2.2.4 + */ +#define talker_stream_info stream_output_dynamic_desc + +struct avdecc_ctx; +struct acmp_ctx; +struct entity; +struct acmp_ctx; + +int acmp_ieee_init(struct acmp_ctx *acmp, void *data, struct avdecc_entity_config *cfg); +int acmp_ieee_exit(struct acmp_ctx *acmp); +unsigned int acmp_ieee_data_size(struct avdecc_entity_config *cfg); +void acmp_ieee_listener_fast_connect(struct acmp_ctx *acmp, u64 entity_id, unsigned int port_id); +void acmp_ieee_listener_fast_connect_btb(struct acmp_ctx *acmp, u64 entity_id, unsigned int port_id); +void acmp_ieee_listener_talker_left(struct acmp_ctx *acmp, u64 entity_id); +bool acmp_ieee_is_stream_running(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index); +const char *acmp_ieee_msgtype2string(acmp_message_type_t msg_type); +int acmp_ieee_get_command_timeout_ms(acmp_message_type_t msg_type); +int acmp_ieee_listener_rcv(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 msg_type, u8 status, unsigned int port_id); +int acmp_ieee_talker_rcv(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 msg_type, u8 status, unsigned int port_id); + +#endif /* _ACMP_IEEE_H_ */ diff --git a/avdecc/acmp_milan.c b/avdecc/acmp_milan.c new file mode 100644 index 0000000..0278e45 --- /dev/null +++ b/avdecc/acmp_milan.c @@ -0,0 +1,2388 @@ +/* +* Copyright 2021-2024 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + @brief ACMP Milan code + @details Handles ACMP Milan stack +*/ + +#include "os/stdlib.h" + +#include "common/log.h" +#include "common/ether.h" +#include "common/hash.h" + +#include "acmp_milan.h" +#include "avdecc.h" + +#define ACMP_MSG_TYPE_NONE 0xff + +#define ACMP_MILAN_IS_LISTENER_BINDING_COMMAND(msg_type) ((msg_type == ACMP_BIND_RX_COMMAND) || (msg_type == ACMP_UNBIND_RX_COMMAND)) +#define ACMP_MILAN_IS_LISTENER_NETWORK_RCV(msg_type) ((msg_type != ACMP_MSG_TYPE_NONE)) + +/* MILAN Specification v1.2 (5.5.3.3 Specification of the Listener’s behavior) */ +/* Random timer between 0 and 1 sec per spec: Make it between 0.2 and 1 sec to avoid 0 period timers and give time to async notifications. */ +#define ACMP_MILAN_LISTENER_TMR_DELAY_MS (random_range(200, 1000)) + +#define ACMP_MILAN_LISTENER_TMR_RETRY_MS (4 * 1000) /* 4 seconds timer */ +#define ACMP_MILAN_LISTENER_TMR_NO_TK_MS (10 * 1000) /* 10 seconds timer */ + +#define ACMP_MILAN_LISTENER_TMR_DELAY_GRANULARITY_MS 10 +#define ACMP_MILAN_LISTENER_TMR_RETRY_GRANULARITY_MS 100 +#define ACMP_MILAN_LISTENER_TMR_NO_TK_GRANULARITY_MS 100 + +/* MILAN Specification v1.2 (4.3.3.1 Talker attribute declaration) */ +#define ACMP_MILAN_TALKER_TMR_PROBE_TX_RECEPTION_MS (15 * 1000) /* 15 seconds timer */ +#define ACMP_MILAN_TALKER_TMR_PROBE_TX_RECEPTION_GRANULARITY_MS 100 + +/* MILAN Specification v1.2 (5.3.7.5 Plug-and-play SRP parameters) */ +#define ACMP_MILAN_TALKER_TMR_SRP_WITHDRAW_MS (2 * MRP_LVATIMER_VAL_MAX) /* 2 Max supported LeaveALL period for Milan */ +#define ACMP_MILAN_TALKER_TMR_SRP_WITHDRAW_GRANULARITY_MS 100 + +#define ACMP_MILAN_TALKER_ASYNC_UNSOLICITED_NOTIFICATION_MS 1000 /* 1 second timer */ +#define ACMP_MILAN_TALKER_ASYNC_UNSOLICITED_NOTIFICATION_GRANULARITY_MS 100 + +/* Listener's unsolicited notification timer should be lower than all the timers in the listener state machines so we don't miss any states changes */ +#define ACMP_MILAN_LISTENER_ASYNC_UNSOLICITED_NOTIFICATION_MS 100 /* 100ms timer */ +#define ACMP_MILAN_LISTENER_ASYNC_UNSOLICITED_NOTIFICATION_GRANULARITY_MS 10 + +#define acmp_milan_listener_sink_srp_state_clear(stream_input_dynamic) \ + acmp_milan_set_stream_input_srp_params((stream_input_dynamic), NULL); \ + (stream_input_dynamic)->u.milan.srp_state = ACMP_LISTENER_SINK_SRP_STATE_NOT_REGISTERING; \ + (stream_input_dynamic)->u.milan.srp_stream_status = NO_TALKER; \ + +static const char *acmp_milan_listener_sink_state2string(acmp_milan_listener_sink_sm_state_t state) +{ + switch (state) { + case2str(ACMP_LISTENER_SINK_SM_STATE_UNBOUND); + case2str(ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL); + case2str(ACMP_LISTENER_SINK_SM_STATE_PRB_W_DELAY); + case2str(ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP); + case2str(ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP2); + case2str(ACMP_LISTENER_SINK_SM_STATE_PRB_W_RETRY); + case2str(ACMP_LISTENER_SINK_SM_STATE_SETTLED_NO_RSV); + case2str(ACMP_LISTENER_SINK_SM_STATE_SETTLED_RSV_OK); + default: + return (char *) "Unknown listener sink state"; + } +} + +static const char *acmp_milan_listener_sink_event2string(acmp_milan_listener_sink_sm_event_t event) +{ + switch (event) { + case2str(ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_RESP); + case2str(ACMP_LISTENER_SINK_SM_EVENT_TMR_RETRY); + case2str(ACMP_LISTENER_SINK_SM_EVENT_TMR_DELAY); + case2str(ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_TK); + case2str(ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD); + case2str(ACMP_LISTENER_SINK_SM_EVENT_RCV_PROBE_TX_RESP); + case2str(ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE); + case2str(ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD); + case2str(ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED); + case2str(ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED); + case2str(ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_REGISTERED); + case2str(ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_UNREGISTERED); + case2str(ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS); + default: + return (char *) "Unknown listener sink event"; + } +} + +const char *acmp_milan_msgtype2string(acmp_message_type_t msg_type) +{ + switch (msg_type) { + case2str(ACMP_PROBE_TX_COMMAND); + case2str(ACMP_PROBE_TX_RESPONSE); + case2str(ACMP_DISCONNECT_TX_COMMAND); + case2str(ACMP_DISCONNECT_TX_RESPONSE); + case2str(ACMP_GET_TX_STATE_COMMAND); + case2str(ACMP_GET_TX_STATE_RESPONSE); + case2str(ACMP_BIND_RX_COMMAND); + case2str(ACMP_BIND_RX_RESPONSE); + case2str(ACMP_UNBIND_RX_COMMAND); + case2str(ACMP_UNBIND_RX_RESPONSE); + case2str(ACMP_GET_RX_STATE_COMMAND); + case2str(ACMP_GET_RX_STATE_RESPONSE); + case2str(ACMP_GET_TX_CONNECTION_COMMAND); + case2str(ACMP_GET_TX_CONNECTION_RESPONSE); + default: + return (char *) "Unknown ACMP MILAN message type"; + } +} + +static u8 acmp_milan_listener_get_msg_type(acmp_milan_listener_sink_sm_event_t event) +{ + u8 rc; + + switch(event) { + case ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD: + rc = ACMP_BIND_RX_COMMAND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE: + rc = ACMP_GET_RX_STATE_COMMAND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD: + rc = ACMP_UNBIND_RX_COMMAND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_PROBE_TX_RESP: + rc = ACMP_PROBE_TX_RESPONSE; + break; + + default: + rc = ACMP_MSG_TYPE_NONE; + break; + } + + return rc; +} + +static acmp_milan_listener_sink_sm_event_t acmp_milan_listener_get_event(u8 msg_type) +{ + acmp_milan_listener_sink_sm_event_t rc; + + switch(msg_type) { + case ACMP_BIND_RX_COMMAND: + rc = ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD; + break; + + case ACMP_GET_RX_STATE_COMMAND: + rc = ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE; + break; + + case ACMP_UNBIND_RX_COMMAND: + rc = ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD; + break; + + case ACMP_PROBE_TX_RESPONSE: + rc = ACMP_LISTENER_SINK_SM_EVENT_RCV_PROBE_TX_RESP; + break; + + default: + rc = ACMP_LISTENER_SINK_SM_EVENT_UNKNOWN; + break; + } + + return rc; +} + +int acmp_milan_get_command_timeout_ms(acmp_message_type_t msg_type) +{ + switch (msg_type) { + case ACMP_PROBE_TX_COMMAND: + case ACMP_DISCONNECT_TX_COMMAND: + case ACMP_GET_TX_STATE_COMMAND: + case ACMP_BIND_RX_COMMAND: + case ACMP_UNBIND_RX_COMMAND: + case ACMP_GET_RX_STATE_COMMAND: + case ACMP_GET_TX_CONNECTION_COMMAND: + return 200; + default: + return -1; + } +} + +/** Start a timer to send an async unsolicited notification after a short delay if the timer is not currently running + * \return none + * \param stream_input_dynamic, the dynamic descriptor of the stream input that triggered the notification + */ +static void acmp_milan_listener_register_async_get_stream_info_notification(struct stream_input_dynamic_desc *stream_input_dynamic) +{ + if (!timer_is_running(&stream_input_dynamic->u.milan.async_unsolicited_notification_timer)) + timer_start(&stream_input_dynamic->u.milan.async_unsolicited_notification_timer, ACMP_MILAN_LISTENER_ASYNC_UNSOLICITED_NOTIFICATION_MS); +} + +/** Send unbind message through the media stack ipc channel + * Sends an indication to media application about unbind stream update, to save in non-volatile + * memory. + * \return ACMP status + * \param entity pointer to the entity context + * \param listener_unique_id valid listener unique id (in host order) + */ +static u8 acmp_listener_ipc_send_unbind_status(struct entity *entity, u16 listener_unique_id) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + struct ipc_desc *desc; + u8 rc = ACMP_STAT_SUCCESS; + + if (!acmp_listener_unique_valid(&entity->acmp, listener_unique_id)) { + os_log(LOG_ERR, "acmp(%p) invalid listener unique id %u\n", + &entity->acmp, listener_unique_id); + rc = ACMP_STAT_LISTENER_MISBEHAVING; + goto exit; + } + + desc = ipc_alloc(&avdecc->ipc_tx_media_stack, sizeof(struct genavb_msg_media_stack_unbind)); + if (desc) { + desc->dst = IPC_DST_ALL; + desc->type = GENAVB_MSG_MEDIA_STACK_UNBIND; + desc->len = sizeof(struct genavb_msg_media_stack_unbind); + + desc->u.media_stack_unbind.entity_id = get_ntohll(&entity->desc->entity_id); + desc->u.media_stack_unbind.entity_index = entity->index; + desc->u.media_stack_unbind.listener_stream_index = listener_unique_id; + + if (ipc_tx(&avdecc->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + ipc_free(&avdecc->ipc_tx_media_stack, desc); + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + } + +exit: + return rc; +} + +/** Send bind message through the media stack ipc channel + * Sends an indication to media application about binding parameters update, to save in non-volatile + * memory. + * \return 0 on success, -1 otherwise + * \param entity pointer to the entity context + * \param listener_unique_id valid listener unique id (in host order) + */ +static int acmp_listener_ipc_send_bind_status(struct entity *entity, u16 listener_unique_id) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + struct stream_input_dynamic_desc *stream_input_dynamic; + struct ipc_desc *desc; + int rc = 0; + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + if (!stream_input_dynamic) { + os_log(LOG_ERR, "acmp(%p) cannot find dynamic descriptor type %d index %u\n", + &entity->acmp, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id); + rc = -1; + goto exit; + } + + desc = ipc_alloc(&avdecc->ipc_tx_media_stack, sizeof(struct genavb_msg_media_stack_bind)); + if (desc) { + desc->dst = IPC_DST_ALL; + desc->type = GENAVB_MSG_MEDIA_STACK_BIND; + desc->len = sizeof(struct genavb_msg_media_stack_bind); + + desc->u.media_stack_bind.entity_id = get_ntohll(&entity->desc->entity_id); + desc->u.media_stack_bind.entity_index = entity->index; + desc->u.media_stack_bind.listener_stream_index = listener_unique_id; + desc->u.media_stack_bind.talker_entity_id = get_ntohll(&stream_input_dynamic->talker_entity_id); + desc->u.media_stack_bind.talker_stream_index = ntohs(stream_input_dynamic->talker_unique_id); + desc->u.media_stack_bind.controller_entity_id = get_ntohll(&stream_input_dynamic->controller_entity_id); + desc->u.media_stack_bind.started = (stream_input_dynamic->flags & htons(ACMP_FLAG_STREAMING_WAIT)) ? ACMP_LISTENER_STREAM_STOPPED : ACMP_LISTENER_STREAM_STARTED; + + if (ipc_tx(&avdecc->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + ipc_free(&avdecc->ipc_tx_media_stack, desc); + } + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + } + +exit: + return rc; +} + +static void acmp_milan_set_stream_input_binding_params(struct stream_input_dynamic_desc *stream_input_dynamic, struct acmp_pdu *pdu) +{ + if (!pdu) { + stream_input_dynamic->controller_entity_id = 0; + stream_input_dynamic->talker_entity_id = 0; + stream_input_dynamic->talker_unique_id = 0; + stream_input_dynamic->flags = 0; + } else { + copy_64(&stream_input_dynamic->controller_entity_id, &pdu->controller_entity_id); + copy_64(&stream_input_dynamic->talker_entity_id, &pdu->talker_entity_id); + stream_input_dynamic->talker_unique_id = pdu->talker_unique_id; + stream_input_dynamic->flags = pdu->flags & htons(ACMP_FLAG_STREAMING_WAIT); + } +} + +/* Per MILAN Specification v1.2 5.5.3: When already bound, restart binding process only + * if talker_entity_id and talker_unique_id fields are not equal to those saved for the current binding + */ +static bool acmp_milan_need_binding_restart(struct stream_input_dynamic_desc *stream_input_dynamic, struct acmp_pdu *pdu) +{ + if (!cmp_64(&stream_input_dynamic->talker_entity_id, &pdu->talker_entity_id) || + stream_input_dynamic->talker_unique_id != pdu->talker_unique_id) + return true; + + return false; +} + +static void acmp_milan_set_stream_input_srp_params(struct stream_input_dynamic_desc *stream_input_dynamic, struct acmp_pdu *pdu) +{ + u8 invalid_stream_dest_mac[6] = {0}; + + if (!pdu) { + stream_input_dynamic->stream_vlan_id = 0; + stream_input_dynamic->stream_id = 0; + os_memcpy(stream_input_dynamic->stream_dest_mac, invalid_stream_dest_mac, 6); + } else { + stream_input_dynamic->stream_vlan_id = pdu->stream_vlan_id; + copy_64(&stream_input_dynamic->stream_id, &pdu->stream_id); + os_memcpy(stream_input_dynamic->stream_dest_mac, pdu->stream_dest_mac, 6); + } + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_INPUT descriptor state (stream_id, stream_dest_mac, stream_vlan_id) + */ + acmp_milan_listener_register_async_get_stream_info_notification(stream_input_dynamic); +} + +static int acmp_milan_send_get_rx_state_response(struct entity *entity, u16 listener_unique_id, struct avdecc_port *port, struct net_tx_desc *desc_rsp) +{ + struct acmp_pdu *acmp_rsp; + struct acmp_ctx *acmp = &entity->acmp; + struct stream_input_dynamic_desc *stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + + acmp_rsp = (struct acmp_pdu *)((char *)NET_DATA_START(desc_rsp) + OFFSET_TO_ACMP); + + acmp_listener_copy_common_params(acmp_rsp, stream_input_dynamic); + + if (ACMP_MILAN_IS_LISTENER_SINK_BOUND(stream_input_dynamic)) { + acmp_rsp->connection_count = htons(1); + acmp_rsp->flags |= htons(ACMP_FLAG_FAST_CONNECT); + } else { + acmp_rsp->connection_count = htons(0); + acmp_rsp->flags &= ~htons(ACMP_FLAG_FAST_CONNECT); + } + + if ((stream_input_dynamic->u.milan.state == ACMP_LISTENER_SINK_SM_STATE_SETTLED_RSV_OK) + && (stream_input_dynamic->u.milan.srp_stream_status == FAILED)) { + acmp_rsp->flags |= htons(ACMP_FLAG_REGISTERING_FAILED); + } else { + acmp_rsp->flags &= ~htons(ACMP_FLAG_REGISTERING_FAILED); + } + + return acmp_send_rsp(acmp, port, desc_rsp, ACMP_GET_RX_STATE_RESPONSE, ACMP_STAT_SUCCESS); +} + +static int acmp_milan_send_unbind_rx_response(struct entity *entity, u16 listener_unique_id, struct avdecc_port *port, struct net_tx_desc *desc_rsp) +{ + struct acmp_pdu *acmp_rsp; + struct acmp_ctx *acmp = &entity->acmp; + struct stream_input_dynamic_desc *stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + + acmp_rsp = (struct acmp_pdu *)((char *)NET_DATA_START(desc_rsp) + OFFSET_TO_ACMP); + + acmp_listener_copy_common_params(acmp_rsp, stream_input_dynamic); + + acmp_rsp->connection_count = htons(0); + + return acmp_send_rsp(acmp, port, desc_rsp, ACMP_UNBIND_RX_RESPONSE, ACMP_STAT_SUCCESS); +} + +static int acmp_milan_send_bind_rx_response(struct entity *entity, u16 listener_unique_id, struct avdecc_port *port, struct net_tx_desc *desc_rsp, u16 flags, u16 status) +{ + struct acmp_pdu *acmp_rsp; + struct acmp_ctx *acmp = &entity->acmp; + + acmp_rsp = (struct acmp_pdu *)((char *)NET_DATA_START(desc_rsp) + OFFSET_TO_ACMP); + + /* STREAMING_WAIT flag same as in the bind command. */ + if (status == ACMP_STAT_SUCCESS) { + acmp_rsp->flags |= flags & htons(ACMP_FLAG_STREAMING_WAIT); + + acmp_rsp->flags &= ~htons(ACMP_FLAG_FAST_CONNECT | ACMP_FLAG_REGISTERING_FAILED); + + acmp_rsp->connection_count = htons(1); + } + + return acmp_send_rsp(acmp, port, desc_rsp, ACMP_BIND_RX_RESPONSE, status); +} + +static int acmp_milan_send_probe_tx_command(struct entity *entity, u16 listener_unique_id) +{ + struct acmp_pdu *acmp_cmd; + struct net_tx_desc *desc_cmd; + int rc = 0; + struct acmp_ctx *acmp = &entity->acmp; + struct avdecc_ctx *avdecc = entity->avdecc; + struct avdecc_port *port_cmd; + struct stream_input_dynamic_desc *stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + struct stream_descriptor *stream_input = aem_get_descriptor(entity->aem_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + + /* Send PROBE_TX_COMMAND on the port associated with the STREAM_INPUT descriptor */ + port_cmd = &avdecc->port[ntohs(stream_input->avb_interface_index)]; + + desc_cmd = acmp_net_tx_alloc(acmp, port_cmd); + if (!desc_cmd) { + rc = -1; + goto exit; + } + + acmp_cmd = (struct acmp_pdu *)((char *)NET_DATA_START(desc_cmd) + OFFSET_TO_ACMP); + + copy_64(&acmp_cmd->controller_entity_id, &stream_input_dynamic->controller_entity_id); + copy_64(&acmp_cmd->talker_entity_id, &stream_input_dynamic->talker_entity_id); + copy_64(&acmp_cmd->listener_entity_id, &entity->desc->entity_id); + + acmp_cmd->talker_unique_id = stream_input_dynamic->talker_unique_id; + acmp_cmd->listener_unique_id = htons(listener_unique_id); + + /* Save the sequence_id of the PDU to be sent */ + stream_input_dynamic->u.milan.probe_tx_seq_id = acmp->sequence_id; + + rc = acmp_send_cmd(acmp, port_cmd, acmp_cmd, desc_cmd, ACMP_PROBE_TX_COMMAND, 0, acmp->sequence_id, NULL, 0); + +exit: + return rc; +} + +/** Non standard check + * Our low level network code doesn't allow to receive the same Talker stream in two network sockets, + * In other words, we don't support multiple Listener streams to connect/bind to a same Talker stream + * So check if this Talker stream is already bound to another Listener stream + */ +static bool is_listener_already_bound_to_talker_unique_id(struct entity *entity, u16 listener_unique_id, u64 talker_entity_id, u16 talker_unique_id) +{ + struct stream_input_dynamic_desc *stream_input_dynamic; + unsigned int num_stream_input; + int i; + + num_stream_input = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT); + + for (i = 0; i < num_stream_input; i++) { + /* Skip ourself */ + if (i == listener_unique_id) + continue; + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + + if (cmp_64(&stream_input_dynamic->talker_entity_id, &talker_entity_id) + && (stream_input_dynamic->talker_unique_id == talker_unique_id)) { + + return true; + } + } + + return false; +} + +static int acmp_milan_listener_bind(struct entity *entity, u16 listener_unique_id, struct avdecc_port *port, struct acmp_pdu *pdu, + struct net_tx_desc *desc_rsp, bool *binding_restart) +{ + bool need_binding_restart, streaming_wait_changed, controller_changed; + struct stream_input_dynamic_desc *stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + int rc = 0; + + if (is_listener_already_bound_to_talker_unique_id(entity, listener_unique_id, pdu->talker_entity_id, pdu->talker_unique_id)) { + acmp_milan_send_bind_rx_response(entity, listener_unique_id, port, desc_rsp, pdu->flags, ACMP_STAT_TALKER_EXCLUSIVE); + rc = -1; + goto exit; + } + + need_binding_restart = acmp_milan_need_binding_restart(stream_input_dynamic, pdu); + streaming_wait_changed = (stream_input_dynamic->flags != (pdu->flags & htons(ACMP_FLAG_STREAMING_WAIT))); + controller_changed = !cmp_64(&stream_input_dynamic->controller_entity_id, &pdu->controller_entity_id); + + acmp_milan_set_stream_input_binding_params(stream_input_dynamic, pdu); + + rc = acmp_milan_send_bind_rx_response(entity, listener_unique_id, port, desc_rsp, pdu->flags, ACMP_STAT_SUCCESS); + + if (need_binding_restart) { + rc |= acmp_milan_send_probe_tx_command(entity, listener_unique_id); + + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_ACTIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + } + + if (binding_restart) + *binding_restart = need_binding_restart; + + /* If any binding params has changed, notifiy upper layer. */ + if (need_binding_restart || streaming_wait_changed || controller_changed) + acmp_listener_ipc_send_bind_status(entity, listener_unique_id); + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_INPUT descriptor state (flag: BOUND and flag:STREAMING_WAIT) + */ + if (streaming_wait_changed || controller_changed) { + acmp_milan_listener_register_async_get_stream_info_notification(stream_input_dynamic); + } + +exit: + return rc; +} + +static int acmp_milan_listener_unbind(struct entity *entity, u16 listener_unique_id, struct avdecc_port *port, struct net_tx_desc *desc_rsp) +{ + struct stream_input_dynamic_desc *stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + int rc = 0; + + /* Clear the binding parameters. */ + acmp_milan_set_stream_input_binding_params(stream_input_dynamic, NULL); + + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_DISABLED; + stream_input_dynamic->u.milan.acmp_status = 0; + + rc = acmp_milan_send_unbind_rx_response(entity, listener_unique_id, port, desc_rsp); + + acmp_listener_ipc_send_unbind_status(entity, listener_unique_id); + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_INPUT descriptor state (flag: BOUND and flag:STREAMING_WAIT) + */ + acmp_milan_listener_register_async_get_stream_info_notification(stream_input_dynamic); + + return rc; +} + +static void acmp_listener_sink_delay_timer_handler(void *data) +{ + struct stream_input_dynamic_desc *stream_input_dynamic = (struct stream_input_dynamic_desc *)data; + + acmp_milan_listener_sink_event(stream_input_dynamic->u.milan.entity, stream_input_dynamic->u.milan.unique_id, ACMP_LISTENER_SINK_SM_EVENT_TMR_DELAY); +} + +static void acmp_listener_sink_retry_timer_handler(void *data) +{ + struct stream_input_dynamic_desc *stream_input_dynamic = (struct stream_input_dynamic_desc *)data; + + acmp_milan_listener_sink_event(stream_input_dynamic->u.milan.entity, stream_input_dynamic->u.milan.unique_id, ACMP_LISTENER_SINK_SM_EVENT_TMR_RETRY); +} + +static void acmp_listener_sink_tk_timer_handler(void *data) +{ + struct stream_input_dynamic_desc *stream_input_dynamic = (struct stream_input_dynamic_desc *)data; + + acmp_milan_listener_sink_event(stream_input_dynamic->u.milan.entity, stream_input_dynamic->u.milan.unique_id, ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_TK); +} + +static void acmp_milan_listener_async_unsolicited_notification_timer_handler(void *data) +{ + struct stream_input_dynamic_desc *stream_input_dynamic = (struct stream_input_dynamic_desc *)data; + struct entity *entity = stream_input_dynamic->u.milan.entity; + + aecp_aem_send_async_unsolicited_notification(&entity->aecp, AECP_AEM_CMD_GET_STREAM_INFO, AEM_DESC_TYPE_STREAM_INPUT, stream_input_dynamic->u.milan.unique_id); +} + +__init static int acmp_milan_listener_sink_init_timers(struct entity *entity, u16 listener_unique_id) +{ + struct stream_input_dynamic_desc *stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + + stream_input_dynamic->u.milan.acmp_retry_timer.func = acmp_listener_sink_retry_timer_handler; + stream_input_dynamic->u.milan.acmp_retry_timer.data = stream_input_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &stream_input_dynamic->u.milan.acmp_retry_timer, 0, ACMP_MILAN_LISTENER_TMR_RETRY_GRANULARITY_MS) < 0) + goto err_retry_timer; + + stream_input_dynamic->u.milan.acmp_delay_timer.func = acmp_listener_sink_delay_timer_handler; + stream_input_dynamic->u.milan.acmp_delay_timer.data = stream_input_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &stream_input_dynamic->u.milan.acmp_delay_timer, 0, ACMP_MILAN_LISTENER_TMR_DELAY_GRANULARITY_MS) < 0) + goto err_delay_timer; + + stream_input_dynamic->u.milan.acmp_talker_registration_timer.func = acmp_listener_sink_tk_timer_handler; + stream_input_dynamic->u.milan.acmp_talker_registration_timer.data = stream_input_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &stream_input_dynamic->u.milan.acmp_talker_registration_timer, 0, ACMP_MILAN_LISTENER_TMR_NO_TK_GRANULARITY_MS) < 0) + goto err_tk_timer; + + stream_input_dynamic->u.milan.async_unsolicited_notification_timer.func = acmp_milan_listener_async_unsolicited_notification_timer_handler; + stream_input_dynamic->u.milan.async_unsolicited_notification_timer.data = stream_input_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &stream_input_dynamic->u.milan.async_unsolicited_notification_timer, 0, + ACMP_MILAN_LISTENER_ASYNC_UNSOLICITED_NOTIFICATION_GRANULARITY_MS) < 0) + goto err_notification_timer; + + return 0; + +err_notification_timer: + timer_destroy(&stream_input_dynamic->u.milan.acmp_talker_registration_timer); + +err_tk_timer: + timer_destroy(&stream_input_dynamic->u.milan.acmp_delay_timer); + +err_delay_timer: + timer_destroy(&stream_input_dynamic->u.milan.acmp_retry_timer); + +err_retry_timer: + return -1; +} + +__exit static void acmp_milan_listener_sink_exit_timers(struct entity *entity, u16 listener_unique_id) +{ + struct stream_input_dynamic_desc *stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + + timer_destroy(&stream_input_dynamic->u.milan.acmp_retry_timer); + timer_destroy(&stream_input_dynamic->u.milan.acmp_delay_timer); + timer_destroy(&stream_input_dynamic->u.milan.acmp_talker_registration_timer); + timer_destroy(&stream_input_dynamic->u.milan.async_unsolicited_notification_timer); +} + +/** Helper function to check if a stream input or output is running + * \return bool, true if stream is running, false otherwise + * \param entity + * \param stream_desc_type, AEM_DESC_TYPE_STREAM_INPUT or AEM_DESC_TYPE_STREAM_OUTPUT + * \param stream_desc_index, index of the descriptor + */ +bool acmp_milan_is_stream_running(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index) +{ + bool ret = false; + void *desc; + + if (stream_desc_type != AEM_DESC_TYPE_STREAM_INPUT && stream_desc_type != AEM_DESC_TYPE_STREAM_OUTPUT) { + os_log(LOG_ERR, "entity(%p) descriptor type (%u) not supported\n", entity, stream_desc_type); + goto out; + } + + desc = aem_get_descriptor(entity->aem_dynamic_descs, stream_desc_type, stream_desc_index, NULL); + if (!desc) { + os_log(LOG_ERR, "entity(%p) stream descriptor(%u, %u) not found.\n", entity, stream_desc_type, stream_desc_index); + goto out; + } + + if (stream_desc_type == AEM_DESC_TYPE_STREAM_INPUT) { + ret = ACMP_MILAN_IS_LISTENER_SINK_BOUND((struct stream_input_dynamic_desc *)desc); + } else if (stream_desc_type == AEM_DESC_TYPE_STREAM_OUTPUT) { + ret = ((struct stream_output_dynamic_desc *)desc)->u.milan.avtp_connected; + } + +out: + return ret; +} + +/** Update streaming_wait flag and send a GENAVB_MSG_MEDIA_STACK_BIND to update the status of the stream + * \return positive value (0 if nothing changed, 1 if stream descriptor was updated) if successful, negative otherwise + * \param entity + * \param stream_desc_type, AEM_DESC_TYPE_STREAM_INPUT + * \param stream_desc_index, index of the descriptor + */ +int acmp_milan_start_streaming(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index) +{ + struct stream_input_dynamic_desc *stream_in_desc; + int rc = 0; + + if (stream_desc_type != AEM_DESC_TYPE_STREAM_INPUT) { + os_log(LOG_ERR, "entity(%p) descriptor type(%u) not supported for Milan start streaming\n", entity, stream_desc_type); + rc = -1; + goto exit; + } + + stream_in_desc = aem_get_descriptor(entity->aem_dynamic_descs, stream_desc_type, stream_desc_index, NULL); + if (!stream_in_desc) { + os_log(LOG_ERR, "entity(%p) stream input descriptor(%u) not found.\n", entity, stream_desc_index); + rc = -1; + goto exit; + } + + /* Stream is bound and stopped (streaming_wait flag = 1) */ + if (ACMP_MILAN_IS_LISTENER_SINK_BOUND(stream_in_desc) && (stream_in_desc->flags & htons(ACMP_FLAG_STREAMING_WAIT)) != 0) { + stream_in_desc->flags &= ~(htons(ACMP_FLAG_STREAMING_WAIT)); + + acmp_listener_ipc_send_bind_status(entity, stream_desc_index); + + rc = 1; + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_INPUT descriptor state (Started state) + */ + acmp_milan_listener_register_async_get_stream_info_notification(stream_in_desc); + + os_log(LOG_DEBUG, "entity(%p) stream input(%u) started.\n", entity, stream_desc_index); + } + +exit: + return rc; +} + +/** Update streaming_wait flag and send a GENAVB_MSG_MEDIA_STACK_BIND to update the status of the stream + * \return positive value (0 if nothing changed, 1 if stream descriptor was updated) if successful, negative otherwise + * \param entity + * \param stream_desc_type, AEM_DESC_TYPE_STREAM_INPUT + * \param stream_desc_index, index of the descriptor + */ +int acmp_milan_stop_streaming(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index) +{ + struct stream_input_dynamic_desc *stream_in_desc; + int rc = 0; + + if (stream_desc_type != AEM_DESC_TYPE_STREAM_INPUT) { + os_log(LOG_ERR, "entity(%p) descriptor type(%u) not supported for Milan stop streaming\n", entity, stream_desc_type); + rc = -1; + goto exit; + } + + stream_in_desc = aem_get_descriptor(entity->aem_dynamic_descs, stream_desc_type, stream_desc_index, NULL); + if (!stream_in_desc) { + os_log(LOG_ERR, "entity(%p) stream input descriptor(%u) not found.\n", entity, stream_desc_index); + rc = -1; + goto exit; + } + + /* Stream is bound and started (streaming_wait flag = 0) */ + if (ACMP_MILAN_IS_LISTENER_SINK_BOUND(stream_in_desc) && (stream_in_desc->flags & htons(ACMP_FLAG_STREAMING_WAIT)) == 0) { + stream_in_desc->flags |= htons(ACMP_FLAG_STREAMING_WAIT); + + acmp_listener_ipc_send_bind_status(entity, stream_desc_index); + + rc = 1; + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_INPUT descriptor state (Stopped state) + */ + acmp_milan_listener_register_async_get_stream_info_notification(stream_in_desc); + + os_log(LOG_DEBUG, "entity(%p) stream input(%u) stopped.\n", entity, stream_desc_index); + } + +exit: + return rc; +} + +/** Get a listener index matching the stream ID + * \return 0 on success, negative otherwise + * \param entity pointer to entity struct + * \param stream_id stream ID (in network order) + * \param[out] listener_unique_id pointer to variable holding the matching listener index on success. + */ +int acmp_milan_get_listener_unique_id(struct entity *entity, u64 stream_id, u16 *listener_unique_id) +{ + int i; + int rc = -1; + struct acmp_ctx *acmp = &entity->acmp; + struct stream_input_dynamic_desc *stream_input_dynamic; + + for (i = 0; i < acmp->max_listener_streams; i++) { + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + + if (cmp_64(&stream_input_dynamic->stream_id, &stream_id) && listener_unique_id) { + *listener_unique_id = i; + rc = 0; + goto exit; + } + } + +exit: + return rc; +} + +/** Main SRP state machine for a listener sink. + * This state machine is not explicitly stated in the Milan Spec, but it helps to track the listener + * srp stream status transitions and their listener sink SM event triggering. + * \return none + * \param entity pointer to entity struct + * \param listener_unique_id listener index (host order) + * \param ipc_listener_status ipc status message for srp listener + */ +void acmp_milan_listener_srp_state_sm(struct entity *entity, u16 listener_unique_id, struct genavb_msg_listener_status *ipc_listener_status) +{ + genavb_listener_stream_status_t listener_status = ipc_listener_status->status; + struct stream_input_dynamic_desc *stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + u64 stream_id; + + if (!stream_input_dynamic) { + os_log(LOG_ERR, "entity(%p) listener unique id (%u): unable to get stream input dynamic descriptor\n", + entity, listener_unique_id); + + return; + } + + if (!ACMP_MILAN_IS_LISTENER_SINK_SETTLED(stream_input_dynamic)) + return; + + /* Per MILAN Specification v1.2 5.5.3.3: EVT_TK_REGISTERED: check that the talker attribute (Talker advertise or Talker failed) + * matches Stream ID, Stream Destination MAC Address and Stream VLAN ID that are associated with the settled stream. + */ + if (listener_status == ACTIVE || listener_status == FAILED) { + + stream_id = get_64(ipc_listener_status->stream_id); + + if (!cmp_64(&stream_input_dynamic->stream_id, &stream_id) + || (os_memcmp(stream_input_dynamic->stream_dest_mac, ipc_listener_status->params.destination_address, 6)) + || (stream_input_dynamic->stream_vlan_id != htons(ipc_listener_status->params.vlan_id))) + return; + } + + /* Always update the listener stream status parameters independently from the srp state */ + stream_input_dynamic->u.milan.srp_stream_status = listener_status; + + if (listener_status != NO_TALKER) { + stream_input_dynamic->u.milan.msrp_accumulated_latency = ipc_listener_status->params.accumulated_latency; + } else { + stream_input_dynamic->u.milan.msrp_accumulated_latency = 0; + } + + if (listener_status == FAILED) { + os_memcpy(stream_input_dynamic->u.milan.failure.bridge_id, ipc_listener_status->failure.bridge_id, 8); + stream_input_dynamic->u.milan.failure.failure_code = ipc_listener_status->failure.failure_code; + } else { + os_memset(stream_input_dynamic->u.milan.failure.bridge_id, 0, 8); + stream_input_dynamic->u.milan.failure.failure_code = 0; + } + + switch (stream_input_dynamic->u.milan.srp_state) { + case ACMP_LISTENER_SINK_SRP_STATE_NOT_REGISTERING: /* The sink is not resgistering any talker attribute */ + if (listener_status == ACTIVE || listener_status == FAILED) { + stream_input_dynamic->u.milan.srp_state = ACMP_LISTENER_SINK_SRP_STATE_REGISTERING; + + /* EVT_TK_REGISTERED is only triggered on SRP state NOT_REGISTERED -> REGISTERED transition */ + acmp_milan_listener_sink_event(entity, listener_unique_id, ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_REGISTERED); + } + break; + + case ACMP_LISTENER_SINK_SRP_STATE_REGISTERING: /* The sink is already resgistering a talker attribute */ + if (listener_status == NO_TALKER) { + stream_input_dynamic->u.milan.srp_state = ACMP_LISTENER_SINK_SRP_STATE_NOT_REGISTERING; + + /* EVT_TK_UNREGISTERED is only triggered on SRP state REGISTERED -> NOT_REGISTERED transition */ + acmp_milan_listener_sink_event(entity, listener_unique_id, ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_UNREGISTERED); + } + break; + + default: + break; + } + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_INPUT's SRP talker registration state + */ + acmp_milan_listener_register_async_get_stream_info_notification(stream_input_dynamic); +} + +/** Main ACMP MILAN listener sink state machine (8.3.5) + * \return 0 on success, negative otherwise + * \param entity pointer to entity struct + * \param listener_unique_id Listener unique ID (in host order) + * \param event ACMP listener SM event (8.3.3) + * \param pdu pointer to the received ACMP PDU if network event, NULL otherwise + * \param status status from AVTP control header (8.2.1.6) if network event, 0 otherwise + * \param port_id avdecc port / interface index on which we received the PDU + */ +static int acmp_milan_listener_sink_sm(struct entity *entity, u16 listener_unique_id, acmp_milan_listener_sink_sm_event_t event, + struct acmp_pdu *pdu, u8 status, unsigned int port_id) +{ + acmp_milan_listener_sink_sm_state_t listener_sink_state; + struct stream_input_dynamic_desc *stream_input_dynamic; + u8 msg_type = acmp_milan_listener_get_msg_type(event); + struct avdecc_ctx *avdecc = entity->avdecc; + struct acmp_ctx *acmp = &entity->acmp; + struct net_tx_desc *desc_rsp = NULL; + struct avdecc_port *port_rsp = NULL; + bool stream_status_changed = false; + bool binding_restart; + u16 probe_tx_seq_id; + int rc = 0; + + /* If we received a listener network command, prepare the response PDU based on the received one */ + if (ACMP_IS_LISTENER_COMMAND(msg_type)) { + /* Response will always be sent on the same port we received the command from. */ + port_rsp = &avdecc->port[port_id]; + + desc_rsp = acmp_net_tx_init(acmp, port_rsp, pdu, true); + if (!desc_rsp) { + rc = -1; + goto exit; + } + } + + /* Check the listener sink index validity */ + if (!acmp_listener_unique_valid(acmp, listener_unique_id)) { + if (ACMP_IS_LISTENER_COMMAND(msg_type)) { + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_LISTENER_UNKNOWN_ID); + } else { + os_log(LOG_ERR, "entity(%p) event %s : wrong listener sink index %u\n", + entity, acmp_milan_listener_sink_event2string(event), listener_unique_id); + rc = -1; + } + goto exit; + } + + /* Check the locking status for the binding commands */ + if (ACMP_MILAN_IS_LISTENER_BINDING_COMMAND(msg_type) + && avdecc_entity_is_locked(entity, pdu->controller_entity_id)) { + + os_log(LOG_INFO, "entity(%p) listener sink %u: entity locked, ignore event %s\n", + entity, listener_unique_id, acmp_milan_listener_sink_event2string(event)); + + acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_CONTROLLER_NOT_AUTHORIZED); + goto exit; + } + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + listener_sink_state = stream_input_dynamic->u.milan.state; + + switch (listener_sink_state) { + case ACMP_LISTENER_SINK_SM_STATE_UNBOUND: /* The sink is not bound */ + switch (event) { + case ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD: + rc = acmp_milan_listener_bind(entity, listener_unique_id, port_rsp, pdu, desc_rsp, NULL); + if (rc < 0) + break; + + /* start the ADP discovery state machine */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE: + rc = acmp_milan_send_get_rx_state_response(entity, listener_unique_id, port_rsp, desc_rsp); + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD: + rc = acmp_milan_listener_unbind(entity, listener_unique_id, port_rsp, desc_rsp); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_UNBOUND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS: + /* This event is not explicitely mentioned in the spec but based on 8.3.5.2, start the SM with received saved binding params. */ + + /* start the ADP discovery state machine */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_PASSIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + stream_status_changed = true; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL; + break; + + default: + os_log(LOG_ERR, "entity(%p) listener sink %u state %s: invalid event %s\n", + entity, listener_unique_id, + acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event)); + break; + } + + break; + + case ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL: /* The sink is probing: waiting for ADP to report that the talker is discovered */ + switch (event) { + case ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD: + rc = acmp_milan_listener_bind(entity, listener_unique_id, port_rsp, pdu, desc_rsp, &binding_restart); + + if (rc < 0 || !binding_restart) + break; + + /* restart the ADP discovery state machine */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE: + rc = acmp_milan_send_get_rx_state_response(entity, listener_unique_id, port_rsp, desc_rsp); + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD: + rc = acmp_milan_listener_unbind(entity, listener_unique_id, port_rsp, desc_rsp); + + /* stop the ADP discovery SM */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_UNBOUND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED: + timer_start(&stream_input_dynamic->u.milan.acmp_delay_timer, ACMP_MILAN_LISTENER_TMR_DELAY_MS); + + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_ACTIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + stream_status_changed = true; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_DELAY; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS: + break; + + default: + os_log(LOG_ERR, "entity(%p) listener sink %u state %s: invalid event %s\n", + entity, listener_unique_id, + acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event)); + break; + } + + break; + + case ACMP_LISTENER_SINK_SM_STATE_PRB_W_DELAY: /* The sink is probing: ready to send PROBE_TX_COMMAND but waiting for the TMR_DELAY */ + switch (event) { + case ACMP_LISTENER_SINK_SM_EVENT_TMR_DELAY: + rc = acmp_milan_send_probe_tx_command(entity, listener_unique_id); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD: + rc = acmp_milan_listener_bind(entity, listener_unique_id, port_rsp, pdu, desc_rsp, &binding_restart); + + if (rc < 0 || !binding_restart) + break; + + timer_stop(&stream_input_dynamic->u.milan.acmp_delay_timer); + + /* restart the ADP discovery state machine */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE: + rc = acmp_milan_send_get_rx_state_response(entity, listener_unique_id, port_rsp, desc_rsp); + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD: + /* stop the ADP discovery SM */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + /* stop TMR_DELAY */ + timer_stop(&stream_input_dynamic->u.milan.acmp_delay_timer); + + rc = acmp_milan_listener_unbind(entity, listener_unique_id, port_rsp, desc_rsp); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_UNBOUND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED: + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED: + + /* stop TMR_DELAY */ + timer_stop(&stream_input_dynamic->u.milan.acmp_delay_timer); + + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_PASSIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + stream_status_changed = true; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS: + break; + + default: + os_log(LOG_ERR, "entity(%p) listener sink %u state %s: invalid event %s\n", + entity, listener_unique_id, + acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event)); + break; + } + + break; + + case ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP: /* The sink is probing: just sent the PROBE_TX_COMMAND and waiting for response */ + case ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP2: /* The sink is probing: timeout on first PROBE_TX_COMMAND sent a second one and waiting for response */ + switch (event) { + case ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_RESP: + if (listener_sink_state == ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP) { + /* Called from the inflight callback: first PROBE_TX_COMMAND timedout without + * response, let the callback retry the command send + */ + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP2; + } else { + /* Called from the inflight callback: second PROBE_TX_COMMAND timedout without + * response, start the TMR_RETRY and move to the correspondent state. + */ + timer_start(&stream_input_dynamic->u.milan.acmp_retry_timer, ACMP_MILAN_LISTENER_TMR_RETRY_MS); + + stream_input_dynamic->u.milan.acmp_status = ACMP_STAT_LISTENER_TALKER_TIMEOUT; + + stream_status_changed = true; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RETRY; + } + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD: + probe_tx_seq_id = stream_input_dynamic->u.milan.probe_tx_seq_id; + + rc = acmp_milan_listener_bind(entity, listener_unique_id, port_rsp, pdu, desc_rsp, &binding_restart); + + if (rc < 0 || !binding_restart) + break; + + /* stop timer TMR_NO_RESP */ + if (avdecc_inflight_cancel(entity, &acmp->inflight, probe_tx_seq_id, NULL, NULL, NULL) < 0) + os_log(LOG_ERR, "entity(%p) listener sink %u state %s event %s: Could not find an inflight with sequence_id %u\n", + entity, listener_unique_id, acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event), + stream_input_dynamic->u.milan.probe_tx_seq_id); + + /* restart ADP discovery SM */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_PROBE_TX_RESP: + // FIXME the avdecc_inflight checks only the sequence id, we should check also the controller entity id, talker entity id and index + if (avdecc_inflight_cancel(entity, &acmp->inflight, ntohs(pdu->sequence_id), NULL, NULL, NULL) < 0) { + os_log(LOG_ERR, "entity(%p) listener sink %u: Could not find an inflight with sequence_id %u\n", + entity, listener_unique_id, ntohs(pdu->sequence_id)); + rc = -1; + goto exit; + } + + if (status != ACMP_STAT_SUCCESS) { + stream_input_dynamic->u.milan.acmp_status = status; + + stream_status_changed = true; + + /* start TMR_RETRY */ + timer_start(&stream_input_dynamic->u.milan.acmp_retry_timer, ACMP_MILAN_LISTENER_TMR_RETRY_MS); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RETRY; + } else { + /* Save the received SRP params */ + acmp_milan_set_stream_input_srp_params(stream_input_dynamic, pdu); + + /* Stack connect: AVTP connect and SRP register */ + if (acmp_listener_stack_connect(entity, listener_unique_id, ntohs(pdu->flags)) != ACMP_STAT_SUCCESS) + rc = -1; + + /* start TMR_NO_TK */ + timer_start(&stream_input_dynamic->u.milan.acmp_talker_registration_timer, ACMP_MILAN_LISTENER_TMR_NO_TK_MS); + + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_COMPLETED; + stream_input_dynamic->u.milan.acmp_status = 0; + + stream_status_changed = true; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_SETTLED_NO_RSV; + } + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE: + rc = acmp_milan_send_get_rx_state_response(entity, listener_unique_id, port_rsp, desc_rsp); + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD: + /* stop the ADP discovery SM */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + /* stop TMR_NO_RESP */ + if (avdecc_inflight_cancel(entity, &acmp->inflight, stream_input_dynamic->u.milan.probe_tx_seq_id, NULL, NULL, NULL) < 0) + os_log(LOG_ERR, "entity(%p) listener sink %u state %s event %s: Could not find an inflight with sequence_id %u\n", + entity, listener_unique_id, acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event), + stream_input_dynamic->u.milan.probe_tx_seq_id); + + rc = acmp_milan_listener_unbind(entity, listener_unique_id, port_rsp, desc_rsp); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_UNBOUND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED: + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED: + /* stop TMR_NO_RESP */ + if (avdecc_inflight_cancel(entity, &acmp->inflight, stream_input_dynamic->u.milan.probe_tx_seq_id, NULL, NULL, NULL) < 0) + os_log(LOG_ERR, "entity(%p) listener sink %u state %s event %s: Could not find an inflight with sequence_id %u\n", + entity, listener_unique_id, acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event), + stream_input_dynamic->u.milan.probe_tx_seq_id); + + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_PASSIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + stream_status_changed = true; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS: + break; + + default: + os_log(LOG_ERR, "entity(%p) listener sink %u state %s: invalid event %s\n", + entity, listener_unique_id, + acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event)); + break; + } + + break; + + case ACMP_LISTENER_SINK_SM_STATE_PRB_W_RETRY: /* The sink is probing: timeout on the two PROBE_TX_COMMAND and waiting for TMR_RETRY before retrying */ + switch (event) { + case ACMP_LISTENER_SINK_SM_EVENT_TMR_RETRY: + if (stream_input_dynamic->u.milan.talker_state == ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED) { + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_PASSIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + stream_status_changed = true; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL; + } else { + /* start TMR_DELAY */ + timer_start(&stream_input_dynamic->u.milan.acmp_delay_timer, ACMP_MILAN_LISTENER_TMR_DELAY_MS); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_DELAY; + } + + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD: + rc = acmp_milan_listener_bind(entity, listener_unique_id, port_rsp, pdu, desc_rsp, &binding_restart); + + if (rc < 0 || !binding_restart) + break; + + /* stop timer TMR_RETRY */ + timer_stop(&stream_input_dynamic->u.milan.acmp_retry_timer); + + /* Restart the ADP discovery SM */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE: + rc = acmp_milan_send_get_rx_state_response(entity, listener_unique_id, port_rsp, desc_rsp); + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD: + /* stop the ADP discovery SM */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + /* stop TMR_RETRY */ + timer_stop(&stream_input_dynamic->u.milan.acmp_retry_timer); + rc = acmp_milan_listener_unbind(entity, listener_unique_id, port_rsp, desc_rsp); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_UNBOUND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED: + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED: + /* stop TMR_RETRY */ + timer_stop(&stream_input_dynamic->u.milan.acmp_retry_timer); + + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_PASSIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + stream_status_changed = true; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS: + break; + + default: + os_log(LOG_ERR, "entity(%p) listener sink %u state %s: invalid event %s\n", + entity, listener_unique_id, + acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event)); + break; + } + + break; + + case ACMP_LISTENER_SINK_SM_STATE_SETTLED_NO_RSV: /* The sink is settled (has up-to-date SRP params: from PROBE_RX_RESPONSE): waiting SRP talker attribute */ + switch (event) { + case ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_TK: + /* Stack disconnect: AVTP disconnect and SRP deregister */ + if (acmp_listener_stack_disconnect(entity, listener_unique_id) != ACMP_STAT_SUCCESS) + rc = -1; + + /* Clear SRP params */ + acmp_milan_listener_sink_srp_state_clear(stream_input_dynamic); + + if (stream_input_dynamic->u.milan.talker_state == ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED) { + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_PASSIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL; + } else { + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_ACTIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + /* start TMR_DELAY */ + timer_start(&stream_input_dynamic->u.milan.acmp_delay_timer, ACMP_MILAN_LISTENER_TMR_DELAY_MS); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_DELAY; + } + + stream_status_changed = true; + + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD: + rc = acmp_milan_listener_bind(entity, listener_unique_id, port_rsp, pdu, desc_rsp, &binding_restart); + + if (rc < 0 || !binding_restart) + break; + + /* Stack disconnect: AVTP disconnect and SRP deregister */ + if (acmp_listener_stack_disconnect(entity, listener_unique_id) != ACMP_STAT_SUCCESS) + rc = -1; + + /* Clear SRP params */ + acmp_milan_listener_sink_srp_state_clear(stream_input_dynamic); + + /* stop TMR_NO_TK */ + timer_stop(&stream_input_dynamic->u.milan.acmp_talker_registration_timer); + + /* Restart the ADP discovery state machine */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE: + rc = acmp_milan_send_get_rx_state_response(entity, listener_unique_id, port_rsp, desc_rsp); + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD: + /* Stack disconnect: AVTP disconnect and SRP deregister */ + if (acmp_listener_stack_disconnect(entity, listener_unique_id) != ACMP_STAT_SUCCESS) + rc = -1; + + /* Clear SRP params */ + acmp_milan_listener_sink_srp_state_clear(stream_input_dynamic); + + /* stop the ADP discovery SM */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + /* stop TMR_NO_TK */ + timer_stop(&stream_input_dynamic->u.milan.acmp_talker_registration_timer); + + rc |= acmp_milan_listener_unbind(entity, listener_unique_id, port_rsp, desc_rsp); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_UNBOUND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED: + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED: + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_REGISTERED: + /* stop TMR_NO_TK */ + timer_stop(&stream_input_dynamic->u.milan.acmp_talker_registration_timer); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_SETTLED_RSV_OK; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS: + break; + + default: + os_log(LOG_ERR, "entity(%p) listener sink %u state %s: invalid event %s\n", + entity, listener_unique_id, + acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event)); + break; + } + break; + + case ACMP_LISTENER_SINK_SM_STATE_SETTLED_RSV_OK: /* The sink is settled: has registered SRP talker attribute */ + switch (event) { + case ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD: + rc = acmp_milan_listener_bind(entity, listener_unique_id, port_rsp, pdu, desc_rsp, &binding_restart); + + if (rc < 0 || !binding_restart) + break; + + /* Stack disconnect: AVTP disconnect and SRP deregister */ + if (acmp_listener_stack_disconnect(entity, listener_unique_id) != ACMP_STAT_SUCCESS) + rc = -1; + + /* Clear SRP params */ + acmp_milan_listener_sink_srp_state_clear(stream_input_dynamic); + + /* Restart the ADP discovery state machine */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE: + rc = acmp_milan_send_get_rx_state_response(entity, listener_unique_id, port_rsp, desc_rsp); + break; + + case ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD: + /* Stack disconnect: AVTP disconnect and SRP deregister */ + rc = acmp_listener_stack_disconnect(entity, listener_unique_id); + + /* Clear SRP params */ + acmp_milan_listener_sink_srp_state_clear(stream_input_dynamic); + + /* stop the ADP discovery SM */ + adp_milan_listener_sink_discovery_sm(entity, listener_unique_id, ADP_MILAN_LISTENER_SINK_RESET, 0, NULL); + + rc |= acmp_milan_listener_unbind(entity, listener_unique_id, port_rsp, desc_rsp); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_UNBOUND; + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED: + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED: + break; + + case ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_UNREGISTERED: + /* Stack disconnect: AVTP disconnect and SRP deregister */ + rc = acmp_listener_stack_disconnect(entity, listener_unique_id); + + /* Clear SRP params */ + acmp_milan_listener_sink_srp_state_clear(stream_input_dynamic); + + if (stream_input_dynamic->u.milan.talker_state == ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED) { + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_PASSIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL; + } else { + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_ACTIVE; + stream_input_dynamic->u.milan.acmp_status = 0; + /* start TMR_DELAY */ + timer_start(&stream_input_dynamic->u.milan.acmp_delay_timer, ACMP_MILAN_LISTENER_TMR_DELAY_MS); + + listener_sink_state = ACMP_LISTENER_SINK_SM_STATE_PRB_W_DELAY; + } + + stream_status_changed = true; + + break; + + case ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS: + break; + + default: + os_log(LOG_ERR, "entity(%p) listener sink %u state %s: invalid event %s\n", + entity, listener_unique_id, + acmp_milan_listener_sink_state2string(listener_sink_state), acmp_milan_listener_sink_event2string(event)); + break; + } + break; + + default: + break; + } + + os_log(LOG_INFO, "entity(%p) listener sink (%u) : event %s, state from %s to %s\n", + entity, listener_unique_id, + acmp_milan_listener_sink_event2string(event), + acmp_milan_listener_sink_state2string(stream_input_dynamic->u.milan.state), acmp_milan_listener_sink_state2string(listener_sink_state)); + + stream_input_dynamic->u.milan.state = listener_sink_state; + + if (stream_status_changed) { + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_INPUT descriptor state (Probing and ACMP status) + */ + acmp_milan_listener_register_async_get_stream_info_notification(stream_input_dynamic); + } + +exit: + return rc; +} + +/** Main ACMP MILAN listener receive function. + * \return 0 on success, negative otherwise + * \param acmp pointer to the ACMP context + * \param pdu pointer to the ACMP PDU + * \param msg_type ACMP message type (8.2.1.5) + * \param status status from AVTP control header (8.2.1.6) + * \param port_id port on which the PDU is received + */ +int acmp_milan_listener_rcv(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 msg_type, u8 status, unsigned int port_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + acmp_milan_listener_sink_sm_event_t event = acmp_milan_listener_get_event(msg_type); + + if (ACMP_IS_COMMAND(msg_type)) + os_log(LOG_INFO, "acmp(%p) %s: controller(%016"PRIx64") listener(%016"PRIx64", %u) talker(%016"PRIx64", %u)\n", + acmp, acmp_milan_msgtype2string(msg_type), ntohll(pdu->controller_entity_id), + ntohll(entity->desc->entity_id), ntohs(pdu->listener_unique_id), + ntohll(pdu->talker_entity_id), ntohs(pdu->talker_unique_id)); + else + os_log(LOG_INFO, "acmp(%p) %s: controller(%016"PRIx64") listener(%016"PRIx64", %u) talker(%016"PRIx64", %u) status(%d)\n", + acmp, acmp_milan_msgtype2string(msg_type), ntohll(pdu->controller_entity_id), + ntohll(entity->desc->entity_id), ntohs(pdu->listener_unique_id), + ntohll(pdu->talker_entity_id), ntohs(pdu->talker_unique_id), status); + + + return acmp_milan_listener_sink_sm(entity, ntohs(pdu->listener_unique_id), event, pdu, status, port_id); +} + +/** ACMP MILAN listener sink event trigger function (non networking events). + * \return 0 on success, negative otherwise + * \param entity pointer to the entity struct + * \param listener_unique_id Listener unique ID + * \param event ACMP listener sink SM event + */ +int acmp_milan_listener_sink_event(struct entity *entity, u16 listener_unique_id, acmp_milan_listener_sink_sm_event_t event) +{ + return acmp_milan_listener_sink_sm(entity, listener_unique_id, event, NULL, 0, 0); +} + +/** ACMP MILAN listener sink receive saved binding params. + * \return 0 on success, negative otherwise + * \param entity pointer to the entity struct + * \param listener_unique_id Listener unique ID + * \param binding_params pointer to the genavb_msg_media_stack_bind struct containing the saved binding params + */ +int acmp_milan_listener_sink_rcv_binding_params(struct entity *entity, struct genavb_msg_media_stack_bind *binding_params) +{ + + struct stream_input_dynamic_desc *stream_input_dynamic; + + if (!binding_params) + goto err; + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, binding_params->listener_stream_index, NULL); + + if (!stream_input_dynamic) + goto err; + + /* Sanity checks */ + if (!binding_params->talker_entity_id || !binding_params->controller_entity_id) + goto err; + + /* Set the saved parameters. */ + stream_input_dynamic->controller_entity_id = htonll(binding_params->controller_entity_id); + stream_input_dynamic->talker_entity_id = htonll(binding_params->talker_entity_id); + stream_input_dynamic->talker_unique_id = htons(binding_params->talker_stream_index); + stream_input_dynamic->flags = (binding_params->started == ACMP_LISTENER_STREAM_STOPPED) ? htons(ACMP_FLAG_STREAMING_WAIT) : htons(0); + + return acmp_milan_listener_sink_sm(entity, binding_params->listener_stream_index, ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS, NULL, 0, 0); + +err: + return -1; +} + +/** Start a timer to send an async unsolicited notification after a short delay if the timer is not currently running + * \return none + * \param stream_output_dynamic, the dynamic descriptor of the stream output that triggered the notification + */ +static void acmp_milan_talker_register_async_get_stream_info_notification(struct stream_output_dynamic_desc *stream_output_dynamic) +{ + if (!timer_is_running(&stream_output_dynamic->u.milan.async_unsolicited_notification_timer)) + timer_start(&stream_output_dynamic->u.milan.async_unsolicited_notification_timer, ACMP_MILAN_TALKER_ASYNC_UNSOLICITED_NOTIFICATION_MS); +} + +/** Get a talker index matching the stream ID + * \return 0 on success, negative otherwise + * \param entity pointer to entity struct + * \param stream_id stream ID (in network order) + * \param[out] talker_unique_id pointer to variable holding the matching talker index on success. + */ +int acmp_milan_get_talker_unique_id(struct entity *entity, u64 stream_id, u16 *talker_unique_id) +{ + int i; + struct acmp_ctx *acmp = &entity->acmp; + struct stream_output_dynamic_desc *stream_output_dynamic; + + for (i = 0; i < acmp->max_talker_streams; i++) { + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, i, NULL); + if (cmp_64(&stream_output_dynamic->stream_id, &stream_id) && talker_unique_id) { + + *talker_unique_id = i; + return 0; + } + } + + return -1; +} + +/** Check if we have valid SRP params and needs to advertise talker attribute + * \return True if all SRP params are valid and we need to advertise talker attribute. + * \param stream_output_dynamic pointer to the stream_output_dynamic_desc structure + */ +static bool acmp_milan_talker_has_valid_srp_params(struct stream_output_dynamic_desc *stream_output_dynamic) +{ + bool ret = false; + + /* Per MILAN Specification v1.2 (5.3.7.2 and 4.3.3.1): We need to declare the SRP talker, once SRP params are valid: + * - If we already received a new valid MAAP address + * - we have received a PROBE_TX_COMMAND in the last 15 sec or registered a listener attribute + */ + if (!is_invalid_mac_addr(stream_output_dynamic->stream_dest_mac) && + (stream_output_dynamic->u.milan.probe_tx_valid || + (stream_output_dynamic->u.milan.srp_listener_status != NO_LISTENER))) { + + ret = true; + } + + return ret; +} + +/** Save the SRP parameters of the Talker that is withdrawing + * \return none + * \param stream_output_dynamic pointer to the stream_output_dynamic_desc structure + * \param valid validity flag for the SRP params, if valid save them otherwise reset them + */ +static void acmp_milan_set_stream_output_withdraw_srp_params(struct stream_output_dynamic_desc *stream_output_dynamic, bool valid) +{ + if (valid) { + os_memcpy(&stream_output_dynamic->u.milan.srp_talker_withdraw_params.stream_id, &stream_output_dynamic->stream_id, 8); + stream_output_dynamic->u.milan.srp_talker_withdraw_params.vlan_id = stream_output_dynamic->stream_vlan_id; + os_memcpy(&stream_output_dynamic->u.milan.srp_talker_withdraw_params.stream_dest_mac, &stream_output_dynamic->stream_dest_mac, 6); + } else { + os_memset(&stream_output_dynamic->u.milan.srp_talker_withdraw_params, 0, sizeof(struct srp_talker_params)); + } +} + +/** Check if we are advertising the same talker attributes we previously withdrawn + * \return True if the talker attributes are the same as the ones last advertised. + * \param stream_output_dynamic pointer to the stream_output_dynamic_desc structure + */ +static bool acmp_milan_redeclaring_same_talker(struct stream_output_dynamic_desc *stream_output_dynamic) +{ + bool ret = false; + + if (!os_memcmp(&stream_output_dynamic->u.milan.srp_talker_withdraw_params.stream_id, &stream_output_dynamic->stream_id, 8) && + !os_memcmp(&stream_output_dynamic->u.milan.srp_talker_withdraw_params.stream_dest_mac, &stream_output_dynamic->stream_dest_mac, 6) && + (stream_output_dynamic->u.milan.srp_talker_withdraw_params.vlan_id == stream_output_dynamic->stream_vlan_id)) { + ret = true; + } + + return ret; +} + +/** Checks Talker status and perform stack connection/disconnection if needed. + * \return ACMP status + * \param entity pointer to the entity context + * \param talker_unique_id valid talker unique id (in host order) + */ +static u8 acmp_milan_talker_update(struct entity *entity, u16 talker_unique_id) +{ + struct stream_output_dynamic_desc *stream_output_dynamic; + struct avdecc_ctx *avdecc = entity->avdecc; + struct stream_descriptor *stream_output; + u8 status = ACMP_STAT_SUCCESS; + unsigned int port_id; + + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + if (!stream_output_dynamic) { + os_log(LOG_ERR, "entity(%p) talker unique id (%u): unable to get stream output dynamic descriptor\n", + entity, talker_unique_id); + status = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + if (!stream_output) { + os_log(LOG_ERR, "acmp(%p) cannot find descriptor type %d index %u\n", + &entity->acmp, AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id); + status = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + port_id = ntohs(stream_output->avb_interface_index); + + /* Per MILAN Specification v1.2 4.3.3.1: If no Valid SRP params, withdraw any previously declared talker. + * (i.e avoid declaring talker attributes for Streams that are not requested by any Listener) + */ + if (!acmp_milan_talker_has_valid_srp_params(stream_output_dynamic) && stream_output_dynamic->u.milan.talker_declared) { + if (acmp_talker_withdraw(avdecc, port_id, stream_output_dynamic) < 0) { + os_log(LOG_ERR, "acmp(%p) cannot disconnect SRP\n", &entity->acmp); + status = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + /* Save deregistered talker attributes */ + acmp_milan_set_stream_output_withdraw_srp_params(stream_output_dynamic, true); + + stream_output_dynamic->u.milan.talker_declared = false; + + /* start the srp talker withdraw timer */ + stream_output_dynamic->u.milan.srp_talker_withdraw_in_progress = true; + timer_start(&stream_output_dynamic->u.milan.srp_talker_withdraw_timer, ACMP_MILAN_TALKER_TMR_SRP_WITHDRAW_MS); + } + + /* Per MILAN Specification v1.2 5.3.7.3 Streaming state: Stream is running when declaring + * a Talker Advertise attribute and receiving a Listener Ready or Listener Ready Failed + */ + if ((!stream_output_dynamic->u.milan.talker_declared || (stream_output_dynamic->u.milan.srp_listener_status == NO_LISTENER)) + && stream_output_dynamic->u.milan.avtp_connected) { + if (acmp_talker_avtp_stack_disconnect(avdecc, port_id, talker_unique_id, stream_output_dynamic) < 0) { + os_log(LOG_ERR, "acmp(%p) cannot disconnect AVTP\n", &entity->acmp); + status = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + stream_output_dynamic->u.milan.avtp_connected = false; + } + + /* Valid SRP params, SRP declare if no previous withdraw in progress then check for AVTP connect */ + if (acmp_milan_talker_has_valid_srp_params(stream_output_dynamic)) { + if (!stream_output_dynamic->u.milan.talker_declared) { + /* Per MILAN Specification v1.2 5.3.7.5: If we have a running SRP talker withdraw timer, + * we should wait for the 2 LeaveALL period before re-declaring the talker attribute with different parameters. + */ + if (stream_output_dynamic->u.milan.srp_talker_withdraw_in_progress) { + if (!acmp_milan_redeclaring_same_talker(stream_output_dynamic)) + goto exit; + + stream_output_dynamic->u.milan.srp_talker_withdraw_in_progress = false; + timer_stop(&stream_output_dynamic->u.milan.srp_talker_withdraw_timer); + } + + if (acmp_talker_declare(avdecc, port_id, stream_output, stream_output_dynamic, 0) < 0) { + os_log(LOG_ERR, "acmp(%p) cannot connect SRP\n", &entity->acmp); + status = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + stream_output_dynamic->u.milan.talker_declared = true; + + /* Reset withdraw SRP talker params */ + acmp_milan_set_stream_output_withdraw_srp_params(stream_output_dynamic, false); + } + + /* Per MILAN Specification v1.2 5.3.7.3 Streaming state: Stream is running when declaring a Talker Advertise attribute + * and receiving a Listener Ready or Listener Ready Failed attribute + */ + if (ACMP_MILAN_TALKER_HAS_ACTIVE_LISTENER(stream_output_dynamic) && !stream_output_dynamic->u.milan.avtp_connected + && stream_output_dynamic->u.milan.talker_declared) { + /* If Listener attributes are registered as ACTIVE or ACTIVE_AND_FAILED, connect AVTP too */ + if (acmp_talker_avtp_stack_connect(entity, port_id, talker_unique_id, stream_output, stream_output_dynamic, 0) < 0) { + os_log(LOG_ERR, "acmp(%p) cannot connect AVTP\n", &entity->acmp); + status = ACMP_STAT_TALKER_MISBEHAVING; + goto exit; + } + + stream_output_dynamic->u.milan.avtp_connected = true; + } + } + +exit: + return status; +} + +/** Updates the SRP talker declaration for the specific stream output + * \return none + * \param entity pointer to entity struct + * \param talker_unique_id talker_unique_id (host order) + * \param ipc_talker_declaration_status ipc status message for talker stream declaration + */ +void acmp_milan_talker_update_declaration(struct entity *entity, u16 talker_unique_id, struct genavb_msg_talker_declaration_status *ipc_talker_declaration_status) +{ + genavb_talker_stream_declaration_type_t declaration_type = ipc_talker_declaration_status->declaration_type; + struct stream_output_dynamic_desc *stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + + if (!stream_output_dynamic) { + os_log(LOG_ERR, "entity(%p) talker unique id (%u): unable to get stream output dynamic descriptor\n", + entity, talker_unique_id); + + return; + } + + stream_output_dynamic->u.milan.srp_talker_declaration_type = declaration_type; + + if (declaration_type == TALKER_FAILED) { + os_memcpy(stream_output_dynamic->u.milan.failure.bridge_id, ipc_talker_declaration_status->failure.bridge_id, 8); + stream_output_dynamic->u.milan.failure.failure_code = ipc_talker_declaration_status->failure.failure_code; + } else { + os_memset(stream_output_dynamic->u.milan.failure.bridge_id, 0, 8); + stream_output_dynamic->u.milan.failure.failure_code = 0; + } + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_OUTPUT descriptor state (MSRP Talker attribute declaration state and MSRP failure information) + */ + acmp_milan_talker_register_async_get_stream_info_notification(stream_output_dynamic); +} + +/** Updates the SRP talker stream status for the specific stream output + * \return none + * \param entity pointer to entity struct + * \param talker_unique_id talker_unique_id (host order) + * \param ipc_talker_status ipc status message for talker stream + */ +void acmp_milan_talker_update_status(struct entity *entity, u16 talker_unique_id, struct genavb_msg_talker_status *ipc_talker_status) +{ + genavb_talker_stream_status_t status = ipc_talker_status->status; + struct stream_output_dynamic_desc *stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + + if (!stream_output_dynamic) { + os_log(LOG_ERR, "entity(%p) talker unique id (%u): unable to get stream output dynamic descriptor\n", + entity, talker_unique_id); + + return; + } + + stream_output_dynamic->u.milan.srp_listener_status = status; + + /* If the listener withdraws its attribute (NO_LISTENER), check if we need to stop declaring the talker attribute. */ + acmp_milan_talker_update(entity, talker_unique_id); + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_OUTPUT descriptor state (MSRP Listener attribute registration state) + */ + acmp_milan_talker_register_async_get_stream_info_notification(stream_output_dynamic); +} + +/** Main ACMP MILAN talker receive function. + * \return 0 on success, negative otherwise + * \param acmp pointer to the ACMP context + * \param pdu pointer to the ACMP PDU + * \param msg_type ACMP message type (8.2.1.5) + * \param status status from AVTP control header (8.2.1.6) + * \param port_id port on which the PDU is received + */ +int acmp_milan_talker_rcv(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 msg_type, u8 status, unsigned int port_id) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct net_tx_desc *desc_rsp; + struct acmp_pdu *acmp_rsp; + struct stream_descriptor *stream_output; + struct stream_output_dynamic_desc *stream_output_dynamic; + int rc = 0; + u16 unique_id = ntohs(pdu->talker_unique_id); + u64 empty_listener_entity_id = 0; + u8 status_rsp = ACMP_STAT_SUCCESS; + struct avdecc_port *port_rsp = &avdecc->port[port_id]; + + os_log(LOG_INFO, "acmp(%p) %s: controller(%016"PRIx64") talker(%016"PRIx64", %u) listener(%016"PRIx64", %u)\n", + acmp, acmp_milan_msgtype2string(msg_type), ntohll(pdu->controller_entity_id), + ntohll(entity->desc->entity_id), unique_id, + ntohll(pdu->listener_entity_id), ntohs(pdu->listener_unique_id)); + + /* Prepare the response PDU */ + desc_rsp = acmp_net_tx_init(acmp, port_rsp, pdu, true); + if (!desc_rsp) { + rc = -1; + goto exit; + } + + acmp_rsp = (struct acmp_pdu *)((char *)NET_DATA_START(desc_rsp) + OFFSET_TO_ACMP); + + if (!acmp_talker_unique_valid(acmp, unique_id)) { + if (ACMP_IS_TALKER_COMMAND(msg_type) && msg_type != ACMP_GET_TX_CONNECTION_COMMAND) + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_TALKER_UNKNOWN_ID); + else + net_tx_free(desc_rsp); + + goto exit; + } + + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, unique_id, NULL); + + switch (msg_type) { + case ACMP_PROBE_TX_COMMAND: + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, unique_id, NULL); + + if (port_id != ntohs(stream_output->avb_interface_index)) { + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_INCOMPATIBLE_REQUEST); + goto exit; + } + + /* Restart PROBE_TX timer */ + timer_restart(&stream_output_dynamic->u.milan.probe_tx_reception_timer, ACMP_MILAN_TALKER_TMR_PROBE_TX_RECEPTION_MS); + + stream_output_dynamic->u.milan.probe_tx_valid = true; + + if (!acmp_milan_talker_has_valid_srp_params(stream_output_dynamic)) { + + status_rsp = ACMP_STAT_TALKER_DEST_MAC_FAIL; + } else { + /* We have valid SRP params and we received a PROBE_TX_COMAND, check (No SRP talker withdrawal in progress) + * if we need to do a talker connect. */ + status_rsp = acmp_milan_talker_update(entity, unique_id); + + acmp_rsp->flags &= ~htons(ACMP_FLAG_REGISTERING_FAILED); + acmp_rsp->flags |= pdu->flags & htons(ACMP_FLAG_STREAMING_WAIT | ACMP_FLAG_FAST_CONNECT); + + acmp_rsp->connection_count = htons(0); + + acmp_talker_copy_common_params(acmp_rsp, stream_output_dynamic); + } + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, status_rsp); + break; + + case ACMP_DISCONNECT_TX_COMMAND: + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_SUCCESS); + break; + + case ACMP_GET_TX_STATE_COMMAND: + + copy_64(&acmp_rsp->listener_entity_id, &empty_listener_entity_id); + acmp_rsp->listener_unique_id = 0; + + if (stream_output_dynamic->u.milan.srp_talker_declaration_type != NO_TALKER_DECLARATION) + acmp_talker_copy_common_params(acmp_rsp, stream_output_dynamic); + + + if (stream_output_dynamic->u.milan.srp_listener_status == FAILED_LISTENER) + acmp_rsp->flags |= htons(ACMP_FLAG_REGISTERING_FAILED); + + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_SUCCESS); + break; + + case ACMP_GET_TX_CONNECTION_COMMAND: + rc = acmp_send_rsp(acmp, port_rsp, desc_rsp, msg_type + 1, ACMP_STAT_NOT_SUPPORTED); + break; + + default: + os_log(LOG_ERR, "entity(%p) message type (%x) not supported\n", entity, msg_type); + net_tx_free(desc_rsp); + rc = -1; + + break; + } + +exit: + return rc; +} + +/** Delete the allocated MAAP range for all talkers in the entity. + * \return 0 on success, negative otherwise. + * \param entity pointer to entity struct + */ +static int acmp_milan_talkers_maap_stop(struct entity *entity) +{ + struct stream_output_dynamic_desc *stream_output_dynamic; + struct avdecc_ctx *avdecc = entity->avdecc; + struct stream_descriptor *stream_output; + struct acmp_ctx *acmp = &entity->acmp; + struct ipc_desc *desc; + int i, rc = 0; + + for (i = 0; i < acmp->max_talker_streams; i++) { + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, i, NULL); + + if (!stream_output_dynamic->u.milan.maap_started) + continue; + + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, i, NULL); + + desc = ipc_alloc(&avdecc->ipc_tx_maap, sizeof(struct genavb_msg_maap_delete)); + if (desc) { + desc->type = GENAVB_MSG_MAAP_DELETE_RANGE; + desc->len = sizeof(struct genavb_msg_maap_delete); + desc->flags = 0; + + desc->u.maap_delete.port_id = ntohs(stream_output->avb_interface_index); + desc->u.maap_delete.range_id = CFG_ACMP_DEFAULT_MAAP_BASE_RANGE_ID + i; + + rc = ipc_tx(&avdecc->ipc_tx_maap, desc); + if (rc < 0) { + os_log(LOG_ERR, "ipc_tx() failed(%d)\n", rc); + ipc_free(&avdecc->ipc_tx_maap, desc); + + goto exit; + } + + stream_output_dynamic->u.milan.maap_started = false; + + } else { + os_log(LOG_ERR, "ipc_alloc() failed\n"); + rc = -1; + goto exit; + } + } + +exit: + return rc; +} + +/** Starts the MAAP address allocation for all talkers in the entity. + * \return 0 on success, negative otherwise. + * \param entity pointer to entity struct + */ +int acmp_milan_talkers_maap_start(struct entity *entity) +{ + struct stream_output_dynamic_desc *stream_output_dynamic; + struct avdecc_ctx *avdecc = entity->avdecc; + struct acmp_ctx *acmp = &entity->acmp; + struct stream_descriptor *stream_output; + struct ipc_desc *desc; + int i, rc = 0; + + for (i = 0; i < acmp->max_talker_streams; i++) { + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, i, NULL); + + if (stream_output_dynamic->u.milan.maap_started) + continue; + + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, i, NULL); + + desc = ipc_alloc(&avdecc->ipc_tx_maap, sizeof(struct genavb_msg_maap_create)); + if (desc) { + desc->type = GENAVB_MSG_MAAP_CREATE_RANGE; + desc->len = sizeof(struct genavb_msg_maap_create); + desc->flags = 0; + + desc->u.maap_create.flag = 0; + desc->u.maap_create.port_id = ntohs(stream_output->avb_interface_index); + desc->u.maap_create.range_id = CFG_ACMP_DEFAULT_MAAP_BASE_RANGE_ID + i; + desc->u.maap_create.count = CFG_ACMP_DEFAULT_MAAP_COUNT_PER_RANGE; + + rc = ipc_tx(&avdecc->ipc_tx_maap, desc); + if (rc < 0) { + os_log(LOG_ERR, "ipc_tx() failed(%d)\n", rc); + ipc_free(&avdecc->ipc_tx_maap, desc); + + goto exit; + } + + stream_output_dynamic->u.milan.maap_started = true; + } else { + os_log(LOG_ERR, "ipc_alloc() failed\n"); + rc = -1; + goto exit; + } + } + +exit: + return rc; +} + +/** Called when a MAAP conflict was reported for the address range. + * \return none + * \param entity pointer to entity struct + * \param port_id port on which the conflict was reported + * \param range_id id of the range reporting a conflict before restarting + * \param base_address base address for the range + * \param count number of mac addresses in the range + */ +void acmp_milan_talker_maap_conflict(struct entity *entity, avb_u16 port_id, avb_u32 range_id, avb_u8 *base_address, avb_u16 count) +{ + struct stream_output_dynamic_desc *stream_output_dynamic; + struct stream_descriptor *stream_output; + struct acmp_ctx *acmp = &entity->acmp; + u8 invalid_mac_addr[6] = {0}; + unsigned int stream_index; + + stream_index = range_id - CFG_ACMP_DEFAULT_MAAP_BASE_RANGE_ID; + + if ((range_id < CFG_ACMP_DEFAULT_MAAP_BASE_RANGE_ID) || (stream_index >= acmp->max_talker_streams)) + goto exit; + + if (count != CFG_ACMP_DEFAULT_MAAP_COUNT_PER_RANGE) { + os_log(LOG_ERR, "entity(%p) stream index(%u) port(%u) range(%u): received incorrect address count (%u) != (%u)\n", + entity, stream_index, port_id, range_id, count, CFG_ACMP_DEFAULT_MAAP_COUNT_PER_RANGE); + goto exit; + } + + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, stream_index, NULL); + + /* MAAP range ids are per port, process only indications on the right one */ + if (ntohs(stream_output->avb_interface_index) != port_id) + goto exit; + + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, stream_index, NULL); + + /* MAAP conflict indication for the range id should contain the same address previously reported valid + * This usecase should never happen: but if so, invalid the address anyway. + */ + if (os_memcmp(stream_output_dynamic->stream_dest_mac, base_address, 6)) { + os_log(LOG_ERR, "entity(%p) stream index(%u) port(%u) range(%u): received incorrect base address \ + (%02x:%02x:%02x:%02x:%02x:%02x) != (%02x:%02x:%02x:%02x:%02x:%02x)\n", + entity, stream_index, port_id, range_id, + base_address[0], base_address[1], base_address[2], base_address[3], base_address[4], base_address[5], + stream_output_dynamic->stream_dest_mac[0], stream_output_dynamic->stream_dest_mac[1], stream_output_dynamic->stream_dest_mac[2], + stream_output_dynamic->stream_dest_mac[3], stream_output_dynamic->stream_dest_mac[4], stream_output_dynamic->stream_dest_mac[5]); + } + + /* Invalidate the destination MAC address for the stream */ + os_memcpy(stream_output_dynamic->stream_dest_mac, invalid_mac_addr, 6); + + /* Disconnect the talker stream, if already connected */ + acmp_milan_talker_update(entity, stream_index); + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_OUTPUT descriptor state (MAAP stream_dest_mac) + */ + acmp_milan_talker_register_async_get_stream_info_notification(stream_output_dynamic); + +exit: + return; +} + +/** Called when a MAAP valid/success was reported for the requested address range. + * \return none + * \param entity pointer to entity struct + * \param port_id port on which the range was allocated + * \param range_id id of the allocated range + * \param base_address base address for the range + * \param count number of mac addresses in the range + */ +void acmp_milan_talker_maap_valid(struct entity *entity, avb_u16 port_id, avb_u32 range_id, avb_u8 *base_address, avb_u16 count) +{ + struct stream_output_dynamic_desc *stream_output_dynamic; + struct stream_descriptor *stream_output; + struct acmp_ctx *acmp = &entity->acmp; + unsigned int stream_index; + + stream_index = range_id - CFG_ACMP_DEFAULT_MAAP_BASE_RANGE_ID; + + if ((range_id < CFG_ACMP_DEFAULT_MAAP_BASE_RANGE_ID) || (stream_index >= acmp->max_talker_streams)) + goto exit; + + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, stream_index, NULL); + + /* MAAP range ids are per port, process only indications on the right one */ + if (ntohs(stream_output->avb_interface_index) != port_id) + goto exit; + + if (count != CFG_ACMP_DEFAULT_MAAP_COUNT_PER_RANGE) { + os_log(LOG_ERR, "entity(%p) stream index(%u) port(%u) range(%u): received incorrect address count (%u) != (%u)\n", + entity, stream_index, port_id, range_id, count, CFG_ACMP_DEFAULT_MAAP_COUNT_PER_RANGE); + goto exit; + } + + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, stream_index, NULL); + + /* Save the stream destination MAC address. */ + os_memcpy(stream_output_dynamic->stream_dest_mac, base_address, 6); + + /* we have now a valid MAAP address, check if we need/can do a talker connect. */ + acmp_milan_talker_update(entity, stream_index); + + /* Register send of an asynchronous GET_STREAM_INFO unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the STREAM_OUTPUT descriptor state (MAAP stream_dest_mac) + */ + acmp_milan_talker_register_async_get_stream_info_notification(stream_output_dynamic); + +exit: + return; +} + +static void acmp_milan_srp_talker_withdraw_timer_handler(void *data) +{ + struct stream_output_dynamic_desc *stream_output_dynamic = (struct stream_output_dynamic_desc *) data; + struct entity *entity = stream_output_dynamic->u.milan.entity; + + stream_output_dynamic->u.milan.srp_talker_withdraw_in_progress = false; + + /* 2 LeaveALL timer period has passed since last talker withdraw, check if we need to do a talker connect. */ + acmp_milan_talker_update(entity, stream_output_dynamic->u.milan.unique_id); +} + +static void acmp_talker_probe_tx_reception_timer_handler(void *data) +{ + struct stream_output_dynamic_desc *stream_output_dynamic = (struct stream_output_dynamic_desc *) data; + struct entity *entity = stream_output_dynamic->u.milan.entity; + + stream_output_dynamic->u.milan.probe_tx_valid = false; + + /* Per MILAN Specification v1.2 (4.3.3.1 Talker attribute declaration): + * If after 15 seconds of last received PROBE_TX_COMMAND and No listener attribute registered, withdraw + * the SRP talker attribute (if already declared). + */ + acmp_milan_talker_update(entity, stream_output_dynamic->u.milan.unique_id); +} + +static void acmp_milan_talker_async_unsolicited_notification_timer_handler(void *data) +{ + struct stream_output_dynamic_desc *stream_output_dynamic = (struct stream_output_dynamic_desc *)data; + struct entity *entity = stream_output_dynamic->u.milan.entity; + + aecp_aem_send_async_unsolicited_notification(&entity->aecp, AECP_AEM_CMD_GET_STREAM_INFO, AEM_DESC_TYPE_STREAM_OUTPUT, stream_output_dynamic->u.milan.unique_id); +} + +__init static int acmp_milan_talker_init_timers(struct entity *entity, u16 talker_unique_id) +{ + struct stream_output_dynamic_desc *stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + + stream_output_dynamic->u.milan.probe_tx_reception_timer.func = acmp_talker_probe_tx_reception_timer_handler; + stream_output_dynamic->u.milan.probe_tx_reception_timer.data = stream_output_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &stream_output_dynamic->u.milan.probe_tx_reception_timer, 0, + ACMP_MILAN_TALKER_TMR_PROBE_TX_RECEPTION_GRANULARITY_MS) < 0) + goto err_probe_tx_timer; + + /* Init the SRP talker withdraw timer */ + stream_output_dynamic->u.milan.srp_talker_withdraw_timer.func = acmp_milan_srp_talker_withdraw_timer_handler; + stream_output_dynamic->u.milan.srp_talker_withdraw_timer.data = stream_output_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &stream_output_dynamic->u.milan.srp_talker_withdraw_timer, 0, + ACMP_MILAN_TALKER_TMR_SRP_WITHDRAW_GRANULARITY_MS) < 0) + goto err_srp_talker_withdraw_timer; + + stream_output_dynamic->u.milan.async_unsolicited_notification_timer.func = acmp_milan_talker_async_unsolicited_notification_timer_handler; + stream_output_dynamic->u.milan.async_unsolicited_notification_timer.data = stream_output_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &stream_output_dynamic->u.milan.async_unsolicited_notification_timer, 0, + ACMP_MILAN_TALKER_ASYNC_UNSOLICITED_NOTIFICATION_GRANULARITY_MS) < 0) + goto err_notification_timer; + + return 0; + +err_notification_timer: + timer_destroy(&stream_output_dynamic->u.milan.srp_talker_withdraw_timer); +err_srp_talker_withdraw_timer: + timer_destroy(&stream_output_dynamic->u.milan.probe_tx_reception_timer); +err_probe_tx_timer: + return -1; +} + +__exit static void acmp_milan_talker_exit_timers(struct entity *entity, u16 talker_unique_id) +{ + struct stream_output_dynamic_desc *stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_STREAM_OUTPUT, talker_unique_id, NULL); + + timer_destroy(&stream_output_dynamic->u.milan.probe_tx_reception_timer); + timer_destroy(&stream_output_dynamic->u.milan.srp_talker_withdraw_timer); + timer_destroy(&stream_output_dynamic->u.milan.async_unsolicited_notification_timer); +} + +__init int acmp_milan_init(struct acmp_ctx *acmp) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct stream_input_dynamic_desc *stream_input_dynamic; + struct stream_output_dynamic_desc *stream_output_dynamic; + struct stream_descriptor *stream_output; + struct avb_interface_descriptor *avb_itf; + int i, j; + + for (i = 0; i < acmp->max_listener_streams; i++) { + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + if (!stream_input_dynamic) { + os_log(LOG_ERR, "entity(%p) listener unique id (%u): unable to get stream input dynamic descriptor\n", + entity, i); + goto err_listener_sinks_init; + } + + stream_input_dynamic->u.milan.state = ACMP_LISTENER_SINK_SM_STATE_UNBOUND; + stream_input_dynamic->u.milan.srp_stream_status = NO_TALKER; + stream_input_dynamic->u.milan.srp_state = ACMP_LISTENER_SINK_SRP_STATE_NOT_REGISTERING; + stream_input_dynamic->u.milan.acmp_status = 0; + stream_input_dynamic->u.milan.probing_status = ACMP_PROBING_STATUS_DISABLED; + stream_input_dynamic->u.milan.entity = entity; + stream_input_dynamic->u.milan.unique_id = i; + + acmp_milan_set_stream_input_binding_params(stream_input_dynamic, NULL); + + if (acmp_milan_listener_sink_init_timers(entity, i) < 0) { + os_log(LOG_ERR, "entity(%p) listener unique id (%u): unable to init timers\n", + entity, i); + goto err_listener_sinks_init; + } + } + + for (j = 0; j < acmp->max_talker_streams; j++) { + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, j, NULL); + if (!stream_output_dynamic) { + os_log(LOG_ERR, "entity(%p) talker unique id (%u): unable to get stream output dynamic descriptor\n", + entity, j); + goto err_talkers_init; + } + + stream_output_dynamic->u.milan.entity = entity; + stream_output_dynamic->u.milan.unique_id = j; + stream_output_dynamic->u.milan.srp_talker_declaration_type = NO_TALKER_DECLARATION; + stream_output_dynamic->u.milan.srp_listener_status = NO_LISTENER; + + stream_output_dynamic->presentation_time_offset = sr_class_max_transit_time(SR_CLASS_A); + + stream_output_dynamic->stream_class = SR_CLASS_A; + /* Always use the Default Vlan ID */ + stream_output_dynamic->stream_vlan_id = MRP_DEFAULT_VID; + + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, j, NULL); + if (!stream_output) { + os_log(LOG_ERR, "entity(%p) talker unique id (%u): unable to get stream output descriptor\n", + entity, i); + goto err_talkers_init; + } + + avb_itf = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE, ntohs(stream_output->avb_interface_index), NULL); + if (!avb_itf) { + os_log(LOG_ERR, "entity(%p) talker unique id (%u): unable to get avb interface with index %u\n", + entity, j, ntohs(stream_output->avb_interface_index)); + goto err_talkers_init; + } + + os_memset(&stream_output_dynamic->stream_id, 0, sizeof(stream_output_dynamic->stream_id)); + os_memcpy(&stream_output_dynamic->stream_id, avb_itf->mac_address, 6); + *(((u16 *)&stream_output_dynamic->stream_id) + 3) = stream_output->descriptor_index; + + acmp_milan_set_stream_output_withdraw_srp_params(stream_output_dynamic, false); + + stream_output_dynamic->u.milan.srp_talker_withdraw_in_progress = false; + stream_output_dynamic->u.milan.talker_declared = false; + stream_output_dynamic->u.milan.avtp_connected = false; + stream_output_dynamic->u.milan.maap_started = false; + stream_output_dynamic->u.milan.probe_tx_valid = false; + + if (acmp_milan_talker_init_timers(entity, j) < 0) { + os_log(LOG_ERR, "entity(%p) talker unique id (%u): unable to init timers\n", + entity, j); + goto err_talkers_init; + } + + } + + return 0; + +err_talkers_init: + while (j--) + acmp_milan_talker_exit_timers(entity, j); + +err_listener_sinks_init: + while (i--) + acmp_milan_listener_sink_exit_timers(entity, i); + + return -1; +} + +__exit int acmp_milan_exit(struct acmp_ctx *acmp) +{ + struct entity *entity = container_of(acmp, struct entity, acmp); + struct avdecc_ctx *avdecc = entity->avdecc; + int i; + + for (i = 0; i < acmp->max_listener_streams; i++) { + struct stream_input_dynamic_desc *stream_input_dynamic; + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + + /* Disconnect (AVTP disconnect and SRP deregister) settled (connected) listener streams. */ + if (stream_input_dynamic && ACMP_MILAN_IS_LISTENER_SINK_SETTLED(stream_input_dynamic)) + acmp_listener_stack_disconnect(entity, i); + + acmp_milan_listener_sink_exit_timers(entity, i); + } + + for (i = 0; i < acmp->max_talker_streams; i++) { + struct stream_output_dynamic_desc *stream_output_dynamic; + struct stream_descriptor *stream_output; + unsigned int port_id; + + stream_output_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, i, NULL); + stream_output = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, i, NULL); + + if (stream_output_dynamic && stream_output) { + port_id = ntohs(stream_output->avb_interface_index); + + if (stream_output_dynamic->u.milan.talker_declared) { + acmp_talker_withdraw(avdecc, port_id, stream_output_dynamic); + stream_output_dynamic->u.milan.talker_declared = false; + } + + if (stream_output_dynamic->u.milan.avtp_connected) { + acmp_talker_avtp_stack_disconnect(avdecc, port_id, stream_output_dynamic->u.milan.unique_id, stream_output_dynamic); + stream_output_dynamic->u.milan.avtp_connected = false; + } + } + + acmp_milan_talker_exit_timers(entity, i); + } + + /* Delete the maap ranges if we have talker streams. */ + if (acmp->max_talker_streams) + acmp_milan_talkers_maap_stop(entity); + + return 0; +} diff --git a/avdecc/acmp_milan.h b/avdecc/acmp_milan.h new file mode 100644 index 0000000..a287127 --- /dev/null +++ b/avdecc/acmp_milan.h @@ -0,0 +1,92 @@ +/* + * Copyright 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ACMP Milan common definitions +*/ + +#ifndef _ACMP_MILAN_H_ +#define _ACMP_MILAN_H_ + +#include "common/acmp.h" +#include "genavb/control_srp.h" +#include "genavb/control_avdecc.h" + +typedef enum { + ACMP_LISTENER_SINK_SM_EVENT_UNKNOWN = -1, + ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_RESP = 0, + ACMP_LISTENER_SINK_SM_EVENT_TMR_RETRY, + ACMP_LISTENER_SINK_SM_EVENT_TMR_DELAY, + ACMP_LISTENER_SINK_SM_EVENT_TMR_NO_TK, + ACMP_LISTENER_SINK_SM_EVENT_RCV_BIND_RX_CMD, + ACMP_LISTENER_SINK_SM_EVENT_RCV_PROBE_TX_RESP, + ACMP_LISTENER_SINK_SM_EVENT_RCV_GET_RX_STATE, + ACMP_LISTENER_SINK_SM_EVENT_RCV_UNBIND_RX_CMD, + ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED, + ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED, + ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_REGISTERED, + ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_UNREGISTERED, + ACMP_LISTENER_SINK_SM_EVENT_SAVED_BINDING_PARAMS +} acmp_milan_listener_sink_sm_event_t; + +typedef enum { + ACMP_LISTENER_SINK_SM_STATE_UNBOUND = 0, + ACMP_LISTENER_SINK_SM_STATE_PRB_W_AVAIL, + ACMP_LISTENER_SINK_SM_STATE_PRB_W_DELAY, + ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP, + ACMP_LISTENER_SINK_SM_STATE_PRB_W_RESP2, + ACMP_LISTENER_SINK_SM_STATE_PRB_W_RETRY, + ACMP_LISTENER_SINK_SM_STATE_SETTLED_NO_RSV, + ACMP_LISTENER_SINK_SM_STATE_SETTLED_RSV_OK +} acmp_milan_listener_sink_sm_state_t; + +typedef enum { + ACMP_LISTENER_SINK_SRP_STATE_NOT_REGISTERING = 0, + ACMP_LISTENER_SINK_SRP_STATE_REGISTERING, +} acmp_milan_listener_sink_srp_state_t; + +/** + * ACMP Probing Status codes about the probing status of a STREAM_INPUT + * Follows definition of section 6.8.6 from MILAN Discovery, connection and control specification for talkers and listeners rev1.1a */ +typedef enum{ + ACMP_PROBING_STATUS_DISABLED = 0, /**< The sink is not probing because it is not bound. */ + ACMP_PROBING_STATUS_PASSIVE = 1, /**< The sink is probing passively. It waits until the bound talker has been discovered. */ + ACMP_PROBING_STATUS_ACTIVE = 2, /**< The sink is probing actively. It is querying the stream parameters to the talker. */ + ACMP_PROBING_STATUS_COMPLETED = 3, /**< The sink is not probing because it is settled. */ +} acmp_milan_probing_status_t; + +#define ACMP_MILAN_IS_LISTENER_SINK_BOUND(stream_input_dynamic) (((stream_input_dynamic)->u.milan.state != ACMP_LISTENER_SINK_SM_STATE_UNBOUND)) +#define ACMP_MILAN_IS_LISTENER_SINK_SETTLED(stream_input_dynamic) (((stream_input_dynamic)->u.milan.state == ACMP_LISTENER_SINK_SM_STATE_SETTLED_NO_RSV) || \ + ((stream_input_dynamic)->u.milan.state == ACMP_LISTENER_SINK_SM_STATE_SETTLED_RSV_OK)) + +#define ACMP_MILAN_TALKER_HAS_ACTIVE_LISTENER(stream_output_dynamic) (((stream_output_dynamic)->u.milan.srp_listener_status == ACTIVE_LISTENER) || \ + ((stream_output_dynamic)->u.milan.srp_listener_status == ACTIVE_AND_FAILED_LISTENERS)) + +struct avdecc_ctx; +struct acmp_ctx; +struct entity; + +int acmp_milan_talker_rcv(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 msg_type, u8 status, unsigned int port_id); +int acmp_milan_listener_sink_event(struct entity *entity, u16 listener_unique_id, acmp_milan_listener_sink_sm_event_t event); +int acmp_milan_listener_rcv(struct acmp_ctx *acmp, struct acmp_pdu *pdu, u8 msg_type, u8 status, unsigned int port_id); +int acmp_milan_get_listener_unique_id(struct entity *entity, u64 stream_id, u16 *listener_unique_id); +int acmp_milan_get_talker_unique_id(struct entity *entity, u64 stream_id, u16 *talker_unique_id); +void acmp_milan_listener_srp_state_sm(struct entity *entity, u16 listener_unique_id, struct genavb_msg_listener_status *ipc_listener_status); +void acmp_milan_talker_update_status(struct entity *entity, u16 talker_unique_id, struct genavb_msg_talker_status *ipc_talker_status); +void acmp_milan_talker_update_declaration(struct entity *entity, u16 talker_unique_id, struct genavb_msg_talker_declaration_status *ipc_talker_declaration_status); +int acmp_milan_init(struct acmp_ctx *acmp); +int acmp_milan_exit(struct acmp_ctx *acmp); +int acmp_milan_get_command_timeout_ms(acmp_message_type_t msg_type); +bool acmp_milan_is_stream_running(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index); +int acmp_milan_start_streaming(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index); +int acmp_milan_stop_streaming(struct entity *entity, u16 stream_desc_type, u16 stream_desc_index); +const char *acmp_milan_msgtype2string(acmp_message_type_t msg_type); +int acmp_milan_talkers_maap_start(struct entity *entity); +void acmp_milan_talker_maap_conflict(struct entity *entity, avb_u16 port_id, avb_u32 range_id, avb_u8 *base_address, avb_u16 count); +void acmp_milan_talker_maap_valid(struct entity *entity, avb_u16 port_id, avb_u32 range_id, avb_u8 *base_address, avb_u16 count); +int acmp_milan_listener_sink_rcv_binding_params(struct entity *entity, struct genavb_msg_media_stack_bind *binding_params); + +#endif /* _ACMP_MILAN_H_ */ diff --git a/avdecc/adp.c b/avdecc/adp.c new file mode 100644 index 0000000..a48fb3f --- /dev/null +++ b/avdecc/adp.c @@ -0,0 +1,738 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ADP common code + @details Handles ADP stack +*/ + +#include "os/stdlib.h" + +#include "common/log.h" +#include "common/timer.h" +#include "common/net.h" + +#include "genavb/aem.h" + +#include "adp.h" +#include "aem.h" + +#include "avdecc.h" +#include "avdecc_ieee.h" + +static const u8 adp_dst_mac[6] = MC_ADDR_AVDECC_ADP_ACMP; + +static const char *adp_msgtype2string(u8 msg_type) +{ + switch (msg_type) { + case2str(ADP_ENTITY_AVAILABLE); + case2str(ADP_ENTITY_DEPARTING); + case2str(ADP_ENTITY_DISCOVER); + case2str(ADP_ENTITY_NOTFOUND); + default: + return (char *) "Unknown adp message type"; + } +} + +void adp_update(struct adp_ctx *adp) +{ + struct entity *entity = container_of(adp, struct entity, adp); + + if (entity->milan_mode) + adp_milan_advertise_start(adp); + else + adp_ieee_advertise_start(adp); +} + +/** Sends an ADP DISCOVER PDU to the network. + * \return 0 on success, -1 if error + * \param disc Pointer to the discovery context + * \param entity_id Entity ID to discover. Set to to NULL or point to a zero value to discover all entities. + */ +int adp_discovery_send_packet(struct adp_discovery_ctx *disc, u8 *entity_id) +{ + struct avdecc_port *port = discovery_to_avdecc_port(disc); + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + struct adp_pdu *adp_hdr; + void *pdu; + struct net_tx_desc *desc; + + desc = net_tx_alloc(&port->net_tx, ADP_NET_DATA_SIZE); + if (!desc) { + os_log(LOG_ERR, "avdecc(%p) couldn't allocate message type (%x)\n", avdecc, ADP_ENTITY_DISCOVER); + return -1; + } + + pdu = NET_DATA_START(desc); + + desc->len += net_add_eth_header(pdu, adp_dst_mac, ETHERTYPE_AVTP); + desc->len += avdecc_add_common_header((char *)pdu + desc->len, AVTP_SUBTYPE_ADP, ADP_ENTITY_DISCOVER, ADP_PDU_LEN, 0); + + adp_hdr = (struct adp_pdu *)((char *)pdu + desc->len); + + os_memset(adp_hdr, 0, sizeof(struct adp_pdu)); + if (entity_id) + copy_64(&adp_hdr->entity_id, entity_id); + + desc->len += sizeof(struct adp_pdu); + + avdecc_net_tx(port, desc); + + os_log(LOG_INFO, "avdecc(%p) message type (%x)\n", avdecc, ADP_ENTITY_DISCOVER); + + return 0; +} + +/** Sends an ADP PDU to the network. + * \return 0 on success, -1 if error + * \param adp pointer to the adp context + * \param message_type advertise or discovery message type + * \param port_id port on which to send the packet + */ +int adp_advertise_send_packet(struct adp_ctx *adp, u8 message_type, unsigned int port_id) +{ + struct adp_pdu *adp_hdr; + void *pdu; + struct net_tx_desc *desc; + struct avb_interface_dynamic_desc *avb_itf_dynamic; + struct entity *entity = container_of(adp, struct entity, adp); + struct avdecc_port *port; + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + + if (!avb_itf_dynamic) { + os_log(LOG_ERR, "adp(%p) Can not get dynamic avb interface descriptor for index (%u)\n", &entity->adp, port_id); + return -1; + } + + if (message_type != ADP_ENTITY_AVAILABLE && message_type != ADP_ENTITY_DEPARTING) + return -1; + + port = &entity->avdecc->port[port_id]; + + desc = net_tx_alloc(&port->net_tx, ADP_NET_DATA_SIZE); + if (!desc) { + os_log(LOG_ERR, "adp(%p) Cannot alloc tx descriptor\n", &entity->adp); + return -1; + } + + pdu = NET_DATA_START(desc); + + desc->len += net_add_eth_header(pdu, adp_dst_mac, ETHERTYPE_AVTP); + desc->len += avdecc_add_common_header((char *)pdu + desc->len, AVTP_SUBTYPE_ADP, message_type, ADP_PDU_LEN, + (message_type == ADP_ENTITY_AVAILABLE) ? (entity->valid_time / 2) : 0); + + adp_hdr = (struct adp_pdu *)((char *)pdu + desc->len); + + adp_hdr->entity_id = entity->desc->entity_id; + adp_hdr->entity_model_id = entity->desc->entity_model_id; + adp_hdr->entity_capabilities = entity->desc->entity_capabilities; + adp_hdr->talker_stream_sources = entity->desc->talker_stream_sources; + adp_hdr->talker_capabilities = entity->desc->talker_capabilities; + adp_hdr->listener_stream_sinks = entity->desc->listener_stream_sinks; + adp_hdr->listener_capabilities = entity->desc->listener_capabilities; + adp_hdr->controller_capabilities = entity->desc->controller_capabilities; + + adp_hdr->available_index = entity->desc->available_index; + + adp_hdr->gptp_grandmaster_id = avb_itf_dynamic->gptp_grandmaster_id; + adp_hdr->gptp_domain_number = 0; //FIXME + adp_hdr->identity_control_index = 0; //FIXME + adp_hdr->interface_index = port_id; + adp_hdr->association_id = entity->desc->association_id; + adp_hdr->rsvd0 = 0; + adp_hdr->rsvd1 = 0; + + desc->len += sizeof(struct adp_pdu); + + avdecc_net_tx(port, desc); + + os_log(LOG_INFO, "entity(%p) port(%u) entity id: %016"PRIx64" message type %s\n", + entity, port_id, ntohll(entity->desc->entity_id), adp_msgtype2string(message_type)); + + return 0; +} + +static void adp_discover_rcv(struct avdecc_ctx *avdecc, struct adp_pdu *pdu, unsigned int port_id) +{ + int i; + + if (!pdu->entity_id) { + for (i = 0; i < avdecc->num_entities; i++) { + if (entity_ready(avdecc->entities[i]) && avdecc_entity_port_valid(avdecc->entities[i], port_id)) { + if (avdecc->milan_mode) + adp_milan_advertise_sm(avdecc->entities[i], port_id, ADP_MILAN_ADV_RCV_ADP_DISCOVER); + else + adp_ieee_advertise_interface_sm(avdecc->entities[i], port_id, ADP_INTERFACE_ADV_EVENT_RCV_DISCOVER); + } + } + } else { + struct entity *entity = avdecc_get_entity(avdecc, pdu->entity_id); + if (entity && avdecc_entity_port_valid(entity, port_id)) { + if (avdecc->milan_mode) + adp_milan_advertise_sm(entity, port_id, ADP_MILAN_ADV_RCV_ADP_DISCOVER); + else + adp_ieee_advertise_interface_sm(entity, port_id, ADP_INTERFACE_ADV_EVENT_RCV_DISCOVER); + } + } + + os_log(LOG_DEBUG, "avdecc(%p) port(%u) done\n", avdecc, port_id); +} + +/** Main ADP receive function. + * Handles both advertise and discovery (6.2.5.3 and 6.2.6.4). + * \return 0 on success, negative otherwise + * \param port pointer to the AVDECC port + * \param pdu pointer to the ADP PDU + * \param msg_type ADP message type (6.2.1.5) + * \param valid_time valid_time from AVTP control header (6.2.1.6) + * \param mac_src pointer to the source MAC address + */ +int adp_net_rx(struct avdecc_port *port, struct adp_pdu *pdu, u8 msg_type, u8 valid_time, u8 *mac_src) +{ + struct entity *entity; + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + + os_log(LOG_DEBUG, "port(%u) message type %s\n", port->port_id, adp_msgtype2string(msg_type)); + + switch (msg_type) { + case ADP_ENTITY_AVAILABLE: + adp_discovery_update(&port->discovery, pdu, valid_time, mac_src); + + if (avdecc->milan_mode) { + entity = avdecc_get_local_listener(avdecc, port->port_id); + if (entity) + adp_milan_listener_rcv(entity, msg_type, pdu, valid_time); + } + + break; + + case ADP_ENTITY_DEPARTING: + adp_discovery_remove(&port->discovery, pdu); + + if (avdecc->milan_mode) { + entity = avdecc_get_local_listener(avdecc, port->port_id); + if (entity) + adp_milan_listener_rcv(entity, msg_type, pdu, valid_time); + } + + break; + + case ADP_ENTITY_DISCOVER: + adp_discover_rcv(avdecc, pdu, port->port_id); + + break; + + default: + break; + } + + return 0; +} + +/* ADP common controller discovery */ +int adp_run_discovery(struct adp_discovery_ctx *disc, struct adp_pdu *pdu, u8 valid_time) +{ + return 0; +} + +static struct entity_discovery *adp_discovery_find(struct adp_discovery_ctx *disc, u64 entity_id) +{ + int i; + + for (i = 0; i < disc->max_entities_discovery; i++) + if (disc->entities[i].in_use && (entity_id == disc->entities[i].info.entity_id)) + return &disc->entities[i]; + + return NULL; +} + +/** Find discovered entity by ID on a specific port + * \return entity_discovery if the entity has been discovered on network or NULL otherwise. + * \param avdecc Pointer to the avdecc context + * \param port_id Avdecc port / interface index on which to look for the discovered entity + * \param entity_id Entity ID of the entity to search for (in network order) + */ +struct entity_discovery *adp_find_entity_discovery(struct avdecc_ctx *avdecc, unsigned int port_id, u64 entity_id) +{ + struct adp_discovery_ctx *disc; + + if (port_id >= avdecc->port_max || !avdecc->port[port_id].initialized) + return NULL; + + disc = &avdecc->port[port_id].discovery; + + return adp_discovery_find(disc, entity_id); +} + +/** Find discovered entity by ID on a any port + * \return entity_discovery if the entity has been discovered on network or NULL otherwise. + * \param avdecc Pointer to the avdecc context + * \param entity_id Entity ID of the entity to search for (in network order) + * \param num_interfaces Number of interfaces on which to search for the entity. + */ +struct entity_discovery *adp_find_entity_discovery_any(struct avdecc_ctx *avdecc, u64 entity_id, unsigned int num_interfaces) +{ + struct entity_discovery *entity_disc = NULL; + unsigned int port_num; + + for (port_num = 0; port_num < num_interfaces; port_num++) { + entity_disc = adp_find_entity_discovery(avdecc, port_num, entity_id); + /* For an entity discovered on multiple interfaces, always return the entity on the first port. + * FIXME return the most recent discovered one + */ + if (entity_disc) + break; + } + + return entity_disc; +} + +static unsigned int adp_get_total_discovered_entities(struct entity *entity) +{ + int i; + unsigned int total_discovered = 0, num_interfaces; + struct adp_discovery_ctx *disc; + + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + /* loop over all discovery contexts on all the entity interfaces */ + for (i = 0; i < num_interfaces; i++) { + disc = &entity->avdecc->port[i].discovery; + total_discovered += disc->num_discovered_entities; + } + + return total_discovered; +} + +/** Send an ADP IPC message + * \return 0 on success or -1 otherwise. + * \param entity Pointer to the entity struct. + * \param ipc IPC to send the message through. + * \param msg_type Type of ADP message to send (See section 6.2.1.5 of IEEE 1722.1-2013). + * \param info Entity information to include in the message. Will be ignored if NULL. + */ +static int adp_ipc_tx(struct entity *entity, struct ipc_tx *ipc, unsigned int ipc_dst, u8 msg_type, struct entity_info *info) +{ + struct ipc_desc *tx_desc; + int rc = 0; + + os_log(LOG_DEBUG, "entity(%p) Sending ADP IPC message_type (%d) entity_info(%p)\n", entity, msg_type, info); + tx_desc = ipc_alloc(ipc, sizeof(struct genavb_adp_msg)); + if (tx_desc) { + tx_desc->dst = ipc_dst; + tx_desc->type = GENAVB_MSG_ADP; + tx_desc->len = sizeof(struct genavb_adp_msg); + + tx_desc->u.adp_msg.msg_type = msg_type; + tx_desc->u.adp_msg.total = adp_get_total_discovered_entities(entity); + + if (info) + os_memcpy(&tx_desc->u.adp_msg.info, info, sizeof(struct entity_info)); + else { + os_memset(&tx_desc->u.adp_msg.info, 0, sizeof(struct entity_info)); + os_log(LOG_DEBUG, "entity(%p) Trying to send an IPC ADP message_type(%d) but no entity info available\n", entity, msg_type); + } + + rc = ipc_tx(ipc, tx_desc); + if (rc < 0) { + if (rc == -IPC_TX_ERR_QUEUE_FULL) + os_log(LOG_ERR, "entity(%p) ipc_tx() failed (%d), ADP message type (%d)\n", entity, rc, msg_type); + ipc_free(ipc, tx_desc); + rc = -1; + } + } else { + os_log(LOG_ERR, "entity(%p) ipc_alloc() failed\n", entity); + rc = -1; + } + + return rc; +} + +static struct entity_discovery * adp_discovery_get(struct adp_discovery_ctx *disc) +{ + int i; + struct entity_discovery *entity_disc = NULL; + + for (i = 0; i < disc->max_entities_discovery; i++) + if (!disc->entities[i].in_use) + entity_disc = &disc->entities[i]; + + if (entity_disc) { + entity_disc->in_use = 1; + entity_disc->disc->num_discovered_entities++; + } + else + os_log(LOG_ERR, "disc(%p) no more discovery entries\n", disc); + + return entity_disc; +} + +static void inline adp_discovery_put(struct entity_discovery *entity_disc) +{ + int i; + struct avdecc_port *port = discovery_to_avdecc_port(entity_disc->disc); + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + struct entity *entity; + + entity = avdecc_get_local_controller(avdecc, port->port_id); + if (entity) { + if (adp_ipc_tx(entity, &avdecc->ipc_tx_controller, IPC_DST_ALL, ADP_ENTITY_DEPARTING, &entity_disc->info) < 0) + os_log(LOG_ERR, "entity(%p) adp_ipc_tx() failed msg_type(%d) entity_info(%p)\n", + entity, ADP_ENTITY_DEPARTING, &entity_disc->info); + } + + if (!avdecc->milan_mode) { + for (i = 0; i < avdecc->num_entities; i++) + if (entity_ready(avdecc->entities[i])) + acmp_ieee_listener_talker_left(&avdecc->entities[i]->acmp, entity_disc->info.entity_id); + } + + entity_disc->in_use = 0; + entity_disc->disc->num_discovered_entities--; + os_memset(&entity_disc->info, 0 , sizeof(struct entity_info)); +} + +static void adp_discovery_update_entity(struct adp_discovery_ctx *disc, struct entity_info *info, struct adp_pdu *pdu, u8 *mac_addr) +{ + struct avdecc_port *port = discovery_to_avdecc_port(disc); + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + struct entity *entity = avdecc_get_local_controller(avdecc, port->port_id); + bool send_ipc = false; + + if ((info->entity_id != pdu->entity_id) || + (info->entity_model_id != pdu->entity_model_id) || + (info->entity_capabilities != pdu->entity_capabilities) || + (info->talker_stream_sources != pdu->talker_stream_sources) || + (info->talker_capabilities != pdu->talker_capabilities) || + (info->listener_stream_sinks != pdu->listener_stream_sinks) || + (info->listener_capabilities != pdu->listener_capabilities) || + (info->controller_capabilities != pdu->controller_capabilities) || + (info->gptp_grandmaster_id != pdu->gptp_grandmaster_id) || + (info->gptp_domain_number != pdu->gptp_domain_number) || + (info->identity_control_index != pdu->identity_control_index) || + (info->interface_index != pdu->interface_index) || + (info->association_id != pdu->association_id) || + os_memcmp(info->mac_addr, mac_addr, 6) ) { + + /* The discovered entity has changed, notify the controller if existing. */ + if (entity) + send_ipc = true; + } + + info->entity_id = pdu->entity_id; + info->entity_model_id = pdu->entity_model_id; + info->entity_capabilities = pdu->entity_capabilities; + info->talker_stream_sources = pdu->talker_stream_sources; + info->talker_capabilities = pdu->talker_capabilities; + info->listener_stream_sinks = pdu->listener_stream_sinks; + info->listener_capabilities = pdu->listener_capabilities; + info->controller_capabilities = pdu->controller_capabilities; + info->gptp_grandmaster_id = pdu->gptp_grandmaster_id; + info->gptp_domain_number = pdu->gptp_domain_number; + info->identity_control_index = pdu->identity_control_index; + info->interface_index = pdu->interface_index; + info->available_index = pdu->available_index; + info->association_id = pdu->association_id; + os_memcpy(info->mac_addr, mac_addr, 6); + os_memcpy(info->local_mac_addr, port->local_physical_mac, 6); + + if (send_ipc) { + if (adp_ipc_tx(entity, &avdecc->ipc_tx_controller, IPC_DST_ALL, ADP_ENTITY_AVAILABLE, info) < 0) + os_log(LOG_ERR, "entity(%p) adp_ipc_tx() failed msg_type(%d) entity_info(%p)\n", entity, ADP_ENTITY_AVAILABLE, info); + } +} + +void adp_discovery_update(struct adp_discovery_ctx *disc, struct adp_pdu *pdu, u8 valid_time, u8 *mac_src) +{ + struct entity_discovery *entity_disc; + struct avdecc_port *port = discovery_to_avdecc_port(disc); + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + bool gptp_gmid_changed = false; + + os_log(LOG_INFO, "port(%u) entity : %016"PRIx64", capabilities : %x, association ID: %"PRIx64" gPTP GM ID : %016"PRIx64", valid time : %d s\n", + port->port_id, ntohll(pdu->entity_id), pdu->entity_capabilities, ntohll(pdu->association_id), ntohll(pdu->gptp_grandmaster_id), valid_time * 2); + + entity_disc = adp_discovery_find(disc, pdu->entity_id); + + /* New entity */ + if (!entity_disc) { + entity_disc = adp_discovery_get(disc); + + if (!entity_disc) + return; + + os_memset(&entity_disc->info, 0, sizeof(struct entity_info)); + } + else { + /* Check if entity power-cycled */ + if (ntohl(pdu->available_index) <= ntohl(entity_disc->info.available_index)) { + os_log(LOG_INFO, "port(%u) entity : %016"PRIx64" power-cycled\n", + port->port_id, ntohll(pdu->entity_id)); + + /* Put entity as if it departed... */ + timer_stop(&entity_disc->timeout); + adp_discovery_put(entity_disc); + + /* .. and get a new one */ + entity_disc = adp_discovery_get(disc); + if (!entity_disc) + return; + } + } + + /* Check if grand master ID changed for this entity */ + if (!cmp_64(&pdu->gptp_grandmaster_id, &entity_disc->info.gptp_grandmaster_id)) { + gptp_gmid_changed = true; + os_log(LOG_INFO, "port(%u) gPTP GM ID change for entity : %016"PRIx64", former : %016"PRIx64", new : %016"PRIx64"\n", + port->port_id, ntohll(pdu->entity_id), ntohll(entity_disc->info.gptp_grandmaster_id), ntohll(pdu->gptp_grandmaster_id)); + } + + adp_discovery_update_entity(disc, &entity_disc->info, pdu, mac_src); + + timer_stop(&entity_disc->timeout); + /* Per IEEE1722.1-2013 6.2.1.6: received valid_time field in the PDU is in units of 2s */ + timer_start(&entity_disc->timeout, max(1, valid_time * 2) * MS_PER_S); + + if (!avdecc->milan_mode) + avdecc_ieee_discovery_update(avdecc, disc, entity_disc, gptp_gmid_changed); + + return; +} + +/** Removes a discovered entity. + * Stops the associated timeout timer if needed. + * \return 0 on success, negative otherwise + * \param disc pointer to the discovery context + * \param pdu pointer to the ADP PDU + */ +void adp_discovery_remove(struct adp_discovery_ctx *disc, struct adp_pdu *pdu) +{ + struct entity_discovery *entity_disc; + struct avdecc_port *port = discovery_to_avdecc_port(disc); + + entity_disc = adp_discovery_find(disc, pdu->entity_id); + + if (entity_disc) { + os_log(LOG_INFO, "port(%u) entity remove: %016"PRIx64"\n", port->port_id, ntohll(entity_disc->info.entity_id)); + + timer_stop(&entity_disc->timeout); + adp_discovery_put(entity_disc); + } +} + +/** Main ADP IPC receive function. + * \return 0 in all cases (error cases handled inside the function). + * \param entity Pointer to the entity struct. + * \param adp_msg Pointer to the received IPC ADP message. + * \param len Length of the received IPC message payload. + * \param ipc IPC the message was received through. + */ +int adp_ipc_rx(struct entity *entity, struct ipc_adp_msg *adp_msg, u32 len, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + struct entity_discovery *entity_disc = NULL; + struct avdecc_ctx *avdecc = entity->avdecc; + struct adp_discovery_ctx *disc; + int i, port_num, sent = 0; + int rc = 0; + unsigned int num_interfaces; + + if (len < sizeof(struct ipc_adp_msg)) { + os_log(LOG_ERR, "entity(%p) Invalid IPC ADP message size (%d instead of expected %zd)\n", + entity, len, sizeof(struct ipc_adp_msg)); + return -1; + } + + os_log(LOG_DEBUG, "entity(%p) ADP message_type (%d)\n", entity, adp_msg->msg_type); + + switch (adp_msg->msg_type) { + case ADP_ENTITY_DISCOVER: + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + /* loop over all discovery contexts on all the entity interfaces */ + for (port_num = 0; port_num < num_interfaces; port_num++) { + disc = &avdecc->port[port_num].discovery; + + for (i = 0; i < disc->max_entities_discovery; i++) { + if (disc->entities[i].in_use) { + rc = adp_ipc_tx(entity, ipc, ipc_dst, ADP_ENTITY_AVAILABLE, &disc->entities[i].info); + if (rc < 0) { + os_log(LOG_ERR, "entity(%p) adp_ipc_tx() failed msg_type(%d) entity_info(%p)\n", + entity, ADP_ENTITY_AVAILABLE, &disc->entities[i].info); + break; + } + else + sent++; + } + } + } + + if (!sent) { + rc = adp_ipc_tx(entity, ipc, ipc_dst, ADP_ENTITY_NOTFOUND, NULL); + if (rc < 0) + os_log(LOG_ERR, "entity(%p) adp_ipc_tx() failed msg_type(%d) entity_info(%p)\n", + entity, ADP_ENTITY_NOTFOUND, NULL); + } + + if (sent != adp_get_total_discovered_entities(entity)) { /* should never happen */ + os_log(LOG_ERR, "entity(%p) inconsistent number of total discovered entities (%d discovered but %d reported)\n", + entity, sent, adp_get_total_discovered_entities(entity)); + } + + break; + + case ADP_ENTITY_AVAILABLE: + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + /* loop over all discovery contexts on all the entity interfaces */ + for (port_num = 0; port_num < num_interfaces; port_num++) { + disc = &avdecc->port[port_num].discovery; + + entity_disc = adp_discovery_find(disc, adp_msg->info.entity_id); + if (entity_disc) { + rc = adp_ipc_tx(entity, ipc, ipc_dst, ADP_ENTITY_AVAILABLE, &entity_disc->info); + if (rc < 0) + os_log(LOG_ERR, "entity(%p) adp_ipc_tx() failed msg_type(%d) entity_info(%p)\n", + entity, ADP_ENTITY_AVAILABLE, &entity_disc->info); + } + } + + if (!entity_disc) { + rc = adp_ipc_tx(entity, ipc, ipc_dst, ADP_ENTITY_NOTFOUND, NULL); + if (rc < 0) + os_log(LOG_ERR, "entity(%p) adp_ipc_tx() failed msg_type(%d) entity_info(%p)\n", + entity, ADP_ENTITY_NOTFOUND, NULL); + } + + break; + + default: + os_log(LOG_ERR, "entity(%p) Received ADP message from application with unknown msg_type(%d)\n", + entity, adp_msg->msg_type); + + rc = adp_ipc_tx(entity, ipc, ipc_dst, ADP_ENTITY_NOTFOUND, NULL); + if (rc < 0) + os_log(LOG_ERR, "entity(%p) adp_ipc_tx() failed msg_type(%d) entity_info(%p)\n", + entity, ADP_ENTITY_NOTFOUND, NULL); + break; + } + + return 0; +} + +/** Discovery timeout handles. + * The discovered entity entry is removed as no advertise has been received + * since the time defined by the entity valid_time (6.2.1.6). + * \return 0 on success, negative otherwise + * \param data pointer to the timeout context data + */ +void adp_discovery_timeout(void *data) +{ + struct entity_discovery *entity_disc = (struct entity_discovery *)data; + struct avdecc_port *port = discovery_to_avdecc_port(entity_disc->disc); + + os_log(LOG_INFO, "port(%u) entity timeout: %016"PRIx64"\n", port->port_id, ntohll(entity_disc->info.entity_id)); + + adp_discovery_put(entity_disc); +} + +__init unsigned int adp_discovery_data_size(unsigned int max_entities_discovery) +{ + return max_entities_discovery * sizeof(struct entity_discovery); +} + +__init int adp_discovery_init(struct adp_discovery_ctx *disc, void *data, struct avdecc_config *cfg) +{ + int i, j; + struct avdecc_port *port = discovery_to_avdecc_port(disc); + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + + disc->entities = (struct entity_discovery *)data; + disc->max_entities_discovery = cfg->max_entities_discovery; + disc->num_discovered_entities = 0; + + if (!port->initialized) + goto exit; + + for (i = 0; i < disc->max_entities_discovery; i++) { + disc->entities[i].timeout.func = adp_discovery_timeout; + disc->entities[i].timeout.data = (void *) &disc->entities[i]; + + if (timer_create(avdecc->timer_ctx, &disc->entities[i].timeout, 0, 2 * MS_PER_S) < 0) { + os_log(LOG_CRIT, "disc(%p) timer_create failed\n", disc); + goto err; + } + disc->entities[i].disc = disc; + } + + if (adp_discovery_send_packet(disc, NULL) < 0) + goto err; + + os_log(LOG_INIT, "port(%u) disc(%p) done\n", port->port_id, disc); + +exit: + return 0; + +err: + for (j = 0; j < i; j++) + timer_destroy(&disc->entities[j].timeout); + + return -1; +} + +__exit void adp_discovery_exit(struct adp_discovery_ctx *disc) +{ + int i; + struct avdecc_port *port = discovery_to_avdecc_port(disc); + + if (!port->initialized) + return; + + for (i = 0; i < disc->max_entities_discovery; i++) + timer_destroy(&disc->entities[i].timeout); + + os_log(LOG_INIT, "disc(%p) done\n", disc); + +} + +__init int adp_init(struct adp_ctx *adp) +{ + struct entity *entity = container_of(adp, struct entity, adp); + + if (!entity->milan_mode) { + if (adp_ieee_advertise_init(&adp->ieee.advertise) < 0) + goto err_adv; + + } else { + if (adp_milan_advertise_init(adp) < 0) + goto err_adv; + + if (adp_milan_listener_sink_discovery_init(adp) < 0) + goto err_disc; + } + + return 0; + +err_disc: + adp_milan_advertise_exit(adp); + +err_adv: + return -1; +} + +__exit void adp_exit(struct adp_ctx *adp) +{ + struct entity *entity = container_of(adp, struct entity, adp); + + if (!entity->milan_mode) { + adp_ieee_advertise_exit(&adp->ieee.advertise); + + } else { + adp_milan_advertise_exit(adp); + adp_milan_listener_sink_discovery_exit(adp); + } +} diff --git a/avdecc/adp.h b/avdecc/adp.h new file mode 100644 index 0000000..33efb22 --- /dev/null +++ b/avdecc/adp.h @@ -0,0 +1,77 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2019, 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ADP common defitions +*/ + +#ifndef _ADP_H_ +#define _ADP_H_ + +#include "common/types.h" +#include "common/ipc.h" +#include "common/adp.h" +#include "adp_ieee.h" +#include "adp_milan.h" + +/** + * Discovered entity main context + */ +struct entity_discovery { + struct entity_info info; + struct timer timeout; + int in_use; + struct adp_discovery_ctx *disc; +}; + +typedef enum { + ADP_DISC_WAITING = 0, + ADP_DISC_DISCOVER, + ADP_DISC_AVAILABLE, + ADP_DISC_DEPARTING, + ADP_DISC_TIMEOUT +} adp_discovery_states; + +/** + * ADP common controller discovery + */ +struct adp_discovery_ctx { + int num_discovered_entities; + unsigned int max_entities_discovery; + struct entity_discovery *entities; /* Array of the discovered entities */ +}; + +struct adp_ctx { + struct { + struct adp_ieee_advertise_entity_ctx advertise; + } ieee; +}; + +#define discovery_to_avdecc_port(disc) container_of(disc, struct avdecc_port, discovery) + +struct avdecc_port; + +int adp_init(struct adp_ctx *adp); +void adp_exit(struct adp_ctx *adp); + +void adp_update(struct adp_ctx *adp); + +/* ADP common controller discovery */ +int adp_discovery_init(struct adp_discovery_ctx *disc, void *data, struct avdecc_config *cfg); +void adp_discovery_exit(struct adp_discovery_ctx *disc); +unsigned int adp_discovery_data_size(unsigned int max_entities_discovery); +void adp_discovery_update(struct adp_discovery_ctx *disc, struct adp_pdu *pdu, u8 valid_time, u8 *mac_src); +void adp_discovery_remove(struct adp_discovery_ctx *disc, struct adp_pdu *pdu); + +int adp_discovery_send_packet(struct adp_discovery_ctx *disc, u8 *entity_id); +int adp_advertise_send_packet(struct adp_ctx *adp, u8 message_type, unsigned int port_id); +int adp_net_rx(struct avdecc_port *port, struct adp_pdu *pdu, u8 msg_type, u8 valid_time, u8 *mac_src); +int adp_ipc_rx(struct entity *entity, struct ipc_adp_msg *adp_msg, u32 len, struct ipc_tx *ipc, unsigned int ipc_dst); +struct entity_discovery *adp_find_entity_discovery(struct avdecc_ctx *avdecc, unsigned int port_id, u64 entity_id); +struct entity_discovery *adp_find_entity_discovery_any(struct avdecc_ctx *avdecc, u64 entity_id, unsigned int num_interfaces); + +#endif /* _ADP_H_ */ diff --git a/avdecc/adp_ieee.c b/avdecc/adp_ieee.c new file mode 100644 index 0000000..297ccdd --- /dev/null +++ b/avdecc/adp_ieee.c @@ -0,0 +1,439 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2020-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ADP common code + @details Handles ADP stack +*/ + +#include "os/stdlib.h" + +#include "common/log.h" +#include "common/timer.h" +#include "common/net.h" + +#include "genavb/aem.h" + +#include "adp.h" +#include "aem.h" + +#include "avdecc.h" + +#define ADP_IEEE_ADV_TMR_DELAY_MS_MIN 100 + +static const char *adp_ieee_advertise_entity_event2string(adp_ieee_advertise_entity_event_t event) +{ + switch (event) { + case2str(ADP_ENTITY_ADV_EVENT_BEGIN); + case2str(ADP_ENTITY_ADV_EVENT_ADVERTISE); + case2str(ADP_ENTITY_ADV_EVENT_REANNOUNCE_TIMEOUT); + case2str(ADP_ENTITY_ADV_EVENT_DELAY_TIMEOUT); + case2str(ADP_ENTITY_ADV_EVENT_TERMINATE); + case2str(ADP_ENTITY_ADV_EVENT_RUN); + default: + return (char *) "Unknown ADP advertise entity event"; + } +} + +static const char *adp_ieee_advertise_entity_state2string(adp_ieee_advertise_entity_state_t state) +{ + switch (state) { + case2str(ADP_ENTITY_ADV_NOT_STARTED); + case2str(ADP_ENTITY_ADV_WAITING); + case2str(ADP_ENTITY_ADV_ADVERTISE); + case2str(ADP_ENTITY_ADV_DELAY); + case2str(ADP_ENTITY_ADV_RUN); + default: + return (char *) "Unknown ADP advertise entity state"; + } +} + +static const char *adp_ieee_advertise_interface_state2string(adp_ieee_advertise_interface_state_t state) +{ + switch (state) { + case2str(ADP_INTERFACE_ADV_NOT_STARTED); + case2str(ADP_INTERFACE_ADV_WAITING); + case2str(ADP_INTERFACE_ADV_ADVERTISE); + case2str(ADP_INTERFACE_ADV_DEPARTING); + case2str(ADP_INTERFACE_ADV_RECEIVED_DISCOVER); + case2str(ADP_INTERFACE_ADV_UPDATE_GM); + case2str(ADP_INTERFACE_ADV_LINK_DOWN); + default: + return (char *) "Unknown ADP advertise interface state"; + } +} + +static const char *adp_ieee_advertise_interface_event2string(adp_ieee_advertise_interface_event_t event) +{ + switch (event) { + case2str(ADP_INTERFACE_ADV_EVENT_BEGIN); + case2str(ADP_INTERFACE_ADV_EVENT_ADVERTISE); + case2str(ADP_INTERFACE_ADV_EVENT_RCV_DISCOVER); + case2str(ADP_INTERFACE_ADV_EVENT_GM_CHANGE); + case2str(ADP_INTERFACE_ADV_EVENT_LINK_UP); + case2str(ADP_INTERFACE_ADV_EVENT_LINK_DOWN); + case2str(ADP_INTERFACE_ADV_EVENT_RUN); + default: + return (char *) "Unknown ADP advertise interface event"; + } +} + +void adp_ieee_advertise_start(struct adp_ctx *adp) +{ + struct entity *entity = container_of(adp, struct entity, adp); + + adp_ieee_advertise_entity_sm(entity, ADP_ENTITY_ADV_EVENT_BEGIN); +} + +/** + * Advertise entity state machine (6.2.4.3) + * \return 0 on success, negative otherwise + * \param entity pointer to entity context + * \param event an adp_ieee_advertise_entity_event_t event + */ +int adp_ieee_advertise_entity_sm(struct entity *entity, adp_ieee_advertise_entity_event_t event) +{ + adp_ieee_advertise_entity_state_t state; + struct adp_ieee_advertise_entity_ctx *adv = &entity->adp.ieee.advertise; + unsigned int num_interfaces, port_num; + + num_interfaces = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE); + +start: + state = adv->state; + + switch (state) { + case ADP_ENTITY_ADV_NOT_STARTED: + switch (event) { + case ADP_ENTITY_ADV_EVENT_BEGIN: + entity->desc->available_index = htonl(0); + + for (port_num = 0; port_num < num_interfaces; port_num++) + adp_ieee_advertise_interface_sm(entity, port_num, ADP_INTERFACE_ADV_EVENT_BEGIN); + + timer_start(&adv->delay_timer, random_range(ADP_IEEE_ADV_TMR_DELAY_MS_MIN, max(1, (entity->valid_time / 5)) * MS_PER_S)); + + adv->state = ADP_ENTITY_ADV_DELAY; + break; + + default: + break; + } + break; + + case ADP_ENTITY_ADV_DELAY: + switch (event) { + case ADP_ENTITY_ADV_EVENT_DELAY_TIMEOUT: + + adv->state = ADP_ENTITY_ADV_ADVERTISE; + break; + + case ADP_ENTITY_ADV_EVENT_TERMINATE: + timer_stop(&adv->delay_timer); + + adv->state = ADP_ENTITY_ADV_NOT_STARTED; + break; + + default: + break; + } + break; + + case ADP_ENTITY_ADV_ADVERTISE: + for (port_num = 0; port_num < num_interfaces; port_num++) + adp_ieee_advertise_interface_sm(entity, port_num, ADP_INTERFACE_ADV_EVENT_ADVERTISE); + + /* Per IEEE1722.1-2013 6.2.1.6: + * entity->valid_time is the configured valid_time in units of seconds + * and we need to send advertisement every 1/4 that period + * (which is 1/2 valid_time status field, in units of 2s, in the sent ENTITY_AVAILABLE) + */ + timer_start(&adv->reannounce_timer, max(1, (entity->valid_time / 4)) * MS_PER_S); + + adv->state = ADP_ENTITY_ADV_WAITING; //UCT + break; + + case ADP_ENTITY_ADV_WAITING: + switch (event) { + case ADP_ENTITY_ADV_EVENT_TERMINATE: + + timer_stop(&adv->reannounce_timer); + + /* Per IEEE1722.1-2013 6.2.1.16 */ + entity->desc->available_index = htonl(0); + + for (port_num = 0; port_num < num_interfaces; port_num++) + adp_ieee_advertise_interface_sm(entity, port_num, ADP_INTERFACE_ADV_EVENT_TERMINATE); + + adv->state = ADP_ENTITY_ADV_NOT_STARTED; + break; + + case ADP_ENTITY_ADV_EVENT_ADVERTISE: + timer_stop(&adv->reannounce_timer); + + /* fallthrough */ + case ADP_ENTITY_ADV_EVENT_REANNOUNCE_TIMEOUT: + entity->desc->available_index = htonl(ntohl(entity->desc->available_index) + 1); + + timer_start(&adv->delay_timer, random_range(ADP_IEEE_ADV_TMR_DELAY_MS_MIN, max(1, (entity->valid_time / 5)) * MS_PER_S)); + + adv->state = ADP_ENTITY_ADV_DELAY; + break; + + default: + break; + } + break; + + default: + os_log(LOG_ERR, "Received event %s in unknown state %d\n", adp_ieee_advertise_entity_event2string(event), state); + goto err; + } + + os_log(LOG_DEBUG, "entity(%p): event %s, state from %s to %s\n", entity, + adp_ieee_advertise_entity_event2string(event), adp_ieee_advertise_entity_state2string(state), + adp_ieee_advertise_entity_state2string(adv->state)); + + if (state != adv->state) { + event = ADP_ENTITY_ADV_EVENT_RUN; + goto start; + } + + return 0; + +err: + return -1; +} + +/** + * Advertise interface state machine (6.2.5.3) + * \return 0 on success, negative otherwise + * \param entity pointer to entity context + * \param port_id port index + * \param event an adp_ieee_advertise_interface_event_t event + */ +int adp_ieee_advertise_interface_sm(struct entity *entity, unsigned int port_id, adp_ieee_advertise_interface_event_t event) +{ + adp_ieee_advertise_interface_state_t state; + struct avb_interface_dynamic_desc *avb_itf_dynamic; + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + + if (!avb_itf_dynamic) + goto err; + +start: + state = avb_itf_dynamic->u.ieee.advertise_state; + + switch (state) { + case ADP_INTERFACE_ADV_NOT_STARTED: + switch (event) { + case ADP_INTERFACE_ADV_EVENT_BEGIN: + if (!(avb_itf_dynamic->operational_state)) + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_LINK_DOWN; + else + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_WAITING; + + break; + + default: + break; + } + break; + + case ADP_INTERFACE_ADV_WAITING: + switch (event) { + case ADP_INTERFACE_ADV_EVENT_TERMINATE: + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_DEPARTING; + break; + + case ADP_INTERFACE_ADV_EVENT_ADVERTISE: + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_ADVERTISE; + break; + + case ADP_INTERFACE_ADV_EVENT_RCV_DISCOVER: + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_RECEIVED_DISCOVER; + break; + + case ADP_INTERFACE_ADV_EVENT_GM_CHANGE: + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_UPDATE_GM; + break; + + case ADP_INTERFACE_ADV_EVENT_LINK_DOWN: + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_LINK_DOWN; + break; + + default: + break; + } + break; + + case ADP_INTERFACE_ADV_DEPARTING: + if (adp_advertise_send_packet(&entity->adp, ADP_ENTITY_DEPARTING, port_id) < 0) + os_log(LOG_ERR, "entity(%p) port_id(%d) Couldn't send ENTITY DEPARTING message\n", + entity, port_id); + + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_NOT_STARTED; //UCT + break; + + case ADP_INTERFACE_ADV_ADVERTISE: + if (adp_advertise_send_packet(&entity->adp, ADP_ENTITY_AVAILABLE, port_id) < 0) + os_log(LOG_ERR, "entity(%p) port_id(%d) Couldn't send ENTITY AVAILABLE message\n", + entity, port_id); + + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_WAITING; //UCT + break; + + case ADP_INTERFACE_ADV_RECEIVED_DISCOVER: + + adp_ieee_advertise_entity_sm(entity, ADP_ENTITY_ADV_EVENT_ADVERTISE); + + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_WAITING; //UCT + break; + + case ADP_INTERFACE_ADV_UPDATE_GM: + adp_ieee_advertise_entity_sm(entity, ADP_ENTITY_ADV_EVENT_ADVERTISE); + + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_WAITING; //UCT + break; + + case ADP_INTERFACE_ADV_LINK_DOWN: + switch (event) { + case ADP_INTERFACE_ADV_EVENT_LINK_UP: + adp_ieee_advertise_entity_sm(entity, ADP_ENTITY_ADV_EVENT_ADVERTISE); + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_WAITING; + break; + + default: + break; + } + break; + + default: + os_log(LOG_ERR, "Received event %s in unknown state %d\n", adp_ieee_advertise_interface_event2string(event), state); + goto err; + } + + os_log(LOG_DEBUG, "entity(%p) port(%u) : event %s, state from %s to %s\n", entity, port_id, + adp_ieee_advertise_interface_event2string(event), adp_ieee_advertise_interface_state2string(state), + adp_ieee_advertise_interface_state2string(avb_itf_dynamic->u.ieee.advertise_state)); + + if (state != avb_itf_dynamic->u.ieee.advertise_state) { + event = ADP_INTERFACE_ADV_EVENT_RUN; + goto start; + } + + return 0; + +err: + return -1; +} + +static void adp_ieee_advertise_entity_reannounce_timeout(void *data) +{ + struct adp_ieee_advertise_entity_ctx *adv = data; + struct entity *entity = container_of(adv, struct entity, adp.ieee.advertise); + + if (entity_ready(entity)) { + adp_ieee_advertise_entity_sm(entity, ADP_ENTITY_ADV_EVENT_REANNOUNCE_TIMEOUT); + os_log(LOG_DEBUG, "adv(%p)\n", adv); + } +} + +static void adp_ieee_advertise_entity_delay_timeout(void *data) +{ + struct adp_ieee_advertise_entity_ctx *adv= data; + struct entity *entity = container_of(adv, struct entity, adp.ieee.advertise); + + if (entity_ready(entity)) { + adp_ieee_advertise_entity_sm(entity, ADP_ENTITY_ADV_EVENT_DELAY_TIMEOUT); + os_log(LOG_DEBUG, "adv(%p)\n", adv); + } +} + +__init static int adp_ieee_init_timers(struct avdecc_ctx *avdecc, struct adp_ieee_advertise_entity_ctx *adv) +{ + adv->reannounce_timer.func = adp_ieee_advertise_entity_reannounce_timeout; + adv->reannounce_timer.data = adv; + + if (timer_create(avdecc->timer_ctx, &adv->reannounce_timer, TIMER_TYPE_SYS, 0) < 0) + goto err_reannounce_timer; + + adv->delay_timer.func = adp_ieee_advertise_entity_delay_timeout; + adv->delay_timer.data = adv; + + if (timer_create(avdecc->timer_ctx, &adv->delay_timer, TIMER_TYPE_SYS, 0) < 0) + goto err_delay_timer; + + os_log(LOG_INIT, "adv(%p)\n", adv); + + return 0; + +err_delay_timer: + timer_destroy(&adv->reannounce_timer); +err_reannounce_timer: + return -1; +} + +__exit static int adp_ieee_exit_timers(struct avdecc_ctx *avdecc, struct adp_ieee_advertise_entity_ctx *adv) +{ + timer_destroy(&adv->reannounce_timer); + timer_destroy(&adv->delay_timer); + + os_log(LOG_INIT, "adv(%p) done\n", adv); + + return 0; +} + +__init int adp_ieee_advertise_init(struct adp_ieee_advertise_entity_ctx *adv) +{ + struct entity *entity = container_of(adv, struct entity, adp.ieee.advertise); + struct avb_interface_dynamic_desc *avb_itf_dynamic; + int num_interfaces, i; + + if (adp_ieee_init_timers(entity->avdecc, adv) < 0) { + os_log(LOG_CRIT, "Cannot initialize timers\n"); + goto err; + } + + adv->state = ADP_ENTITY_ADV_NOT_STARTED; + + num_interfaces = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + for (i = 0; i < num_interfaces; i++) { + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, i, NULL); + + avb_itf_dynamic->interface_index = i; + + avb_itf_dynamic->entity = entity; + + avb_itf_dynamic->u.ieee.advertise_state = ADP_INTERFACE_ADV_NOT_STARTED; + + os_log(LOG_DEBUG, "entity(%p) avb_interface(%p, %d)\n", entity, avb_itf_dynamic, i); + } + + os_log(LOG_INIT, "adv(%p) num interfaces(%u) done\n", adv, num_interfaces); + + return 0; + +err: + return -1; +} + +__exit int adp_ieee_advertise_exit(struct adp_ieee_advertise_entity_ctx *adv) +{ + struct entity *entity = container_of(adv, struct entity, adp.ieee.advertise); + + if (entity_ready(entity)) + adp_ieee_advertise_entity_sm(entity, ADP_ENTITY_ADV_EVENT_TERMINATE); + + adp_ieee_exit_timers(entity->avdecc, adv); + + os_log(LOG_INIT, "done\n"); + + return 0; +} diff --git a/avdecc/adp_ieee.h b/avdecc/adp_ieee.h new file mode 100644 index 0000000..86bc7f1 --- /dev/null +++ b/avdecc/adp_ieee.h @@ -0,0 +1,80 @@ +/* + * Copyright 2014-2015 Freescale Semiconductor, Inc. + * Copyright 2020-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ADP common defitions +*/ + +#ifndef _ADP_IEEE_H_ +#define _ADP_IEEE_H_ + +#include "common/types.h" +#include "common/ipc.h" +#include "common/adp.h" + +/* IEEE 1722.1 2013-Cor1-2018 6.2.4.3 */ +typedef enum { + ADP_ENTITY_ADV_NOT_STARTED = 0, + ADP_ENTITY_ADV_WAITING, + ADP_ENTITY_ADV_ADVERTISE, + ADP_ENTITY_ADV_DELAY, + ADP_ENTITY_ADV_RUN +} adp_ieee_advertise_entity_state_t; + +typedef enum { + ADP_ENTITY_ADV_EVENT_BEGIN = 0, + ADP_ENTITY_ADV_EVENT_ADVERTISE, + ADP_ENTITY_ADV_EVENT_REANNOUNCE_TIMEOUT, + ADP_ENTITY_ADV_EVENT_DELAY_TIMEOUT, + ADP_ENTITY_ADV_EVENT_TERMINATE, + ADP_ENTITY_ADV_EVENT_RUN +} adp_ieee_advertise_entity_event_t; + +struct avdecc_ctx; + +struct adp_ieee_advertise_entity_ctx { + adp_ieee_advertise_entity_state_t state; + struct timer reannounce_timer; + struct timer delay_timer; +}; + +/* IEEE 1722.1 2013 6.2.5.3 */ +typedef enum { + ADP_INTERFACE_ADV_NOT_STARTED = 0, + ADP_INTERFACE_ADV_WAITING, + ADP_INTERFACE_ADV_ADVERTISE, + ADP_INTERFACE_ADV_DEPARTING, + ADP_INTERFACE_ADV_RECEIVED_DISCOVER, + ADP_INTERFACE_ADV_UPDATE_GM, + ADP_INTERFACE_ADV_LINK_DOWN +} adp_ieee_advertise_interface_state_t; + +typedef enum { + ADP_INTERFACE_ADV_EVENT_BEGIN = 0, + ADP_INTERFACE_ADV_EVENT_ADVERTISE, + ADP_INTERFACE_ADV_EVENT_RCV_DISCOVER, + ADP_INTERFACE_ADV_EVENT_GM_CHANGE, + ADP_INTERFACE_ADV_EVENT_LINK_UP, + ADP_INTERFACE_ADV_EVENT_LINK_DOWN, + ADP_INTERFACE_ADV_EVENT_TERMINATE, + ADP_INTERFACE_ADV_EVENT_RUN +} adp_ieee_advertise_interface_event_t; + +struct avdecc_ctx; +struct adp_ctx; +struct entity; + +void adp_ieee_advertise_start(struct adp_ctx *adp); + +int adp_ieee_advertise_init(struct adp_ieee_advertise_entity_ctx *adv); +int adp_ieee_advertise_exit(struct adp_ieee_advertise_entity_ctx *adv); +int adp_ieee_advertise_sm(struct adp_ieee_advertise_entity_ctx *adv); + +int adp_ieee_advertise_entity_sm(struct entity *entity, adp_ieee_advertise_entity_event_t event); +int adp_ieee_advertise_interface_sm(struct entity *entity, unsigned int port_id, adp_ieee_advertise_interface_event_t event); + +#endif /* _ADP_IEEE_H_ */ diff --git a/avdecc/adp_milan.c b/avdecc/adp_milan.c new file mode 100644 index 0000000..930184d --- /dev/null +++ b/avdecc/adp_milan.c @@ -0,0 +1,688 @@ +/* + * Copyright 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ADP_MILAN common code + @details Handles ADP_MILAN stack +*/ + +#include "os/stdlib.h" + +#include "common/log.h" +#include "common/timer.h" +#include "common/net.h" +#include "common/random.h" + +#include "genavb/aem.h" + +#include "adp.h" +#include "aem.h" +#include "acmp_milan.h" + +#include "avdecc.h" + +/* _COMMON_ */ +#define ADP_MILAN_ADV_TMR_DELAY_MS (random_range(ADP_MILAN_ADV_TMR_DELAY_MS_MIN, ADP_MILAN_ADV_TMR_DELAY_MS_MAX)) /* Random timer between 0 and 4 sec per spec: Make it between 0.1 and 4 sec to avoid 0 period timers */ + +static const char *adp_milan_advertise_state2string(adp_milan_advertise_state_t state) +{ + switch (state) { + case2str(ADP_MILAN_ADV_NOT_STARTED); + case2str(ADP_MILAN_ADV_DOWN); + case2str(ADP_MILAN_ADV_WAITING); + case2str(ADP_MILAN_ADV_DELAY); + default: + return (char *) "Unknown adp advertise state"; + } +} + +static const char *adp_milan_advertise_event2string(adp_milan_advertise_event_t event) +{ + switch (event) { + case2str(ADP_MILAN_ADV_START); + case2str(ADP_MILAN_ADV_RCV_ADP_DISCOVER); + case2str(ADP_MILAN_ADV_TMR_ADVERTISE); + case2str(ADP_MILAN_ADV_TMR_DELAY); + case2str(ADP_MILAN_ADV_LINK_UP); + case2str(ADP_MILAN_ADV_LINK_DOWN); + case2str(ADP_MILAN_ADV_GM_CHANGE); + case2str(ADP_MILAN_ADV_SHUTDOWN); + default: + return (char *) "Unknown adp advertise event"; + } +} + +static const char *adp_milan_listener_sink_talker_state2string(adp_milan_listener_sink_talker_state_t state) +{ + switch (state) { + case2str(ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED); + case2str(ADP_LISTENER_SINK_TALKER_STATE_DISCOVERED); + default: + return (char *) "Unknown adp listener sink talker state"; + } +} + +static const char *adp_milan_listener_sink_event2string(adp_milan_listener_sink_event_t event) +{ + switch (event) { + case2str(ADP_MILAN_LISTENER_SINK_RCV_ADP_AVAILABLE); + case2str(ADP_MILAN_LISTENER_SINK_RCV_ADP_DEPARTING); + case2str(ADP_MILAN_LISTENER_SINK_TMR_NO_ADP); + default: + return (char *) "Unknown adp listener sink event"; + } +} + +/** Sends an ADP ENTITY AVAILABLE PDU to the network and increments the entity's available_index counter. + * \return 0 on success, -1 if error + * \param entity pointer to the entity struct + * \param port_id port index on which the packet will be sent + */ +static int adp_milan_advertise_send_available(struct entity *entity, unsigned int port_id) +{ + int rc; + + rc = adp_advertise_send_packet(&entity->adp, ADP_ENTITY_AVAILABLE, port_id); + + entity->desc->available_index = htonl(ntohl(entity->desc->available_index) + 1); + + return rc; +} + +/** + * Check the gptp_grandmaster_id and gptp_domain_number fields of the received ADP ENTITY_AVAILABLE PDU + * \return true if the current gPTP configuration and state of the PAAD on this port match those in the PDU, false otherwise + * \param entity, pointer to the entity context + * \param listener_unique_id + * \param pdu, pointer to ADP PDU + */ +static bool adp_milan_check_gptp(struct entity *entity, u16 listener_unique_id, struct adp_pdu *pdu) +{ + struct avb_interface_dynamic_desc *avb_interface_dynamic; + struct stream_descriptor *stream_input; + struct avb_interface_descriptor *avb_interface; + + stream_input = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + + if (!stream_input) + goto exit; + + avb_interface_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, ntohs(stream_input->avb_interface_index), NULL); + + if (!avb_interface_dynamic) + goto exit; + + avb_interface = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE, ntohs(stream_input->avb_interface_index), NULL); + + if (!avb_interface) + goto exit; + + if (cmp_64(&avb_interface_dynamic->gptp_grandmaster_id, &pdu->gptp_grandmaster_id) && (avb_interface->domain_number == pdu->gptp_domain_number)) + return true; + +exit: + return false; +} + +/** + * Update discovery state machine upon reception of an ADP ENTITY_AVAILABLE/ENTITY_DEPARTING message + * \return none + * \param avdecc, pointer to AVDECC context + * \param msg_type, ENTITY_AVAILABLE/ENTITY_DEPARTING + * \param pdu, pointer to ADP PDU + * \param mac_src, pointer to source MAC address + */ +void adp_milan_listener_rcv(struct entity *entity, u8 msg_type, struct adp_pdu *pdu, u8 valid_time) +{ + int num_stream_in, i; + struct stream_input_dynamic_desc *stream_input_dynamic; + adp_milan_listener_sink_event_t event; + + if (msg_type == ADP_ENTITY_AVAILABLE) + event = ADP_MILAN_LISTENER_SINK_RCV_ADP_AVAILABLE; + else if (msg_type == ADP_ENTITY_DEPARTING) + event = ADP_MILAN_LISTENER_SINK_RCV_ADP_DEPARTING; + else + return; + + num_stream_in = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT); + for (i = 0; i < num_stream_in; i++) { + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + if (ACMP_MILAN_IS_LISTENER_SINK_BOUND(stream_input_dynamic) && cmp_64(&stream_input_dynamic->talker_entity_id, &pdu->entity_id)) { + adp_milan_listener_sink_discovery_sm(entity, i, event, valid_time, pdu); + } + } + + os_log(LOG_DEBUG, "entity(%p) msg(%d) done\n", entity, msg_type); +} + +/* _TIMERS_ */ +/* Advertise */ +static void adp_milan_adv_delay_timer_timeout(void *data) +{ + struct avb_interface_dynamic_desc *avb_interface_dynamic = (struct avb_interface_dynamic_desc *)data; + + adp_milan_advertise_sm(avb_interface_dynamic->entity, avb_interface_dynamic->interface_index, ADP_MILAN_ADV_TMR_DELAY); +} + +static void adp_milan_adv_timer_timeout(void *data) +{ + struct avb_interface_dynamic_desc *avb_interface_dynamic = (struct avb_interface_dynamic_desc *)data; + + adp_milan_advertise_sm(avb_interface_dynamic->entity, avb_interface_dynamic->interface_index, ADP_MILAN_ADV_TMR_ADVERTISE); +} + +/* Discovery */ +static void adp_milan_listener_sink_disc_timer_timeout(void *data) +{ + struct stream_input_dynamic_desc *stream_input_dynamic = (struct stream_input_dynamic_desc *)data; + + adp_milan_listener_sink_discovery_sm(stream_input_dynamic->u.milan.entity, stream_input_dynamic->u.milan.unique_id, ADP_MILAN_LISTENER_SINK_TMR_NO_ADP, 0, NULL); +} +/* _TIMERS_ */ + + +/* _STATE_MACHINES_ */ +/* Advertise */ +/** + * Advertise state machine handler + * \return 0 on success, negative otherwise + * \param entity pointer to entity context + * \param port_id port index + * \param event an adp_milan_advertise_event_t event + */ +int adp_milan_advertise_sm(struct entity *entity, unsigned int port_id, adp_milan_advertise_event_t event) +{ + adp_milan_advertise_state_t state; + struct avb_interface_dynamic_desc *avb_interface_dynamic; + + avb_interface_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + + if (!avb_interface_dynamic) + goto err; + + state = avb_interface_dynamic->u.milan.state; + + switch (state) { + case ADP_MILAN_ADV_NOT_STARTED: + switch (event) { + case ADP_MILAN_ADV_START: + /* 9.3.5.1/9.3.5.2 */ + if (!(avb_interface_dynamic->operational_state)) { + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_DOWN; + } else { + timer_start(&avb_interface_dynamic->u.milan.adp_delay_timer, random_range(ADP_MILAN_ADV_TMR_DELAY_MS_INIT_MIN, ADP_MILAN_ADV_TMR_DELAY_MS_INIT_MAX)); /* random between 0.1 s and 2 sec */ + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_DELAY; + } + + break; + + default: + break; + } + break; + + case ADP_MILAN_ADV_DOWN: + switch (event) { + case ADP_MILAN_ADV_LINK_UP: + /* 9.3.5.2/3 */ + timer_start(&avb_interface_dynamic->u.milan.adp_delay_timer, ADP_MILAN_ADV_TMR_DELAY_MS); + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_DELAY; + break; + + default: + break; + } + break; + + case ADP_MILAN_ADV_WAITING: + switch (event) { + case ADP_MILAN_ADV_RCV_ADP_DISCOVER: + /* 9.3.5.4 */ + timer_stop(&avb_interface_dynamic->u.milan.adp_advertise_timer); + timer_start(&avb_interface_dynamic->u.milan.adp_delay_timer, ADP_MILAN_ADV_TMR_DELAY_MS); + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_DELAY; + break; + + case ADP_MILAN_ADV_TMR_ADVERTISE: + /* 9.3.5.5 */ + timer_start(&avb_interface_dynamic->u.milan.adp_delay_timer, ADP_MILAN_ADV_TMR_DELAY_MS); + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_DELAY; + break; + + case ADP_MILAN_ADV_LINK_DOWN: + /* 9.3.5.6 */ + timer_stop(&avb_interface_dynamic->u.milan.adp_advertise_timer); + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_DOWN; + break; + + case ADP_MILAN_ADV_GM_CHANGE: + /* 9.3.5.7 */ + /* Stopping the TMR_ADVERTISE is not mentioned in the spec, but it needs to be stopped before going into + * the DELAY state as it will be restarted there. + */ + timer_stop(&avb_interface_dynamic->u.milan.adp_advertise_timer); + timer_start(&avb_interface_dynamic->u.milan.adp_delay_timer, ADP_MILAN_ADV_TMR_DELAY_MS); + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_DELAY; + break; + + case ADP_MILAN_ADV_SHUTDOWN: + /* 9.3.5.8 */ + timer_stop(&avb_interface_dynamic->u.milan.adp_advertise_timer); + + /* Per IEEE1722.1-2013 6.2.1.16 */ + entity->desc->available_index = htonl(0); + + if (adp_advertise_send_packet(&entity->adp, ADP_ENTITY_DEPARTING, port_id) < 0) { + os_log(LOG_ERR, "entity(%p) port_id(%d) Couldn't send ENTITY DEPARTING message\n", entity, port_id); + } + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_NOT_STARTED; + break; + + default: + break; + } + break; + + case ADP_MILAN_ADV_DELAY: + switch (event) { + case ADP_MILAN_ADV_TMR_DELAY: + /* 9.3.5.9 */ + if (adp_milan_advertise_send_available(entity, port_id) < 0) { + os_log(LOG_ERR, "entity(%p) port_id(%d) Couldn't send ENTITY AVAILABLE message\n", entity, port_id); + } + + timer_start(&avb_interface_dynamic->u.milan.adp_advertise_timer, ADP_MILAN_ADV_TMR_ADVERTISE_MS); /* 5 sec */ + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_WAITING; + break; + + case ADP_MILAN_ADV_LINK_DOWN: + /* 9.3.5.10 */ + timer_stop(&avb_interface_dynamic->u.milan.adp_delay_timer); + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_DOWN; + break; + + case ADP_MILAN_ADV_SHUTDOWN: + /* 9.3.5.11 */ + timer_stop(&avb_interface_dynamic->u.milan.adp_delay_timer); + + /* Per IEEE1722.1-2013 6.2.1.16 */ + entity->desc->available_index = htonl(0); + + if (adp_advertise_send_packet(&entity->adp, ADP_ENTITY_DEPARTING, port_id) < 0) { + os_log(LOG_ERR, "entity(%p) port_id(%d) Couldn't send ENTITY DEPARTING message\n", entity, port_id); + } + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_NOT_STARTED; + break; + + default: + break; + } + break; + + default: + os_log(LOG_ERR, "Received event %s in unknown state %d\n", adp_milan_advertise_event2string(event), state); + goto err; + } + + os_log(LOG_DEBUG, "entity(%p) port(%u) : event %s, state from %s to %s\n", entity, port_id, + adp_milan_advertise_event2string(event), adp_milan_advertise_state2string(state), + adp_milan_advertise_state2string(avb_interface_dynamic->u.milan.state)); + + return 0; + +err: + return -1; +} + +/* Discovery */ +/** + * Discovery state machine handler + * \return 0 on success, negative otherwise + * \param entity, pointer to entity context + * \param listener_unique_id + * \param event, an adp_milan_listener_sink_event_t event + * \param pdu, pointer to ADP PDU + */ +int adp_milan_listener_sink_discovery_sm(struct entity *entity, u16 listener_unique_id, adp_milan_listener_sink_event_t event, u8 valid_time, struct adp_pdu *pdu) +{ + adp_milan_listener_sink_talker_state_t state; + struct stream_input_dynamic_desc *stream_input_dynamic; + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, listener_unique_id, NULL); + + if (!stream_input_dynamic) + goto err; + + state = stream_input_dynamic->u.milan.talker_state; + + switch (state) { + case ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED: + switch (event) { + case ADP_MILAN_LISTENER_SINK_RCV_ADP_AVAILABLE: + /* 9.4.5.1 */ + if (!adp_milan_check_gptp(entity, listener_unique_id, pdu)) { + os_log(LOG_DEBUG, "entity(%p) listener_unique_id(%d) gPTP configuration of current PAAD don't match pdu's configuration\n", entity, listener_unique_id); + break; + } + + stream_input_dynamic->u.milan.available_index = pdu->available_index; + stream_input_dynamic->u.milan.interface_index = pdu->interface_index; + + timer_start(&stream_input_dynamic->u.milan.adp_discovery_timer, max(1, valid_time) * MS_PER_S); + + acmp_milan_listener_sink_event(entity, listener_unique_id, ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED); + + stream_input_dynamic->u.milan.talker_state = ADP_LISTENER_SINK_TALKER_STATE_DISCOVERED; + break; + + default: + break; + } + break; + + case ADP_LISTENER_SINK_TALKER_STATE_DISCOVERED: + switch(event) { + case ADP_MILAN_LISTENER_SINK_RCV_ADP_AVAILABLE: + /* 9.4.5.2 */ + if (ntohs(stream_input_dynamic->u.milan.interface_index) != ntohs(pdu->interface_index)) { + os_log(LOG_DEBUG, "entity(%p) listener_unique_id(%d) pdu's interface index (%d) don't match current interface index (%d)\n", + entity, listener_unique_id, ntohs(pdu->interface_index), ntohs(stream_input_dynamic->u.milan.interface_index)); + + break; + } + + if ((s32)(ntohl(pdu->available_index) - ntohl(stream_input_dynamic->u.milan.available_index)) <= 0) { + acmp_milan_listener_sink_event(entity, listener_unique_id, ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED); + + if (!adp_milan_check_gptp(entity, listener_unique_id, pdu)) { + timer_stop(&stream_input_dynamic->u.milan.adp_discovery_timer); + + stream_input_dynamic->u.milan.talker_state = ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED; + break; + } + + acmp_milan_listener_sink_event(entity, listener_unique_id, ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DISCOVERED); + } + + stream_input_dynamic->u.milan.available_index = pdu->available_index; + + timer_stop(&stream_input_dynamic->u.milan.adp_discovery_timer); + timer_start(&stream_input_dynamic->u.milan.adp_discovery_timer, max(1, valid_time) * MS_PER_S); + + break; + + case ADP_MILAN_LISTENER_SINK_RCV_ADP_DEPARTING: + /* 9.4.5.3 */ + if (ntohs(stream_input_dynamic->u.milan.interface_index) != ntohs(pdu->interface_index)) { + os_log(LOG_DEBUG, "entity(%p) listener_unique_id(%d) pdu's interface index (%d) don't match current interface index (%d)\n", + entity, listener_unique_id, ntohs(pdu->interface_index), ntohs(stream_input_dynamic->u.milan.interface_index)); + + break; + } + + timer_stop(&stream_input_dynamic->u.milan.adp_discovery_timer); + + acmp_milan_listener_sink_event(entity, listener_unique_id, ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED); + + stream_input_dynamic->u.milan.talker_state = ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED; + + break; + + case ADP_MILAN_LISTENER_SINK_TMR_NO_ADP: + /* 9.4.5.4 */ + acmp_milan_listener_sink_event(entity, listener_unique_id, ACMP_LISTENER_SINK_SM_EVENT_EVT_TK_DEPARTED); + + stream_input_dynamic->u.milan.talker_state = ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED; + + break; + + case ADP_MILAN_LISTENER_SINK_RESET: + /* Not mentioned in the spec but used by the ACMP listener sink SM to reset this one. */ + timer_stop(&stream_input_dynamic->u.milan.adp_discovery_timer); + + stream_input_dynamic->u.milan.talker_state = ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED; + + break; + + default: + break; + } + break; + + default: + os_log(LOG_ERR, "Received event %s in unknown state %d\n", adp_milan_listener_sink_event2string(event), state); + goto err; + } + + os_log(LOG_DEBUG, "entity(%p) listener_unique_id(%u) : event %s, state from %s to %s\n", entity, listener_unique_id, + adp_milan_listener_sink_event2string(event), adp_milan_listener_sink_talker_state2string(state), + adp_milan_listener_sink_talker_state2string(stream_input_dynamic->u.milan.talker_state)); + + return 0; + +err: + return -1; +} + +void adp_milan_advertise_start(struct adp_ctx *adp) +{ + int num_interfaces, i; + struct entity *entity = container_of(adp, struct entity, adp); + + num_interfaces = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + for (i = 0; i < num_interfaces; i++) { + adp_milan_advertise_sm(entity, i, ADP_MILAN_ADV_START); + } +} +/* _STATE_MACHINES_ */ + + +/* _EXIT_ */ +/* Timers */ +__exit static void adp_milan_advertise_exit_timers(struct entity *entity, unsigned int port_id) +{ + struct avb_interface_dynamic_desc *avb_interface_dynamic; + + avb_interface_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + + timer_destroy(&avb_interface_dynamic->u.milan.adp_delay_timer); + timer_destroy(&avb_interface_dynamic->u.milan.adp_advertise_timer); +} + +__exit static void adp_milan_listener_sink_exit_timers(struct entity *entity, int stream_input_index) +{ + struct stream_input_dynamic_desc *stream_input_dynamic; + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, stream_input_index, NULL); + + timer_destroy(&stream_input_dynamic->u.milan.adp_discovery_timer); +} + +/* Advertise */ +__exit int adp_milan_advertise_exit(struct adp_ctx *adp) +{ + int num_interfaces, i; + struct entity *entity = container_of(adp, struct entity, adp); + + num_interfaces = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + if (entity_ready(entity)) { + for (i = 0; i < num_interfaces; i++) { + adp_milan_advertise_sm(entity, i, ADP_MILAN_ADV_SHUTDOWN); + + adp_milan_advertise_exit_timers(entity, i); + } + } + + os_log(LOG_DEBUG, "entity(%p) num_interf(%d)\n", entity, num_interfaces); + + os_log(LOG_INIT, "done\n"); + return 0; +} + +/* Discovery */ +__exit int adp_milan_listener_sink_discovery_exit(struct adp_ctx *adp) +{ + int num_streams_input, i; + struct entity *entity = container_of(adp, struct entity, adp); + + num_streams_input = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT); + + for (i = 0; i < num_streams_input; i++) { + adp_milan_listener_sink_exit_timers(entity, i); + } + + os_log(LOG_DEBUG, "entity(%p) num_strin(%d)\n", entity, num_streams_input); + + os_log(LOG_INIT, "done\n"); + return 0; +} +/* _EXIT_ */ + + +/* _INIT_ */ +/* Timers */ +__init static int adp_milan_advertise_init_timers(struct entity *entity, unsigned int port_id) +{ + struct avb_interface_dynamic_desc *avb_interface_dynamic; + + avb_interface_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + + avb_interface_dynamic->u.milan.adp_delay_timer.func = adp_milan_adv_delay_timer_timeout; + avb_interface_dynamic->u.milan.adp_delay_timer.data = avb_interface_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &avb_interface_dynamic->u.milan.adp_delay_timer, 0, ADP_MILAN_TMR_GRANULARITY_MS) < 0) { + os_log(LOG_ERR, "entity(%p) avb_interface(%p, %d) advertise timer_create failed\n", entity, avb_interface_dynamic, port_id); + goto err_delay_timer; + } + + avb_interface_dynamic->u.milan.adp_advertise_timer.func = adp_milan_adv_timer_timeout; + avb_interface_dynamic->u.milan.adp_advertise_timer.data = avb_interface_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &avb_interface_dynamic->u.milan.adp_advertise_timer, 0, ADP_MILAN_TMR_GRANULARITY_MS) < 0) { + os_log(LOG_ERR, "entity(%p) avb_interface(%p, %d) advertise timer_create failed\n", entity, avb_interface_dynamic, port_id); + goto err_advertise_timer; + } + + return 0; + +err_advertise_timer: + timer_destroy(&avb_interface_dynamic->u.milan.adp_delay_timer); + +err_delay_timer: + return -1; +} + +__init static int adp_milan_listener_sink_init_timers(struct entity *entity, int stream_input_index) +{ + struct stream_input_dynamic_desc *stream_input_dynamic; + + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, stream_input_index, NULL); + + stream_input_dynamic->u.milan.adp_discovery_timer.func = adp_milan_listener_sink_disc_timer_timeout; + stream_input_dynamic->u.milan.adp_discovery_timer.data = stream_input_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &stream_input_dynamic->u.milan.adp_discovery_timer, 0, ADP_MILAN_TMR_GRANULARITY_MS) < 0) { + os_log(LOG_ERR, "entity(%p) stream_input(%p, %d) discovery timer_create failed\n", entity, stream_input_dynamic, stream_input_index); + goto err_timer; + } + + return 0; + +err_timer: + return -1; +} + +/* Advertise */ +__init int adp_milan_advertise_init(struct adp_ctx *adp) +{ + int num_interfaces, i; + struct avb_interface_dynamic_desc *avb_interface_dynamic; + struct entity *entity = container_of(adp, struct entity, adp); + + num_interfaces = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + os_log(LOG_DEBUG, "entity(%p) num_interf(%d)\n", entity, num_interfaces); + + for (i = 0; i < num_interfaces; i++) { + avb_interface_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, i, NULL); + + avb_interface_dynamic->interface_index = i; + + avb_interface_dynamic->entity = entity; + + if (adp_milan_advertise_init_timers(entity, i) < 0) { + os_log(LOG_ERR, "init timer failed\n"); + goto err_timer; + } + + avb_interface_dynamic->u.milan.state = ADP_MILAN_ADV_NOT_STARTED; + + os_log(LOG_DEBUG, "entity(%p) avb_interface(%p %d)\n", entity, avb_interface_dynamic, i); + } + + os_log(LOG_INIT, "done\n"); + return 0; + +err_timer: + while(i--) + adp_milan_advertise_exit_timers(entity, i); + + os_log(LOG_ERR, "failed\n"); + return -1; +} + +/* Discovery */ +__init int adp_milan_listener_sink_discovery_init(struct adp_ctx *adp) +{ + int num_stream_in, i; + struct stream_input_dynamic_desc *stream_input_dynamic; + struct entity *entity = container_of(adp, struct entity, adp); + + num_stream_in = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT); + + os_log(LOG_DEBUG, "entity(%p) num_strin(%d)\n", entity, num_stream_in); + + for (i = 0; i < num_stream_in; i++) { + stream_input_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + + stream_input_dynamic->u.milan.unique_id = i; + + stream_input_dynamic->u.milan.interface_index = 0; + stream_input_dynamic->u.milan.available_index = 0; + + if (adp_milan_listener_sink_init_timers(entity, i) < 0) { + os_log(LOG_ERR, "init timer failed\n"); + goto err_timer; + } + + stream_input_dynamic->u.milan.talker_state = ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED; + + os_log(LOG_DEBUG, "entity(%p) stream_input(%p, %d)\n", entity, stream_input_dynamic, i); + } + + os_log(LOG_INIT, "done\n"); + return 0; + +err_timer: + while(i--) + adp_milan_listener_sink_exit_timers(entity, i); + + os_log(LOG_ERR, "failed\n"); + return -1; +} +/* _INIT_ */ diff --git a/avdecc/adp_milan.h b/avdecc/adp_milan.h new file mode 100644 index 0000000..48b57a7 --- /dev/null +++ b/avdecc/adp_milan.h @@ -0,0 +1,84 @@ +/* + * Copyright 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief ADP_MILAN common defitions +*/ + +#ifndef _ADP_MILAN_H_ +#define _ADP_MILAN_H_ + +#include "common/types.h" +#include "common/ipc.h" +#include "common/adp.h" + +#define ADP_MILAN_ADV_TMR_ADVERTISE_MS 5000 +#define ADP_MILAN_ADV_TMR_DELAY_MS_INIT_MIN 100 +#define ADP_MILAN_ADV_TMR_DELAY_MS_INIT_MAX 2000 +#define ADP_MILAN_ADV_TMR_DELAY_MS_MIN 100 +#define ADP_MILAN_ADV_TMR_DELAY_MS_MAX 4000 +#define ADP_MILAN_TMR_GRANULARITY_MS 100 + +/* STATES */ +/* Advertise states as defined in MILAN Discovery,connection and control specification [9.3.2] */ +typedef enum { + ADP_MILAN_ADV_NOT_STARTED, + ADP_MILAN_ADV_DOWN, + ADP_MILAN_ADV_WAITING, + ADP_MILAN_ADV_DELAY, +} adp_milan_advertise_state_t; + +/* Discovery states as defined in MILAN Discovery,connection and control specification [9.4.2] */ +typedef enum { + ADP_LISTENER_SINK_TALKER_STATE_NOT_DISCOVERED = 0, + ADP_LISTENER_SINK_TALKER_STATE_DISCOVERED, +} adp_milan_listener_sink_talker_state_t; + +/* EVENTS */ +/* Advertise events as defined in MILAN Discovery,connection and control specification [9.3.3] */ +typedef enum { + ADP_MILAN_ADV_START, + ADP_MILAN_ADV_RCV_ADP_DISCOVER, + ADP_MILAN_ADV_TMR_ADVERTISE, + ADP_MILAN_ADV_TMR_DELAY, + ADP_MILAN_ADV_LINK_UP, + ADP_MILAN_ADV_LINK_DOWN, + ADP_MILAN_ADV_GM_CHANGE, + ADP_MILAN_ADV_SHUTDOWN, +} adp_milan_advertise_event_t; + +/* Discovery events as defined in MILAN Discovery,connection and control specification [9.4.3] */ +typedef enum { + ADP_MILAN_LISTENER_SINK_RCV_ADP_AVAILABLE, + ADP_MILAN_LISTENER_SINK_RCV_ADP_DEPARTING, + ADP_MILAN_LISTENER_SINK_TMR_NO_ADP, + ADP_MILAN_LISTENER_SINK_RESET, +} adp_milan_listener_sink_event_t; + + +/* STATE MACHINE */ +/* Advertise and Listener Discovery states [9.3.4 and 9.4.4] are tracked in the dynamic descriptors */ + +/* CONTEXT */ +/* Advertise and Listener Discovery contexts are managed in the dynamic descriptors */ + +struct avdecc_ctx; +struct adp_ctx; +struct entity; + +int adp_milan_advertise_init(struct adp_ctx *adp); +int adp_milan_advertise_exit(struct adp_ctx *adp); +int adp_milan_listener_sink_discovery_init(struct adp_ctx *adp); +int adp_milan_listener_sink_discovery_exit(struct adp_ctx *adp); + +void adp_milan_advertise_start(struct adp_ctx *adp); + +int adp_milan_advertise_sm(struct entity *entity, unsigned int port_id, adp_milan_advertise_event_t event); +int adp_milan_listener_sink_discovery_sm(struct entity *entity, u16 listener_unique_id, adp_milan_listener_sink_event_t event, u8 valid_time, struct adp_pdu *pdu); + +void adp_milan_listener_rcv(struct entity *entity, u8 msg_type, struct adp_pdu *pdu, u8 valid_time); + +#endif /* _ADP_MILAN_H_ */ diff --git a/avdecc/aecp.c b/avdecc/aecp.c new file mode 100644 index 0000000..b11c058 --- /dev/null +++ b/avdecc/aecp.c @@ -0,0 +1,3790 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AECP common code + @details Handles AECP stack +*/ + +#include "os/stdlib.h" +#include "os/log.h" +#include "os/string.h" + +#include "common/log.h" +#include "common/types.h" + +#include "genavb/aem.h" +#include "genavb/ptp.h" + +#include "aecp.h" +#include "avdecc.h" + +static const u8 aecp_mvu_protocol_id[6] = MILAN_VENDOR_UNIQUE_PROTOCOL_ID; + +static int aecp_aem_send_command(struct aecp_ctx *aecp, struct avdecc_port *port, struct aecp_aem_pdu *pdu, struct net_tx_desc *desc, u8 *mac_dst, u16 len, struct ipc_tx *ipc, unsigned int ipc_dst); +static int aecp_aem_send_response(struct aecp_ctx *aecp, struct avdecc_port *port, struct aecp_aem_pdu *pdu, struct net_tx_desc *desc, u64 controller_id, u16 sequence_id, u8 status, u8 unsolicited, u8 *mac_dst, u16 len); + +#define IS_VALID_GET_COUNTERS_DESCRIPTOR_TYPE(desc_type) ((desc_type) == AEM_DESC_TYPE_ENTITY || \ + (desc_type) == AEM_DESC_TYPE_CLOCK_SOURCE || \ + (desc_type) == AEM_DESC_TYPE_CLOCK_DOMAIN || \ + (desc_type) == AEM_DESC_TYPE_AVB_INTERFACE || \ + (desc_type) == AEM_DESC_TYPE_STREAM_INPUT) + +static const char *aecp_mvu_cmdtype2string(aecp_mvu_command_type_t cmd_type) +{ + switch (cmd_type) { + case2str(AECP_MVU_CMD_GET_MILAN_INFO); + default: + return (char *) "Unknown AECP MVU command type"; + } +} + +static const char *aecp_aem_cmdtype2string(aecp_aem_command_type_t cmd_type) +{ + switch (cmd_type) { + case2str(AECP_AEM_CMD_ACQUIRE_ENTITY); + case2str(AECP_AEM_CMD_LOCK_ENTITY); + case2str(AECP_AEM_CMD_ENTITY_AVAILABLE); + case2str(AECP_AEM_CMD_CONTROLLER_AVAILABLE); + case2str(AECP_AEM_CMD_READ_DESCRIPTOR); + case2str(AECP_AEM_CMD_WRITE_DESCRIPTOR); + case2str(AECP_AEM_CMD_SET_CONFIGURATION); + case2str(AECP_AEM_CMD_GET_CONFIGURATION); + case2str(AECP_AEM_CMD_SET_STREAM_FORMAT); + case2str(AECP_AEM_CMD_GET_STREAM_FORMAT); + case2str(AECP_AEM_CMD_SET_VIDEO_FORMAT); + case2str(AECP_AEM_CMD_GET_VIDEO_FORMAT); + case2str(AECP_AEM_CMD_SET_SENSOR_FORMAT); + case2str(AECP_AEM_CMD_GET_SENSOR_FORMAT); + case2str(AECP_AEM_CMD_SET_STREAM_INFO); + case2str(AECP_AEM_CMD_GET_STREAM_INFO); + case2str(AECP_AEM_CMD_SET_NAME); + case2str(AECP_AEM_CMD_GET_NAME); + case2str(AECP_AEM_CMD_SET_ASSOCIATION_ID); + case2str(AECP_AEM_CMD_GET_ASSOCIATION_ID); + case2str(AECP_AEM_CMD_SET_SAMPLING_RATE); + case2str(AECP_AEM_CMD_GET_SAMPLING_RATE); + case2str(AECP_AEM_CMD_SET_CLOCK_SOURCE); + case2str(AECP_AEM_CMD_GET_CLOCK_SOURCE); + case2str(AECP_AEM_CMD_SET_CONTROL); + case2str(AECP_AEM_CMD_GET_CONTROL); + case2str(AECP_AEM_CMD_INCREMENT_CONTROL); + case2str(AECP_AEM_CMD_DECREMENT_CONTROL); + case2str(AECP_AEM_CMD_SET_SIGNAL_SELECTOR); + case2str(AECP_AEM_CMD_GET_SIGNAL_SELECTOR); + case2str(AECP_AEM_CMD_SET_MIXER); + case2str(AECP_AEM_CMD_GET_MIXER); + case2str(AECP_AEM_CMD_SET_MATRIX); + case2str(AECP_AEM_CMD_GET_MATRIX); + case2str(AECP_AEM_CMD_START_STREAMING); + case2str(AECP_AEM_CMD_STOP_STREAMING); + case2str(AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION); + case2str(AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION); + case2str(AECP_AEM_CMD_IDENTIFY_NOTIFICATION); + case2str(AECP_AEM_CMD_GET_AVB_INFO); + case2str(AECP_AEM_CMD_GET_AS_PATH); + case2str(AECP_AEM_CMD_GET_COUNTERS); + case2str(AECP_AEM_CMD_REBOOT); + case2str(AECP_AEM_CMD_GET_AUDIO_MAP); + case2str(AECP_AEM_CMD_ADD_AUDIO_MAPPINGS); + case2str(AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS); + case2str(AECP_AEM_CMD_GET_VIDEO_MAP); + case2str(AECP_AEM_CMD_ADD_VIDEO_MAPPINGS); + case2str(AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS); + case2str(AECP_AEM_CMD_GET_SENSOR_MAP); + case2str(AECP_AEM_CMD_ADD_SENSOR_MAPPINGS); + case2str(AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS); + case2str(AECP_AEM_CMD_START_OPERATION); + case2str(AECP_AEM_CMD_ABORT_OPERATION); + case2str(AECP_AEM_CMD_OPERATION_STATUS); + case2str(AECP_AEM_CMD_AUTH_ADD_KEY); + case2str(AECP_AEM_CMD_AUTH_DELETE_KEY); + case2str(AECP_AEM_CMD_AUTH_GET_KEY_LIST); + case2str(AECP_AEM_CMD_AUTH_GET_KEY); + case2str(AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN); + case2str(AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN); + case2str(AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST); + case2str(AECP_AEM_CMD_AUTH_GET_IDENTITY); + case2str(AECP_AEM_CMD_AUTH_ADD_TOKEN); + case2str(AECP_AEM_CMD_AUTH_DELETE_TOKEN); + case2str(AECP_AEM_CMD_AUTHENTICATE); + case2str(AECP_AEM_CMD_DEAUTHENTICATE); + case2str(AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY); + case2str(AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY); + case2str(AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION); + case2str(AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION); + case2str(AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH); + case2str(AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH); + case2str(AECP_AEM_CMD_SET_STREAM_BACKUP); + case2str(AECP_AEM_CMD_GET_STREAM_BACKUP); + case2str(AECP_AEM_CMD_EXPANSION); + default: + return (char *) "Unknown AECP AEM command type"; + } +} + +/** Helper function to display basic information about a given AECP AEM PDU. + * + * \param pdu pointer to AECP AEM PDU. + * \param msg_type message type of the PDU (from the AVTP part of the packet) + * \param status status field of the PDU (from the AVTP part of the packet) + */ +static inline void debug_dump_aecp_aem(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, int msg_type, u16 status) +{ + u64 controller_entity_id = pdu->controller_entity_id; + u64 entity_id = pdu->entity_id; + + os_log(LOG_DEBUG, "aecp(%p) AEM message type(%x) status(%u) u(%d) command_type(%x) entity(%016"PRIx64") controller(%016"PRIx64") seq_id(%d)\n", + aecp, msg_type, status, AECP_AEM_GET_U(pdu), AECP_AEM_GET_CMD_TYPE((struct aecp_aem_pdu *)pdu), + ntohll(entity_id), ntohll(controller_entity_id), ntohs(pdu->sequence_id)); +} + +/* + * Monitor timer handler that will check if registered controller is still available + * \param data, the registered controller + */ +static void aecp_monitor_timer_handler(void *data) +{ + struct unsolicited_ctx *entry = (struct unsolicited_ctx *)data; + struct aecp_ctx *aecp = entry->aecp; + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_port *port_rsp = &entity->avdecc->port[entry->port_id]; + struct net_tx_desc *net_desc; + struct aecp_aem_pdu *aecp_pdu; + u16 len = sizeof(struct aecp_aem_pdu); + void *buf; + + net_desc = net_tx_alloc(&port_rsp->net_tx, DEFAULT_NET_DATA_SIZE); + if (!net_desc) { + os_log(LOG_ERR,"aecp(%p): Cannot alloc net_tx\n", aecp); + goto err; + } + + buf = NET_DATA_START(net_desc); + + aecp_pdu = (struct aecp_aem_pdu *)((char *)buf + OFFSET_TO_AECP); + + AECP_AEM_SET_U_CMD_TYPE(aecp_pdu, 0, AECP_AEM_CMD_CONTROLLER_AVAILABLE); + + /* PDU's entity id is the controller entity id as the controller is the target of the command otherwise the controller would ignore the command */ + copy_64(&aecp_pdu->controller_entity_id, &entity->desc->entity_id); + copy_64(&aecp_pdu->entity_id, &entry->controller_id); + + if (aecp_aem_send_command(aecp, port_rsp, aecp_pdu, net_desc, entry->mac_dst, len, NULL, 0) < 0) { + + os_log(LOG_ERR,"aecp(%p) port(%u) couldn't send command CONTROLLER_AVAILABLE to controller(%016"PRIx64").\n", + aecp, entry->port_id, ntohll(entry->controller_id)); + + goto err; + } + + os_log(LOG_DEBUG,"aecp(%p) port(%u) Sent CONTROLLER_AVAILABLE to controller(%016"PRIx64") mac dest(%016"PRIx64").\n", + aecp, entry->port_id, ntohll(entry->controller_id), NTOH_MAC_VALUE(entry->mac_dst)); + +err: + return; +} + +static void aecp_milan_get_counters_async_unsolicited_notification_timer_handler(void *data) +{ + struct avb_interface_dynamic_desc *avb_itf_dynamic = (struct avb_interface_dynamic_desc *)data; + struct entity *entity = avb_itf_dynamic->entity; + + aecp_aem_send_async_unsolicited_notification(&entity->aecp, AECP_AEM_CMD_GET_COUNTERS, AEM_DESC_TYPE_AVB_INTERFACE, avb_itf_dynamic->interface_index); +} + +static void aecp_get_as_path_async_unsolicited_notification_timer_handler(void *data) +{ + struct avb_interface_dynamic_desc *avb_itf_dynamic = (struct avb_interface_dynamic_desc *)data; + struct entity *entity = avb_itf_dynamic->entity; + + avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer_running = false; + + if (avb_itf_dynamic->async_get_as_path_unsolicited_notification_pending) { + aecp_trigger_async_get_as_path_notification(entity, avb_itf_dynamic->interface_index); + } +} + +__init static void aecp_unsolicited_init(struct aecp_ctx *aecp) +{ + int i; + + list_head_init(&aecp->free_unsolicited); + list_head_init(&aecp->unsolicited); + + for (i = 0; i < aecp->max_unsolicited_registrations; i++) { + list_add(&aecp->free_unsolicited, &aecp->unsolicited_storage[i].list); + os_memset(aecp->unsolicited_storage[i].mac_dst, 0, 6); + } + + os_log(LOG_INIT, "aecp(%p) %d unsolicited registration max\n", aecp, aecp->max_unsolicited_registrations); +} + +/** Find an unsolicited entry based on the controller ID. + * \param aecp AECP context to search into. + * \param controller_id Pointer to the controller ID to match. + * \param port_id avdecc port / interface index on which the controller has registered + * \return pointer to unsolicited context matching both controller_id and port_id, or NULL if none found. + */ +static struct unsolicited_ctx *aecp_unsolicited_find(struct aecp_ctx *aecp, u64 controller_id, unsigned int port_id) +{ + struct list_head *list_entry; + struct unsolicited_ctx *entry; + + list_entry = list_first(&aecp->unsolicited); + + while (list_entry != &aecp->unsolicited) { + entry = container_of(list_entry, struct unsolicited_ctx, list); + + if ((controller_id == entry->controller_id) && (port_id == entry->port_id)) + return entry; + + list_entry = list_next(list_entry); + } + + return NULL; +} + + +/** Add an unsolicited entry to the list. + * \param aecp AECP context where the entry should be added. + * \param mac_dst Pointer to the MAC address of the controller to add to the list. + * \param controller_id Pointer to the ID of the controller to add to the list. + * \param port_id avdecc port / interface index on which the PDU was received + * \return * 0 if the entry was added successfully, + * * 1 if the entry was already present (in such a case, the MAC address of the entry is updated), + * * -1 on failure. + */ +static int aecp_unsolicited_add(struct aecp_ctx *aecp, u8 *mac_dst, u64 controller_id, unsigned int port_id) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct unsolicited_ctx *entry = NULL; + struct list_head *list_entry; + + entry = aecp_unsolicited_find(aecp, controller_id, port_id); + if (entry) { + os_memcpy(entry->mac_dst, mac_dst, 6); + return 1; + } + + if (!list_empty(&aecp->free_unsolicited)) { + list_entry = list_first(&aecp->free_unsolicited); + entry = container_of(list_entry, struct unsolicited_ctx, list); + list_del(list_entry); + + os_memcpy(entry->mac_dst, mac_dst, 6); + entry->port_id = port_id; + entry->controller_id = controller_id; + entry->sequence_id = 0; /* Per AVNU.IO.CONTROL 7.3.21 */ + entry->aecp = aecp; + + list_add(&aecp->unsolicited, &entry->list); + + /* Monitor timer. Per AVNU.IO.CONTROL 7.5.3 */ + entry->monitor_timer.func = aecp_monitor_timer_handler; + entry->monitor_timer.data = entry; + + if (timer_create(avdecc->timer_ctx, &entry->monitor_timer, 0, MONITOR_TIMER_GRANULARITY) < 0) { + os_log(LOG_ERR,"aecp(%p) port(%u) couldn't create timer for controller(%016"PRIx64").\n", + aecp, entry->port_id, ntohll(entry->controller_id)); + + goto err; + } + + timer_start(&entry->monitor_timer, MONITOR_TIMER_INTERVAL); + + return 0; + } else { + os_log(LOG_ERR, "No more unsolicited entries available\n"); + goto err; + } + +err: + return -1; +} + +/** Remove an unsolicited entry from the list. + * + * \return 0 on success (entry found and removed) or -1 on failure. + * \param aecp AECP context the entry should be removed from. + * \param controller_id pointer to the ID of the controller to be removed. + * \param port_id avdecc port / interface index on which the PDU was received + */ +static int aecp_unsolicited_remove(struct aecp_ctx *aecp, u64 controller_id, unsigned int port_id) +{ + struct unsolicited_ctx *entry = aecp_unsolicited_find(aecp, controller_id, port_id); + + if (entry) { + list_del(&entry->list); + list_add(&aecp->free_unsolicited, &entry->list); + + timer_destroy(&entry->monitor_timer); + + return 0; + } else { + return -1; + } +} + +/** Checks a given value matches a CONTROL descriptor definition. + * + * \return 1 if validation successful or undetermined, 0 otherwise. + * \param desc CONTROL descriptor to validate against. + * \param raw_value Pointer to the value that should be validated. + * \param len Length in bytes of the memory area pointed to by raw_value; + */ +static int aecp_aem_validate_control_value(struct control_descriptor *desc, void *raw_value, u16 len) +{ + int rc = 1; + int control_value_type = AEM_CONTROL_GET_VALUE_TYPE(ntohs(desc->control_value_type)); + + switch (control_value_type) { + case AEM_CONTROL_LINEAR_UINT8: + { + int i; + u8 *values = raw_value; + + if (len != ntohs(desc->number_of_values)) { + os_log(LOG_ERR, "Invalid UINT8 Control value: length not equal number_of_values %u != %u \n", + len, ntohs(desc->number_of_values)); + rc = 0; + break; + } + + for (i = 0; i < ntohs(desc->number_of_values); i++) { + if ((values[i] < desc->value_details.linear_int8[i].min) || (values[i] > desc->value_details.linear_int8[i].max)) { + os_log(LOG_ERR, "Invalid UINT8 Control value: values[%d] = %u is out of bound [%u,%u].\n", + i, values[i], desc->value_details.linear_int8[i].min, + desc->value_details.linear_int8[i].max); + rc = 0; + break; + } + } + + break; + } + case AEM_CONTROL_UTF8: + { + int count = 0; + + if (len > AEM_UTF8_MAX_LENGTH) { + os_log(LOG_ERR, "Invalid UTF8 Control value: size exceeds max supported %u > %u \n", + len, AEM_UTF8_MAX_LENGTH); + rc = 0; + break; + } + + count = os_strnlen(raw_value, len); + if (count == len) { + os_log(LOG_ERR, "Invalid UTF8 Control value: there is no null terminating character in buffer of size %u \n", len); + rc = 0; + } else if (count < (len - 1)) { + os_log(LOG_ERR, "Invalid UTF8 Control value: value (%s) of size (%u) should have only one null character as last byte:" + "null character at index (%u) instead of (%u) \n", (char *)raw_value, len, count, len - 1); + rc = 0; + } + + break; + } + default: + os_log(LOG_INFO, "Unsupported CONTROL value_type(%d), validity of value undetermined.\n", control_value_type); + break; + } + + return rc; +} + +/** Copies the descriptor value(s) from a CONTROL descriptor to the values field of a SET_CONTROL PDU. + * + * \return -1 on failure, otherwise the number of bytes copied. + * \param desc CONTROL descriptor to copy values from. + * \param raw_value Pointer to the values field of a SET_CONTROL PDU. + * \param len Length in bytes of the memory area pointed to by raw_value. No more than len bytes will be copied. + * + */ +static int aecp_aem_control_desc_to_pdu(struct control_descriptor *desc, void *raw_value, u16 len) +{ + int count; + int control_value_type = AEM_CONTROL_GET_VALUE_TYPE(ntohs(desc->control_value_type)); + + if (!len) { + os_log(LOG_ERR, "Invalid null length\n"); + count = -1; + goto exit; + } + + switch (control_value_type) { + case AEM_CONTROL_LINEAR_UINT8: + { + int i; + u8 *values = raw_value; + + if (len < ntohs(desc->number_of_values)) { + os_log(LOG_ERR, "Invalid UINT8 Control value: length inferior to number_of_values %u < %u \n", + len, ntohs(desc->number_of_values)); + count = -1; + goto exit; + } + + count = ntohs(desc->number_of_values); + + for (i = 0; i < count; i++) + values[i] = desc->value_details.linear_int8[i].current; + + break; + } + case AEM_CONTROL_UTF8: + count = os_strnlen((char *)desc->value_details.utf8.string, AEM_UTF8_MAX_LENGTH - 1) + 1; // Remove 1 from max and add it back to account for terminating 0. + + if (len < count) { + os_log(LOG_ERR, "Invalid UTF8 Control value: length inferior to descriptor value size %u < %u \n", len, count); + count = -1; + goto exit; + } + + os_memcpy(raw_value, desc->value_details.utf8.string, count); + + break; + default: + os_log(LOG_INFO, "Unsupported CONTROL value_type(%d) \n", control_value_type); + count = -1; + break; + } +exit: + return count; +} + +/** Copies the descriptor value(s), if valid, from the values field of a SET_CONTROL PDU to a CONTROL descriptor. + * + * \return -1 on failure, 0 nothing copied (values are the same as the desciptor) and 1 if values are copied. + * \param desc CONTROL descriptor to copy values to. + * \param raw_value Pointer to the values field of a SET_CONTROL PDU. + * \param len Length in bytes of the memory area pointed to by raw_value. No more than len bytes will be copied. + * + */ +static int aecp_aem_control_pdu_to_desc(struct control_descriptor *desc, void *raw_value, u16 len) +{ + int rc = 0; + int control_value_type = AEM_CONTROL_GET_VALUE_TYPE(ntohs(desc->control_value_type)); + + /* Validate the values */ + if (!aecp_aem_validate_control_value(desc, raw_value, len)) { + rc = -1; + goto exit; + } + + switch (control_value_type) { + case AEM_CONTROL_LINEAR_UINT8: + { + int i; + u8 *values = raw_value; + + for (i = 0; i < ntohs(desc->number_of_values); i++) { + /* Copy values only if differents */ + if (desc->value_details.linear_int8[i].current != values[i]) { + desc->value_details.linear_int8[i].current = values[i]; + rc = 1; + } + } + + break; + } + case AEM_CONTROL_UTF8: + { + int count = os_strnlen(raw_value, len - 1) + 1; // Remove 1 from max and add it back to account for terminating 0. + int desc_values_len = os_strnlen((char *)desc->value_details.utf8.string, AEM_UTF8_MAX_LENGTH - 1) + 1; + + /* Copy value only if different */ + if ((count != desc_values_len) || + (os_memcmp(desc->value_details.utf8.string, raw_value, count) != 0)) { + + os_memcpy(desc->value_details.utf8.string, raw_value, count); + rc = 1; + } + + break; + } + default: + os_log(LOG_INFO, "Unsupported CONTROL value_type(%d), 0 bytes copied.\n", control_value_type); + rc = -1; + break; + } + +exit: + return rc; +} + +/** Send an AECP AEM message through an IPC channel. + * + * \return 0 on success or -1 on failure. + * \param aecp AECP context. + * \param pdu Pointer to AECP AEM PDU to send (content will be copied into the IPC message). + * \param msg_type Message type of the AECP AEM PDU (normally one of AECP_AEM_COMMAND, AECP_AEM_RESPONSE) + * \param status Status of the AECP AEM PDU. + * \param len Length of the EACP AEM PDU. + * \param ipc IPC channel to send the message through. + */ +static int aecp_aem_ipc_tx(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, u8 msg_type, u8 status, u16 len, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct ipc_desc *desc; + int rc = -1; + + os_log(LOG_DEBUG, "aecp(%p) pdu(%p) len(%u) ipc(%p)\n", aecp, pdu, len, ipc); + debug_dump_aecp_aem(aecp, pdu, msg_type, status); + + desc = ipc_alloc(ipc, sizeof(struct genavb_aecp_msg)); + if (desc) { + desc->dst = ipc_dst; + desc->type = GENAVB_MSG_AECP; + desc->len = sizeof(struct genavb_aecp_msg); + + desc->u.aecp_msg.msg_type = msg_type; + desc->u.aecp_msg.status = status; + if (len > AVB_AECP_MAX_MSG_SIZE) { + os_log(LOG_ERR, "aecp(%p) Truncating PDU to fit inside IPC buffer, length above limit (%d > %d).\n", aecp, len, AVB_AECP_MAX_MSG_SIZE); + len = AVB_AECP_MAX_MSG_SIZE; + } + desc->u.aecp_msg.len = len; + os_memcpy(&desc->u.aecp_msg.buf, pdu, len); + + if (ipc_tx(ipc, desc) < 0) { + os_log(LOG_ERR, "avdecc(%p) ipc_tx() failed\n", avdecc); + goto err_ipc_tx; + } + + rc = 0; + } else { + os_log(LOG_ERR, "avdecc(%p) ipc_alloc() failed\n", avdecc); + } + + return rc; + +err_ipc_tx: + ipc_free(ipc, desc); + return rc; +} + +static inline int aecp_aem_ipc_tx_command(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, u16 len, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + return aecp_aem_ipc_tx(aecp, pdu, AECP_AEM_COMMAND, AECP_AEM_SUCCESS, len, ipc, ipc_dst); +} + +static inline int aecp_aem_ipc_tx_response(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, u8 status, u16 len, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + return aecp_aem_ipc_tx(aecp, pdu, AECP_AEM_RESPONSE, status, len, ipc, ipc_dst); +} + +/** Allocates a new network tx descriptor and initializes it as an AECP PDU. + * + * \return pointer to the new network descriptor, or NULL otherwise. + * \param port Pointer to the avdecc port on which the packet will be sent + * \param buf Pointer to an AECP PDU whose content should be copied into the new descriptor. + * \param len Length of the AECP PDU to be copied. + * \param pdu On return, *pdu will point to the start of the AECP PDU within the new descriptor. + */ +static struct net_tx_desc *aecp_net_tx_prepare(struct avdecc_port *port, void *buf, u16 *len, void **pdu) +{ + struct net_tx_desc *desc = NULL; + void *tx_buf; + + desc = net_tx_alloc(&port->net_tx, DEFAULT_NET_DATA_SIZE); + if (!desc) { + os_log(LOG_ERR, "Cannot alloc tx descriptor\n"); + goto exit; + } + + if (*len > AVDECC_AECP_MAX_SIZE) { + os_log(LOG_ERR, "Truncating AECP message, length above spec (%u > %zu).\n", *len, AVDECC_AECP_MAX_SIZE); + *len = AVDECC_AECP_MAX_SIZE; + } + + tx_buf = NET_DATA_START(desc); + *pdu = (void *)((char *)tx_buf + OFFSET_TO_AECP); + os_memcpy(*pdu, buf, *len); + +exit: + return desc; +} + +/** + * Sends an AECP AEM packet on the network. + * \return 0 on success, negative otherwise + * \param aecp Pointer to the AECP context. + * \param port Pointer to the avdecc port on which the packet will be sent + * \param desc Packet descriptor to send. + * \param status AECP status (9.2.1.1.6), to be placed in the protocol-specific portions of the AVTP control header. + * \param msg_type AECP message type (9.2.2.1.5), to be placed in the protocol-specific portions of the AVTP control header. + * \param mac_dst MAC address to use as destination. + * \param len Length of the data beyond the AVTP control header. + */ +static int aecp_aem_net_tx(struct aecp_ctx *aecp, struct avdecc_port *port, struct net_tx_desc *desc, u8 msg_type, u8 status, u8 *mac_dst, u16 len) +{ + void *buf_rsp; + + //TODO unify AECP/ACMP codes (at least similar logic if not same code) + + buf_rsp = NET_DATA_START(desc); + + desc->len += net_add_eth_header(buf_rsp, mac_dst, ETHERTYPE_AVTP); + desc->len += avdecc_add_common_header((char *)buf_rsp + desc->len, AVTP_SUBTYPE_AECP, msg_type, len - sizeof(u64), status); + desc->len += len; + + os_log(LOG_DEBUG, "aecp(%p) port(%u) AECP message desc(%p) len(%u) total_len(%u) destination mac(%012"PRIx64")\n", + aecp, port->port_id, desc, len, desc->len, NTOH_MAC_VALUE(mac_dst)); + + debug_dump_aecp_aem(aecp, (struct aecp_aem_pdu *)((char *)buf_rsp + OFFSET_TO_AECP), msg_type, status); + + if (avdecc_net_tx(port, desc) < 0) { + os_log(LOG_ERR, "aecp(%p) port(%u) send failed\n", aecp, port->port_id); + goto err; + } + + return 0; + +err: + return -1; +} + +static inline int aecp_aem_net_tx_command(struct aecp_ctx *aecp, struct avdecc_port *port, struct net_tx_desc *desc, u8 *mac_dst, u16 len) +{ + return aecp_aem_net_tx(aecp, port, desc, AECP_AEM_COMMAND, AECP_AEM_SUCCESS, mac_dst, len); +} + +static inline int aecp_aem_net_tx_response(struct aecp_ctx *aecp, struct avdecc_port *port, struct net_tx_desc *desc, u8 status, u8 *mac_dst, u16 len) +{ + return aecp_aem_net_tx(aecp, port, desc, AECP_AEM_RESPONSE, status, mac_dst, len); +} + +static inline int aecp_mvu_net_tx_response(struct aecp_ctx *aecp, struct avdecc_port *port, struct net_tx_desc *desc, u8 status, u8 *mac_dst, u16 len) +{ + return aecp_aem_net_tx(aecp, port, desc, AECP_VENDOR_UNIQUE_RESPONSE, status, mac_dst, len); +} + +static int aecp_aem_inflight_network_timeout(struct inflight_ctx *entry) +{ + struct aecp_ctx *aecp = container_of(entry->list_head, struct aecp_ctx, inflight_network); + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_port *port = &entity->avdecc->port[entry->data.port_id]; + int rc = 0; + bool send_ipc = true; + + os_log(LOG_DEBUG, "aecp(%p) inflight_ctx(%p) sequence id : %x\n", aecp, entry, entry->data.sequence_id); + + if (entry->data.retried) { + switch (AECP_AEM_GET_CMD_TYPE(&entry->data.pdu.aem)) { + case AECP_AEM_CMD_CONTROLLER_AVAILABLE: + { + /* As the controller didn't respond to our CONTROLLER_AVAILABLE command, + * we remove it from the registred controllers list and notify it with a DEREGISTER_UNSOLICITED_NOTIFICATION. + * Per AVNU.IO.CONTROL 7.5.3 + */ + struct net_tx_desc *net_desc; + struct aecp_aem_pdu *aecp_notif; + struct unsolicited_ctx *unsolicited_entry; + u16 len = sizeof(struct aecp_aem_pdu); + void *buf; + + send_ipc = false; + + unsolicited_entry = aecp_unsolicited_find(aecp, entry->data.pdu.aem.entity_id, entry->data.port_id); + if (!unsolicited_entry) { + os_log(LOG_ERR,"aecp(%p) port(%u) couldn't find registered controller(%016"PRIx64").\n", + aecp, entry->data.port_id, ntohll(entry->data.pdu.aem.entity_id)); + break; + } + + net_desc = net_tx_alloc(&port->net_tx, DEFAULT_NET_DATA_SIZE); + if (!net_desc) { + os_log(LOG_ERR,"aecp(%p): Cannot alloc net_tx\n", aecp); + break; + } + + buf = NET_DATA_START(net_desc); + + aecp_notif = (struct aecp_aem_pdu *)((char *)buf + OFFSET_TO_AECP); + + AECP_AEM_SET_U_CMD_TYPE(aecp_notif, 1, AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION); + + if (aecp_aem_send_response(aecp, port, aecp_notif, net_desc, entry->data.pdu.aem.entity_id, + unsolicited_entry->sequence_id, AECP_AEM_SUCCESS, 1, entry->data.mac_dst, len) < 0) { + + os_log(LOG_ERR,"aecp(%p) port(%u) couldn't send DEREGISTER_UNSOLICITED_NOTIFICATION to controller(%016"PRIx64").\n", + aecp, entry->data.port_id, ntohll(entry->data.pdu.aem.entity_id)); + + break; + } + + aecp_unsolicited_remove(aecp, entry->data.pdu.aem.entity_id, entry->data.port_id); + + os_log(LOG_DEBUG,"aecp(%p) port(%u) removing timed out controller(%016"PRIx64") from registered list.\n", + aecp, entry->data.port_id, ntohll(entry->data.pdu.aem.entity_id)); + + break; + } + default: + break; + } + + /* Send error response back to app */ + if (send_ipc) + aecp_aem_ipc_tx_response(aecp, &entry->data.pdu.aem, AECP_AEM_TIMEOUT, entry->data.len, (void *)entry->data.priv[0], (unsigned int)entry->data.priv[1]); + + rc = AVDECC_INFLIGHT_TIMER_STOP; + } + else { + /* Try sending the command one more time */ + struct net_tx_desc *desc; + struct aecp_aem_pdu *pdu; + + desc = aecp_net_tx_prepare(port, entry->data.pdu.buf, &entry->data.len, (void **)&pdu); + + if (!desc) { + os_log(LOG_ERR, "aecp(%p) Cannot prepare tx descriptor\n", aecp); + rc = AVDECC_INFLIGHT_TIMER_STOP; + } else { + rc = aecp_aem_net_tx_command(aecp, port, desc, entry->data.mac_dst, entry->data.len); + if (rc < 0) + rc = AVDECC_INFLIGHT_TIMER_STOP; + else + rc = AVDECC_INFLIGHT_TIMER_RESTART; + + entry->data.retried = 1; + } + } + + return rc; +} + +/** Sends an AECP AEM failure response on the network. + * Takes full ownership of the TX descriptor + * This function will take an inflight command PDU and update needed fields to send failure responses + * \return 0 on success or negative value otherwise. + * \param aecp AECP context the command is being sent from. + * \param pdu Pointer to the start of the sent AECP AEM PDU. + * \param port Pointer to the avdecc port on which the packet will be sent + * \param desc Network TX descriptor. + * \param mac_dst MAC address to send the command to. + * \param status Failure Status of the AECP AEM PDU. + * \param len Length of the AECP AEM PDU (after the AVTP header), can be modified on return. +*/ +static int aecp_aem_net_tx_inflight_response_failure(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, struct avdecc_port *port, struct net_tx_desc *desc, u8 *mac_dst, u8 status, u16 *len) +{ + int rc; + + switch (AECP_AEM_GET_CMD_TYPE(pdu)) { + case AECP_AEM_CMD_SET_CONTROL: + { + /* SET_CONTROL response failures should contain current value in descriptor */ + struct aecp_aem_set_get_control_pdu *set_control_rsp = (struct aecp_aem_set_get_control_pdu *)(pdu + 1); + void *values_rsp = set_control_rsp + 1; + struct entity *entity = container_of(aecp, struct entity, aecp); + struct control_descriptor *ctrl_desc; + int desc_values_len; + + ctrl_desc = aem_get_descriptor(entity->aem_descs, ntohs(set_control_rsp->descriptor_type), + ntohs(set_control_rsp->descriptor_index), NULL); + + if (!ctrl_desc) { + os_log(LOG_ERR, "aecp(%p) Control descriptor (type = %d, index = %d) not found.\n", + aecp, ntohs(set_control_rsp->descriptor_type), ntohs(set_control_rsp->descriptor_index)); + goto error; + } + + if ((desc_values_len = aecp_aem_control_desc_to_pdu(ctrl_desc, values_rsp, AVDECC_AECP_MAX_SIZE)) < 0) { + os_log(LOG_ERR, "aecp(%p) Cannot copy control descriptor values to pdu\n", aecp); + goto error; + } + + *len = desc_values_len + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu); + + rc = aecp_aem_net_tx_response(aecp, port, desc, status, mac_dst, *len); + + break; + } + case AECP_AEM_CMD_START_STREAMING: + case AECP_AEM_CMD_STOP_STREAMING: + case AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS: + case AECP_AEM_CMD_ADD_AUDIO_MAPPINGS: + case AECP_AEM_CMD_GET_AUDIO_MAP: + default: + rc = aecp_aem_net_tx_response(aecp, port, desc, status, mac_dst, *len); + break; + } + + return rc; +error: + net_tx_free(desc); + return -1; +} + +static int aecp_aem_inflight_application_timeout(struct inflight_ctx *entry) +{ + struct aecp_ctx *aecp = container_of(entry->list_head, struct aecp_ctx, inflight_application); + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_port *port = &entity->avdecc->port[entry->data.port_id]; + struct net_tx_desc *desc; + struct aecp_aem_pdu *pdu; + int rc = 0; + + os_log(LOG_DEBUG, "aecp(%p) inflight_ctx(%p) sequence id : %x\n", aecp, entry, entry->data.sequence_id); + + desc = aecp_net_tx_prepare(port, entry->data.pdu.buf, &entry->data.len, (void **)&pdu); + + if (!desc) { + os_log(LOG_ERR, "aecp(%p) Cannot prepare tx descriptor\n", aecp); + rc = AVDECC_INFLIGHT_TIMER_STOP; + } else { + /* Give Application some time (few seconds) before sending a failure status response */ + if (entry->data.retried >= AECP_CFG_MAX_AEM_IN_PROGRESS) { + u16 len = entry->data.len; + + rc = aecp_aem_net_tx_inflight_response_failure(aecp, pdu, port, desc, entry->data.mac_dst, AECP_AEM_ENTITY_MISBEHAVING, &len); + if (rc < 0) + os_log(LOG_ERR, "aecp(%p) Cannot send failure AEM_RESPONSE\n", aecp); + + rc = AVDECC_INFLIGHT_TIMER_STOP; + } else { /* Send an IN_PROGRESS response */ + rc = aecp_aem_net_tx_response(aecp, port, desc, AECP_AEM_IN_PROGRESS, entry->data.mac_dst, entry->data.len); + if (rc < 0) + rc = AVDECC_INFLIGHT_TIMER_STOP; + else + rc = AVDECC_INFLIGHT_TIMER_RESTART; + + entry->data.retried++; + } + } + + return rc; +} + +/** Sends an AECP AEM command on the network. + * + * Sends an AECP AEM command to the specified MAC address, and create an inflight entry to monitor/handle the response. + * Based on IEEE 1722.1-2013 section 9.2.2.3.2. + * + * Takes full ownership of the TX descriptor + * \return 0 on success or negative value otherwise. + * \param aecp AECP context the command is being sent from. + * \param port Pointer to the avdecc port on which the packet will be sent + * \param pdu Pointer to the start of the AECP AEM PDU within the network TX descriptor. + * \param desc Network TX descriptor. + * \param mac_dst MAC address to send the command to. + * \param len Length of the AECP AEM PDU (after the AVTP header). + * \param ipc IPC to forward any potential responses to. Will be stored in the inflight entry to be used on response reception. + */ +static int aecp_aem_send_command(struct aecp_ctx *aecp, struct avdecc_port *port, struct aecp_aem_pdu *pdu, + struct net_tx_desc *desc, u8 *mac_dst, u16 len, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct inflight_ctx *entry; + int rc = -1; + + os_log(LOG_DEBUG, "aecp(%p) pdu(%p) desc(%p) len(%u) ipc_tx(%p) seq_id(%d)\n", aecp, pdu, desc, len, ipc, aecp->sequence_id); + + entry = avdecc_inflight_get(entity); + + pdu->sequence_id = htons(aecp->sequence_id); + if (entry) { + entry->cb = aecp_aem_inflight_network_timeout; + entry->data.msg_type = AECP_AEM_COMMAND; + entry->data.retried = 0; + entry->data.sequence_id = aecp->sequence_id; + if (len > AVDECC_AECP_MAX_SIZE) { + os_log(LOG_ERR, "aecp(%p) Truncating PDU to fit inside inflight buffer, length above spec (%u > %zu).\n", aecp, len, AVDECC_AECP_MAX_SIZE); + len = AVDECC_AECP_MAX_SIZE; + } + entry->data.len = len; + os_memcpy(entry->data.pdu.buf, pdu, len); + entry->data.priv[0] = (uintptr_t)ipc; + entry->data.priv[1] = (uintptr_t)ipc_dst; + os_memcpy(entry->data.mac_dst, mac_dst, 6); + entry->data.port_id = port->port_id; + + if(avdecc_inflight_start(&aecp->inflight_network, entry, AECP_COMMANDS_TIMEOUT) < 0) + os_log(LOG_ERR, "aecp(%p) Could not start inflight\n", aecp); + else + rc = aecp_aem_net_tx_command(aecp, port, desc, mac_dst, len); + } + else { + os_log(LOG_ERR, "aecp(%p) Could not allocate inflight\n", aecp); + } + aecp->sequence_id++; + + return rc; +} + +/** Sends an AECP AEM response on the network. + * + * Sends an AECP AEM response to the specified MAC address on the set port and with the passed sequence id. + * + * Takes full ownership of the TX descriptor + * \return 0 on success or negative value otherwise. + * \param aecp AECP context the command is being sent from. + * \param port Pointer to the avdecc port on which the packet will be sent + * \param pdu Pointer to the start of the AECP AEM PDU within the network TX descriptor. + * \param desc Network TX descriptor. + * \param controller_id Controller entity ID which sent the command + * \param status Response status + * \param unsolicited 1 if the response is an unsolicited notification, 0 otherwise + * \param mac_dst MAC address to send the command to. + * \param len Length of the AECP AEM PDU (after the AVTP header). + * \param sequence_id Sequence ID of the response PDU + */ +static int aecp_aem_send_response(struct aecp_ctx *aecp, struct avdecc_port *port, struct aecp_aem_pdu *pdu, struct net_tx_desc *desc, u64 controller_id, + u16 sequence_id, u8 status, u8 unsolicited, u8 *mac_dst, u16 len) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + + os_log(LOG_DEBUG, "aecp(%p) pdu(%p) desc(%p) len(%u) seq_id(%d)\n", + aecp, pdu, desc, len, sequence_id); + + copy_64(&pdu->controller_entity_id, &controller_id); + copy_64(&pdu->entity_id, &entity->desc->entity_id); + pdu->sequence_id = htons(sequence_id); + + AECP_AEM_SET_U_CMD_TYPE(pdu, unsolicited, AECP_AEM_GET_CMD_TYPE(pdu)); + + return aecp_aem_net_tx_response(aecp, port, desc, status, mac_dst, len); +} + +/** Prepares an AECP AEM response from a previously allocated AECP PDU and and send it over the network. + * + * Allocates a TX descriptor and copies the content of the specified buffer pointing to the AECP PDU. + * Sends an AECP AEM response to the specified MAC address on the set port and with the passed sequence id. + * + * Does not take ownership of the specified buffer pointing to the AECP PDU. + * \return 0 on success or negative value otherwise. + * \param aecp AECP context the command is being sent from. + * \param port Pointer to the avdecc port on which the packet will be sent + * \param buf Pointer to the start of the AECP AEM PDU to be copied + * \param controller_id Controller entity ID which sent the command + * \param status Response status + * \param unsolicited 1 if the response is an unsolicited notification, 0 otherwise + * \param mac_dst MAC address to send the command to. + * \param len Length of the AECP AEM PDU (after the AVTP header). + * \param sequence_id Sequence ID of the response PDU + */ +static int aecp_aem_prepare_send_response(struct aecp_ctx *aecp, struct avdecc_port *port, void *buf, u64 controller_id, + u16 sequence_id, u8 status, u8 unsolicited, u8 *mac_dst, u16 len) +{ + struct net_tx_desc *tx_desc; + struct aecp_aem_pdu *aecp_cmd; + + os_log(LOG_DEBUG, "aecp(%p) pdu(%p) len(%u)\n", aecp, buf, len); + + tx_desc = aecp_net_tx_prepare(port, buf, &len, (void **)&aecp_cmd); + if (!tx_desc) { + os_log(LOG_ERR, "aecp(%p) Cannot alloc tx descriptor\n", aecp); + return -1; + } + + return aecp_aem_send_response(aecp, port, aecp_cmd, tx_desc, controller_id, sequence_id, status, unsolicited, mac_dst, len); +} + +/** Sends an AECP AEM synchronous unsolicited notification on the network. + * + * Sends an AECP AEM synchronous (result to a successful response to a command from a controller) unsolicited notification to + * the registered controllers (except the one which sent the command) + * + * Does not take ownership of the specified buffer pointing to the AECP PDU. + * \return 0 on success or negative value otherwise. + * \param aecp Pointer to the aecp context struct + * \param aecp_rsp Pointer to the AECP AEM PDU containing the successful response. + * \param controller_id Controller entity ID which sent the command (to be excluded from the notification if registered) + * \param len Length of the AECP AEM PDU (after the AVTP header). + */ +static int aecp_aem_send_sync_unsolicited_notification(struct aecp_ctx *aecp, struct aecp_aem_pdu *aecp_rsp, u64 controller_id, u16 len) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct unsolicited_ctx *unsolicited_entry; + struct list_head *list_entry; + + list_entry = list_first(&aecp->unsolicited); + while (list_entry != &aecp->unsolicited) { + struct avdecc_port *port; + + unsolicited_entry = container_of(list_entry, struct unsolicited_ctx, list); + port = &avdecc->port[unsolicited_entry->port_id]; + + /* Do not send synchronous unsolicited notification to the controller sending the command */ + if (controller_id != unsolicited_entry->controller_id) { + + if (aecp_aem_prepare_send_response(aecp, port, aecp_rsp, unsolicited_entry->controller_id, unsolicited_entry->sequence_id, AECP_AEM_SUCCESS, 1, + unsolicited_entry->mac_dst, len) < 0) { + os_log(LOG_ERR, "avdecc(%p) port(%u) couldn't send notification to registered controller (%016"PRIx64")\n", + avdecc, unsolicited_entry->port_id, ntohll(unsolicited_entry->controller_id)); + goto err; + } + + unsolicited_entry->sequence_id++; + } + + list_entry = list_next(list_entry); + } + + return 0; + +err: + return -1; +} + +static int aecp_application_inflight_add(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, u16 data_len, u8 *mac_src, unsigned int port_id) +{ + struct inflight_ctx *entry; + int status = AECP_AEM_SUCCESS; + struct entity *entity = container_of(aecp, struct entity, aecp); + + entry = avdecc_inflight_get(entity); + if (!entry) { + os_log(LOG_ERR, "aecp(%p) Could not allocate inflight\n", aecp); + status = AECP_AEM_NO_RESOURCES; + goto exit; + } + + entry->cb = aecp_aem_inflight_application_timeout; + entry->data.msg_type = AECP_AEM_COMMAND; + entry->data.retried = 0; + entry->data.sequence_id = ntohs(pdu->sequence_id); + /* IN_PROGRESS responses will be sent with the original PDU, so the length comes from there. */ + entry->data.len = data_len; + if (entry->data.len > AVDECC_AECP_MAX_SIZE) { + os_log(LOG_ERR, "aecp(%p) Truncating PDU to fit inside inflight buffer, length above spec (%u > %zu).\n", aecp, entry->data.len, AVDECC_AECP_MAX_SIZE); + entry->data.len = AVDECC_AECP_MAX_SIZE; + } + + os_memcpy(entry->data.pdu.buf, pdu, entry->data.len); + entry->data.priv[0] = 0; + entry->data.priv[1] = 0; + /* response would be sent back on the same port we received the command from */ + entry->data.port_id = port_id; + os_memcpy(entry->data.mac_dst, mac_src, 6); + + if (avdecc_inflight_start(&aecp->inflight_application, entry, AECP_IN_PROGRESS_TIMEOUT) < 0) { + os_log(LOG_ERR, "aecp(%p) Could not start inflight\n", aecp); + status = AECP_AEM_ENTITY_MISBEHAVING; + goto exit; + } + +exit: + return status; +} + +/** Main AECP vendor unique command receive function + * Follows the AVDECC entity model state machine (9.2.2.3.1.4). + * \return 0 on success, negative otherwise + * \param aecp pointer to the AECP context + * \param pdu pointer to the AECP Vendor Unique Format PDU + * \param avtp_len length of the AVTP payload. + * \param mac_src source MAC address of the received PDU + * \param port_id avdecc port / interface index on which we received the PDU + */ +static int aecp_vendor_specific_received_command(struct aecp_ctx *aecp, struct aecp_vuf_pdu *pdu, u16 avtp_len, u8 *mac_src, unsigned int port_id) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_port *port_rsp = &entity->avdecc->port[port_id]; + struct aecp_mvu_pdu *mvu_rsp_pdu = NULL; + struct aecp_mvu_pdu *mvu_cmd_pdu = NULL; + struct net_tx_desc *desc_rsp; + u8 status; + u16 cmd_type; + u16 len; + int rc = 0; + + if (!entity->milan_mode || os_memcmp(pdu->protocol_id, aecp_mvu_protocol_id, 6)) { + os_log(LOG_ERR, "aecp(%p) Unsupported Vendor Specific protocol_id %02X-%02X-%02X-%02X-%02X-%02X \n", + aecp, pdu->protocol_id[0], pdu->protocol_id[1], pdu->protocol_id[2], pdu->protocol_id[3], pdu->protocol_id[4], pdu->protocol_id[5]); + rc = -1; + goto exit; + } + + len = sizeof(struct aecp_mvu_pdu); //data size after AVTP control hdr + mvu_cmd_pdu = (struct aecp_mvu_pdu *)pdu; + + desc_rsp = aecp_net_tx_prepare(port_rsp, pdu, &len, (void **)&mvu_rsp_pdu); //FIXME check if we can re-use same buf + if (!desc_rsp) { + os_log(LOG_ERR, "aecp(%p) Cannot alloc tx descriptor\n", aecp); + rc = -1; + goto exit; + } + + cmd_type = AECP_MVU_GET_CMD_TYPE(mvu_cmd_pdu); + + os_log(LOG_DEBUG, "aecp(%p) command (%x, %s) seq_id(%d)\n", aecp, cmd_type, aecp_mvu_cmdtype2string(cmd_type), ntohs(pdu->sequence_id)); + + switch (cmd_type) { + case AECP_MVU_CMD_GET_MILAN_INFO: + { + struct aecp_mvu_get_milan_info_rsp_pdu *get_milan_rsp = (struct aecp_mvu_get_milan_info_rsp_pdu *)(mvu_rsp_pdu + 1); + + get_milan_rsp->protocol_version = htonl(MILAN_PROTOCOL_VERSION); + get_milan_rsp->features_flags = htonl(0); + get_milan_rsp->certification_version = htonl(MILAN_CERTIFICATION_VERSION(1, 1, 0, 0)); + + status = AECP_AEM_SUCCESS; + len += sizeof(struct aecp_mvu_get_milan_info_rsp_pdu); + + break; + } + default: + status = AECP_AEM_NOT_IMPLEMENTED; + break; + + } + + rc = aecp_mvu_net_tx_response(aecp, port_rsp, desc_rsp, status, mac_src, len); + +exit: + return rc; +} + +/** Build a GET_STREAM_INFO response + * \return status, AECP_AEM status + * \param entity, pointer to the entity + * \param aecp_rsp, pointer to the start of the response pdu + * \param len, pointer to the len of the pdu + * \param descriptor_type, type of the aem_desc + * \param descriptor_index, id of the aem_desc + */ +static u8 aecp_aem_get_stream_info_response(struct entity *entity, struct aecp_aem_pdu *aecp_rsp, u16 *len, u16 descriptor_type, u16 descriptor_index) +{ + struct aecp_aem_get_stream_info_rsp_pdu *get_stream_info_rsp = (struct aecp_aem_get_stream_info_rsp_pdu *)(aecp_rsp + 1); + struct aecp_aem_milan_get_stream_info_rsp_pdu *get_stream_info_milan_rsp = (struct aecp_aem_milan_get_stream_info_rsp_pdu *)(aecp_rsp + 1); + struct stream_descriptor *desc; + void *dynamic_desc; + u8 status = AECP_AEM_SUCCESS; + + if (!entity->milan_mode) { + *len += sizeof(struct aecp_aem_get_stream_info_rsp_pdu); + os_memset(get_stream_info_rsp, 0, sizeof(struct aecp_aem_get_stream_info_rsp_pdu)); + } else { + *len += sizeof(struct aecp_aem_milan_get_stream_info_rsp_pdu); + os_memset(get_stream_info_milan_rsp, 0, sizeof(struct aecp_aem_milan_get_stream_info_rsp_pdu)); + } + + get_stream_info_rsp->descriptor_type = htons(descriptor_type); + get_stream_info_rsp->descriptor_index = htons(descriptor_index); + + if (descriptor_type != AEM_DESC_TYPE_STREAM_INPUT && descriptor_type != AEM_DESC_TYPE_STREAM_OUTPUT) { + status = AECP_AEM_BAD_ARGUMENTS; + goto exit; + } + + desc = aem_get_descriptor(entity->aem_descs, descriptor_type, descriptor_index, NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + goto exit; + } + + dynamic_desc = aem_get_descriptor(entity->aem_dynamic_descs, descriptor_type, descriptor_index, NULL); + if (!dynamic_desc) { + status = AECP_AEM_ENTITY_MISBEHAVING; + goto exit; + } + + if (!entity->milan_mode) { + status = AECP_AEM_NOT_SUPPORTED; /* FIXME add support for GET_STREAM_INFO for IEEE mode */ + goto exit; + } + + copy_64(&get_stream_info_rsp->stream_format, &desc->current_format); + get_stream_info_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_FORMAT_VALID); + + /* STREAM_INPUT */ + if (descriptor_type == AEM_DESC_TYPE_STREAM_INPUT) { + struct stream_input_dynamic_desc *in_dynamic_desc = (struct stream_input_dynamic_desc *)dynamic_desc; + + /* Per AVNU.IO.CONTROL 7.3.10.1 (MSRP_ACC_LAT_VALID) (REGISTERING) */ + if (in_dynamic_desc->u.milan.srp_stream_status != NO_TALKER) { + get_stream_info_milan_rsp->msrp_accumulated_latency = htonl(in_dynamic_desc->u.milan.msrp_accumulated_latency); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_MSRP_ACC_LAT_VALID); + + get_stream_info_milan_rsp->flags_ex |= htonl(AECP_STREAM_FLAG_EX_REGISTERING); + } + + if (ACMP_MILAN_IS_LISTENER_SINK_BOUND(in_dynamic_desc)) { + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_FAST_CONNECT); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_SAVED_STATE); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_BOUND); + + if ((in_dynamic_desc->flags & htons(ACMP_FLAG_STREAMING_WAIT)) != 0) { + /* Bound and stopped */ + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAMING_WAIT); + } + } + + if (ACMP_MILAN_IS_LISTENER_SINK_SETTLED(in_dynamic_desc)) { + /* Shall be the values from the ACMP_PROBE_TX_RESPONSE from the talker */ + copy_64(&get_stream_info_milan_rsp->stream_id, &in_dynamic_desc->stream_id); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_ID_VALID); + + get_stream_info_milan_rsp->stream_vlan_id = in_dynamic_desc->stream_vlan_id; + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_VLAN_ID_VALID); + + os_memcpy(get_stream_info_milan_rsp->stream_dest_mac, in_dynamic_desc->stream_dest_mac, 6); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_DEST_MAC_VALID); + } + + /* Per AVNU.IO.CONTROL 7.3.10.1 (REGISTERING_FAILED and MSRP_FAILURE_VALID) */ + if (in_dynamic_desc->u.milan.srp_stream_status == FAILED) { + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_REGISTERING_FAILED); + + get_stream_info_milan_rsp->msrp_failure_code = in_dynamic_desc->u.milan.failure.failure_code; + copy_64(&get_stream_info_milan_rsp->msrp_failure_bridge_id, in_dynamic_desc->u.milan.failure.bridge_id); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_MSRP_FAILURE_VALID); + } + + get_stream_info_milan_rsp->pbsta = in_dynamic_desc->u.milan.probing_status; + get_stream_info_milan_rsp->acmpsta = in_dynamic_desc->u.milan.acmp_status; + + /* STREAM_OUTPUT */ + } else if (descriptor_type == AEM_DESC_TYPE_STREAM_OUTPUT) { + struct stream_output_dynamic_desc *out_dynamic_desc = (struct stream_output_dynamic_desc *)dynamic_desc; + + /* Per AVNU.IO.CONTROL 7.3.10.2 (REGISTERING) */ + if (out_dynamic_desc->u.milan.srp_listener_status != NO_LISTENER) { + get_stream_info_milan_rsp->flags_ex |= htonl(AECP_STREAM_FLAG_EX_REGISTERING); + } + + /* Per AVNU.IO.CONTROL 7.3.10.2 (REGISTERING_FAILED) */ + if (out_dynamic_desc->u.milan.srp_listener_status == FAILED_LISTENER) { + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_REGISTERING_FAILED); + } + + copy_64(&get_stream_info_rsp->stream_id, &out_dynamic_desc->stream_id); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_ID_VALID); + + get_stream_info_milan_rsp->stream_vlan_id = out_dynamic_desc->stream_vlan_id; + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_VLAN_ID_VALID); + + if (!is_invalid_mac_addr(out_dynamic_desc->stream_dest_mac)) { + os_memcpy(get_stream_info_milan_rsp->stream_dest_mac, out_dynamic_desc->stream_dest_mac, 6); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_DEST_MAC_VALID); + } + + if (out_dynamic_desc->u.milan.srp_talker_declaration_type == TALKER_FAILED) { + get_stream_info_milan_rsp->msrp_failure_code = out_dynamic_desc->u.milan.failure.failure_code; + copy_64(&get_stream_info_milan_rsp->msrp_failure_bridge_id, out_dynamic_desc->u.milan.failure.bridge_id); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_MSRP_FAILURE_VALID); + } + + get_stream_info_milan_rsp->msrp_accumulated_latency = htonl(out_dynamic_desc->presentation_time_offset); + get_stream_info_milan_rsp->flags |= htonl(AECP_STREAM_FLAG_MSRP_ACC_LAT_VALID); + } + +exit: + return status; +} + +static u8 aecp_aem_get_counters_response(struct entity *entity, struct aecp_aem_pdu *aecp_rsp, u16 *len, u16 descriptor_type, u16 descriptor_index) +{ + struct aecp_aem_get_counters_rsp_pdu *get_counters_rsp = (struct aecp_aem_get_counters_rsp_pdu *)(aecp_rsp + 1); + void *desc; + u32 counters_valid; + u8 status = AECP_AEM_SUCCESS; + + *len += sizeof(struct aecp_aem_get_counters_rsp_pdu); + os_memset(get_counters_rsp, 0, sizeof(*get_counters_rsp)); + + get_counters_rsp->descriptor_type = htons(descriptor_type); + get_counters_rsp->descriptor_index = htons(descriptor_index); + + if (IS_VALID_GET_COUNTERS_DESCRIPTOR_TYPE(descriptor_type) || (entity->milan_mode && descriptor_type == AEM_DESC_TYPE_STREAM_OUTPUT)) { + desc = aem_get_descriptor(entity->aem_descs, descriptor_type, descriptor_index, NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + goto exit; + } + } else { + status = AECP_AEM_BAD_ARGUMENTS; + goto exit; + } + + /* + * FIXME + * Complete missing counters, set to 0 for now + */ + switch (descriptor_type) { + case AEM_DESC_TYPE_AVB_INTERFACE: + { + struct avb_interface_dynamic_desc *avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, descriptor_type, descriptor_index, NULL); + if (!avb_itf_dynamic) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + + counters_valid = ((1 << AECP_AEM_COUNTER_AVB_INTERFACE_LINK_UP) + | (1 << AECP_AEM_COUNTER_AVB_INTERFACE_LINK_DOWN) + | (1 << AECP_AEM_COUNTER_AVB_INTERFACE_GPTP_GM_CHANGED)); + + get_counters_rsp->counters_valid = htonl(counters_valid); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_AVB_INTERFACE_LINK_UP] = htonl(avb_itf_dynamic->link_up); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_AVB_INTERFACE_LINK_DOWN] = htonl(avb_itf_dynamic->link_down); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_AVB_INTERFACE_GPTP_GM_CHANGED] = htonl(avb_itf_dynamic->gptp_gm_changed); + } + break; + + case AEM_DESC_TYPE_CLOCK_DOMAIN: + counters_valid = (1 << AECP_AEM_COUNTER_CLOCK_DOMAIN_LOCKED) | (1 << AECP_AEM_COUNTER_CLOCK_DOMAIN_UNLOCKED); + + get_counters_rsp->counters_valid = htonl(counters_valid); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_CLOCK_DOMAIN_LOCKED] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_CLOCK_DOMAIN_UNLOCKED] = htonl(0); + break; + + case AEM_DESC_TYPE_STREAM_INPUT: + counters_valid = ((1 << AECP_AEM_COUNTER_STREAM_INPUT_MEDIA_LOCKED) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_MEDIA_UNLOCKED) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_STREAM_RESET) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_SEQ_NUM_MISMATCH) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_MEDIA_RESET) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_TIMESTAMP_UNCERTAIN) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_UNSUPPORTED_FORMAT) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_LATE_TIMESTAMP) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_EARLY_TIMESTAMP) + | (1 << AECP_AEM_COUNTER_STREAM_INPUT_FRAMES_RX)); + + get_counters_rsp->counters_valid = htonl(counters_valid); + + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_MEDIA_LOCKED] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_MEDIA_UNLOCKED] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_STREAM_RESET] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_SEQ_NUM_MISMATCH] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_MEDIA_RESET] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_TIMESTAMP_UNCERTAIN] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_UNSUPPORTED_FORMAT] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_LATE_TIMESTAMP] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_EARLY_TIMESTAMP] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_INPUT_FRAMES_RX] = htonl(0); + break; + + case AEM_DESC_TYPE_STREAM_OUTPUT: + counters_valid = ((1 << AECP_AEM_COUNTER_STREAM_OUTPUT_STREAM_START) + | (1 << AECP_AEM_COUNTER_STREAM_OUTPUT_STREAM_STOP) + | (1 << AECP_AEM_COUNTER_STREAM_OUTPUT_MEDIA_RESET) + | (1 << AECP_AEM_COUNTER_STREAM_OUTPUT_TIMESTAMP_UNCERTAIN) + | (1 << AECP_AEM_COUNTER_STREAM_OUTPUT_FRAMES_TX)); + + get_counters_rsp->counters_valid = htonl(counters_valid); + + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_OUTPUT_STREAM_START] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_OUTPUT_STREAM_STOP] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_OUTPUT_MEDIA_RESET] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_OUTPUT_TIMESTAMP_UNCERTAIN] = htonl(0); + get_counters_rsp->counters_block[AECP_AEM_COUNTER_STREAM_OUTPUT_FRAMES_TX] = htonl(0); + break; + } + +exit: + return status; +} + +static u8 aecp_aem_get_as_path_response(struct entity *entity, struct aecp_aem_pdu *aecp_rsp, u16 *len, u16 descriptor_index) +{ + struct aecp_aem_get_as_path_rsp_pdu *get_as_path_rsp = (struct aecp_aem_get_as_path_rsp_pdu *)(aecp_rsp + 1); + struct ptp_clock_identity *path_sequence_rsp = (struct ptp_clock_identity *)(get_as_path_rsp + 1); + struct avb_interface_dynamic_desc *avb_itf_dynamic; + u8 status = AECP_AEM_SUCCESS; + + *len += sizeof(struct aecp_aem_get_as_path_rsp_pdu); + os_memset(get_as_path_rsp, 0, sizeof(struct aecp_aem_get_as_path_rsp_pdu)); + + get_as_path_rsp->descriptor_index = htons(descriptor_index); + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, descriptor_index, NULL); + if (!avb_itf_dynamic) { + status = AECP_AEM_ENTITY_MISBEHAVING; + goto exit; + } + + *len += (avb_itf_dynamic->num_ptlv_entries * sizeof(struct ptp_clock_identity)); + get_as_path_rsp->count = htons(avb_itf_dynamic->num_ptlv_entries); + + os_memcpy(path_sequence_rsp, &avb_itf_dynamic->path_sequence , avb_itf_dynamic->num_ptlv_entries * sizeof(struct ptp_clock_identity)); + +exit: + return status; +} + +/** Check if there is a registered controller with different controller id than the one requesting the command + * + * \return true if there is at least another controller than the one requesting the command registered, false otherwise. + * \param aecp AECP context the command is being sent from. + * \param requesting_controller_id ID of the controller requesting the command + */ +static bool aecp_need_sync_unsolicited_notifications(struct aecp_ctx *aecp, u64 requesting_controller_id) +{ + struct list_head *list_entry; + struct unsolicited_ctx *unsolicited_entry; + + list_entry = list_first(&aecp->unsolicited); + + while (list_entry != &aecp->unsolicited) { + unsolicited_entry = container_of(list_entry, struct unsolicited_ctx, list); + + if (requesting_controller_id != unsolicited_entry->controller_id) + return true; + + list_entry = list_next(list_entry); + } + + return false; +} + +/** Send an unsolicited notification to registered controllers + * + * Takes ownership of the specified buffer pointing to the AECP PDU. + * + * \return 0 on success or negative value otherwise. + * \param aecp AECP context the command is being sent from. + * \param desc Pointer to the net descriptor + * \param aecp_pdu Pointer to the AECP PDU buffer + * \param excluded_controller_id Controller entity ID which sent the command + * \param len Length of the AECP AEM PDU (after the AVTP header). + */ +static int aecp_aem_send_unsolicited_notification(struct aecp_ctx *aecp, struct net_tx_desc *desc, struct aecp_aem_pdu *aecp_pdu , u64 excluded_controller_id, u16 len) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct list_head *list_entry; + struct unsolicited_ctx *unsolicited_entry; + u16 notification_type = AECP_AEM_GET_CMD_TYPE(aecp_pdu); + + /* If no registered controller, exit (and free descriptor) early */ + if (list_empty(&aecp->unsolicited)) + goto free_and_exit; + + /* Get the first controller in the list. We have at least one in the list */ + list_entry = list_first(&aecp->unsolicited); + + do { + struct avdecc_port *port_rsp; + + unsolicited_entry = container_of(list_entry, struct unsolicited_ctx, list); + + port_rsp = &entity->avdecc->port[unsolicited_entry->port_id]; + + list_entry = list_next(list_entry); + + /* Don't send the notification to the controller generating it in case of a sync unsolicited notification */ + if (unsolicited_entry->controller_id == excluded_controller_id) + continue; + + /* Always clone the original descriptor using the right tx port then send it. */ + if (aecp_aem_prepare_send_response(aecp, port_rsp, aecp_pdu, unsolicited_entry->controller_id, + unsolicited_entry->sequence_id, AECP_AEM_SUCCESS, 1, unsolicited_entry->mac_dst, len) < 0) { + + os_log(LOG_ERR,"aecp(%p) port(%u) couldn't prepare and send unsolicited notification (%x, %s) to controller(%016"PRIx64").\n", + aecp, unsolicited_entry->port_id, notification_type, aecp_aem_cmdtype2string(notification_type), ntohll(unsolicited_entry->controller_id)); + + goto err_prepare_rsp; + } + + /* Incrementing the sequence_id per AVNU.IO.CONTROL 7.5.1 */ + unsolicited_entry->sequence_id++; + + os_log(LOG_DEBUG,"aecp(%p) port(%u) sent unsolicited notification (%x, %s) to controller(%016"PRIx64").\n", + aecp, unsolicited_entry->port_id, notification_type, aecp_aem_cmdtype2string(notification_type), ntohll(unsolicited_entry->controller_id)); + + } while (list_entry != &aecp->unsolicited); + +free_and_exit: + /* Always free the original descriptor. */ + net_tx_free(desc); + + return 0; + +err_prepare_rsp: + net_tx_free(desc); + + return -1; +} + +/** Send a synchronous unsolicited notification + * + * Takes ownership of the specified buffer pointing to the AECP PDU. + * + * \return 0 on success or negative value otherwise. + * \param aecp AECP context the command is being sent from. + * \param desc Pointer to the net descriptor + * \param aecp_pdu Pointer to the AECP PDU buffer + * \param controller_id Controller entity ID which sent the command + * \param len Length of the AECP AEM PDU (after the AVTP header). + */ +static int aecp_aem_send_sync_unsolicited_notification_full(struct aecp_ctx *aecp, struct net_tx_desc *desc, struct aecp_aem_pdu *aecp_pdu, u64 controller_id, u16 len) +{ + return aecp_aem_send_unsolicited_notification(aecp, desc, aecp_pdu, controller_id, len); +} + +/** Sends an AECP AEM asynchronous unsolicited notification on the network to the registered controllers. + * Notifies changes in the state/dynamic descriptors of the entity. + * Allocate and fill the AECP PDU buffer according to the notification type. + * + * \return int, 0 if successful -1 otherwise + * \param aecp, pointer to the aecp context + * \param notification_type, type of the notification (IEEE Std 1722.1-2013 7.5.2 for the list of available unsolicited notification types) + * \param descriptor_type, type of the aem_desc + * \param descriptor_index, id of the aem_desc + */ +int aecp_aem_send_async_unsolicited_notification(struct aecp_ctx *aecp, u16 notification_type, u16 descriptor_type, u16 descriptor_index) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct unsolicited_ctx *unsolicited_entry; + struct list_head *list_entry; + struct net_tx_desc *desc = NULL; + struct aecp_aem_pdu *aecp_pdu = NULL; + u16 len = sizeof(struct aecp_aem_pdu); + struct avdecc_port *port_rsp; + void *buf; + u8 status; + + if (list_empty(&aecp->unsolicited)) + goto exit; + + /* Allocate a network descriptor using the port response of the first controller + * in the list (there is at least one) to contruct the response buffer. aecp_aem_send_unsolicited_notification() + * will clone it using the right port for each controller anyway. + */ + list_entry = list_first(&aecp->unsolicited); + unsolicited_entry = container_of(list_entry, struct unsolicited_ctx, list); + port_rsp = &entity->avdecc->port[unsolicited_entry->port_id]; + + desc = net_tx_alloc(&port_rsp->net_tx, DEFAULT_NET_DATA_SIZE); + if (!desc) { + os_log(LOG_ERR,"aecp(%p): Cannot alloc net_tx\n", aecp); + goto err_tx_alloc; + } + + buf = NET_DATA_START(desc); + + aecp_pdu = (struct aecp_aem_pdu *)((char *)buf + OFFSET_TO_AECP); + + /* Set command type of the response */ + AECP_AEM_SET_U_CMD_TYPE(aecp_pdu, 1, notification_type); + + switch (notification_type) { + case AECP_AEM_CMD_GET_STREAM_INFO: + { + status = aecp_aem_get_stream_info_response(entity, aecp_pdu, &len, descriptor_type, descriptor_index); + break; + } + case AECP_AEM_CMD_GET_COUNTERS: + { + status = aecp_aem_get_counters_response(entity, aecp_pdu, &len, descriptor_type, descriptor_index); + break; + } + case AECP_AEM_CMD_GET_AS_PATH: + { + status = aecp_aem_get_as_path_response(entity, aecp_pdu, &len, descriptor_index); + break; + } + case AECP_AEM_CMD_LOCK_ENTITY: + { + struct aecp_aem_lock_entity_pdu *lock_rsp = (struct aecp_aem_lock_entity_pdu *)(aecp_pdu + 1); + struct entity_dynamic_desc *entity_dynamic; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_lock_entity_pdu); + os_memset(lock_rsp, 0, sizeof(struct aecp_aem_lock_entity_pdu)); + + lock_rsp->descriptor_type = htons(descriptor_type); + lock_rsp->descriptor_index = htons(descriptor_index); + + if (descriptor_type != AEM_DESC_TYPE_ENTITY) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + entity_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, descriptor_type, descriptor_index, NULL); + if (!entity_dynamic) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + if (entity_dynamic->lock_status == LOCKED) + copy_64(&lock_rsp->locked_id, &entity_dynamic->locking_controller_id); + else + lock_rsp->flags |= htonl(AECP_AEM_LOCK_UNLOCK); + + break; + } + default: + os_log(LOG_ERR,"aecp(%p): Unknown notification_type(%x) or not yet implemented for the unsolicited notification\n", aecp, notification_type); + goto err; + } + + if (status != AECP_AEM_SUCCESS) { + os_log(LOG_ERR,"aecp(%p): Failed to get successful async unsolicited notification for command (%x, %s), status %u\n", + aecp, notification_type, aecp_aem_cmdtype2string(notification_type), status); + goto err; + } + + if (aecp_aem_send_unsolicited_notification(aecp, desc, aecp_pdu, 0, len) < 0) { + os_log(LOG_ERR,"aecp(%p): Couldn't send the async unsolicited notification (%x, %s)\n", + aecp, notification_type, aecp_aem_cmdtype2string(notification_type)); + goto err_send_rsp; + } + +exit: + return 0; + +err: + net_tx_free(desc); + +err_send_rsp: +err_tx_alloc: + return -1; +} + +/** Start a timer to send an GET_COUNTERS async unsolicited notification after a short delay if the timer is not currently running + * \return none + * \paran entity + * \param port_id + */ +void aecp_milan_register_get_counters_async_notification(struct entity *entity, unsigned int port_id) +{ + struct avb_interface_dynamic_desc *avb_itf_dynamic; + + if (!entity->milan_mode) + goto exit; + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + + if (!avb_itf_dynamic) + goto exit; + + if (!timer_is_running(&avb_itf_dynamic->u.milan.async_get_counters_unsolicited_notification_timer)) + timer_start(&avb_itf_dynamic->u.milan.async_get_counters_unsolicited_notification_timer, AECP_MILAN_GET_COUNTERS_ASYNC_UNSOLICITED_NOTIFICATION_MS); + +exit: + return; +} + +/** This function directly sends a GET_AS_PATH async unsolicited notification, if no previous notification has been sent in the past timer period (i.e timer not running). + * Otherwise, it just registers a new notification to be sent at timer expiration. + * That limits the GET_AS_PATH notifications' rate to one per configured timer period (currently one per second). + * \return none + * \paran entity + * \param port_id + */ +void aecp_trigger_async_get_as_path_notification(struct entity *entity, unsigned int port_id) +{ + struct avb_interface_dynamic_desc *avb_itf_dynamic; + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + + if (!avb_itf_dynamic) + goto exit; + + if (!avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer_running) { + aecp_aem_send_async_unsolicited_notification(&entity->aecp, AECP_AEM_CMD_GET_AS_PATH, AEM_DESC_TYPE_AVB_INTERFACE, port_id); + + avb_itf_dynamic->async_get_as_path_unsolicited_notification_pending = false; + avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer_running = true; + + timer_start(&avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer, AECP_GET_AS_PATH_ASYNC_UNSOLICITED_NOTIFICATION_MS); + } else { + avb_itf_dynamic->async_get_as_path_unsolicited_notification_pending = true; + } + +exit: + return; +} + +/** Main AECP AEM receive function for controller's AECP command + * Follows the AVDECC entity model state machine (9.2.2.3.1.4). + * \return 0 on success, negative otherwise + * \param aecp pointer to the AECP context + * \param pdu pointer to the AECP PDU + * \param avtp_len length of the AVTP payload. + * \param mac_src source MAC address of the received PDU + * \param port_id avdecc port / interface index on which we received the PDU + */ +static int aecp_aem_received_controller_command(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, u16 avtp_len, u8 *mac_src, unsigned int port_id) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_port *port_rsp = &entity->avdecc->port[port_id]; + u64 entity_id = pdu->entity_id; + u64 controller_entity_id = pdu->controller_entity_id; + struct aecp_aem_pdu *aecp_rsp = NULL; + struct net_tx_desc *desc_rsp; + struct unsolicited_ctx *unsolicited_entry; + u8 status; + u16 cmd_type; + u16 len = sizeof(struct aecp_aem_pdu); //data size after AVTP control hdr + int rc = 0; + bool send_unsolicited_notification = false; /* to send a synchronous unsolicited notification to registered controllers if command is fully handled here and made changes to the entity */ + + desc_rsp = aecp_net_tx_prepare(port_rsp, pdu, &len, (void **)&aecp_rsp); //FIXME check if we can re-use same buf + if (!desc_rsp) { + os_log(LOG_ERR, "aecp(%p) Cannot alloc tx descriptor\n", aecp); + rc = -1; + goto exit; + } + + cmd_type = AECP_AEM_GET_CMD_TYPE(pdu); + + os_log(LOG_DEBUG, "aecp(%p) command (%x, %s) seq_id(%d)\n", aecp, cmd_type, aecp_aem_cmdtype2string(cmd_type), ntohs(pdu->sequence_id)); + + switch (cmd_type) { + case AECP_AEM_CMD_READ_DESCRIPTOR: + { + struct aecp_aem_read_desc_cmd_pdu *read_desc_cmd = (struct aecp_aem_read_desc_cmd_pdu *)(pdu + 1); + void *desc; + u16 desc_len; + + desc = aem_get_descriptor(entity->aem_descs, ntohs(read_desc_cmd->descriptor_type), ntohs(read_desc_cmd->descriptor_index), &desc_len); + if (desc) { + struct aecp_aem_read_desc_rsp_pdu *read_desc_rsp = (struct aecp_aem_read_desc_rsp_pdu *)(aecp_rsp + 1); + + os_memcpy((read_desc_rsp + 1), desc, desc_len); + read_desc_rsp->configuration_index = read_desc_cmd->configuration_index; //FIXME config handling + status = AECP_AEM_SUCCESS; + len += (desc_len + sizeof(struct aecp_aem_read_desc_rsp_pdu)); + } + else { + struct aecp_aem_read_desc_cmd_pdu *read_desc_fail = (struct aecp_aem_read_desc_cmd_pdu *)(aecp_rsp + 1); + + read_desc_fail->descriptor_type = read_desc_cmd->descriptor_type; + read_desc_fail->descriptor_index = read_desc_cmd->descriptor_index; + read_desc_fail->configuration_index = read_desc_cmd->configuration_index; //FIXME config handling + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + len += sizeof(struct aecp_aem_read_desc_cmd_pdu); + } + break; + } + case AECP_AEM_CMD_ENTITY_AVAILABLE: + { + status = AECP_AEM_SUCCESS; + break; + } + case AECP_AEM_CMD_ACQUIRE_ENTITY: + { + struct aecp_aem_acquire_entity_pdu *acquire_cmd = (struct aecp_aem_acquire_entity_pdu *)(pdu + 1); + struct aecp_aem_acquire_entity_pdu *acquire_rsp = (struct aecp_aem_acquire_entity_pdu *)(aecp_rsp + 1); + struct entity_dynamic_desc *entity_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_ENTITY, 0, NULL); + + len += sizeof(struct aecp_aem_acquire_entity_pdu); + os_memset(acquire_rsp, 0, sizeof(struct aecp_aem_acquire_entity_pdu)); + + acquire_rsp->descriptor_type = acquire_cmd->descriptor_type; + acquire_rsp->descriptor_index = acquire_cmd->descriptor_index; + + if (entity->milan_mode) { + /* AVNU.IO.CONTROL 7.3.1 */ + status = AECP_AEM_NOT_IMPLEMENTED; + break; + } + + if (acquire_cmd->descriptor_type != ntohs(AEM_DESC_TYPE_ENTITY)) { + /* IEEE 1722.1-2013 7.4.1.2 */ + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + if (acquire_cmd->flags & ntohl(AECP_AEM_ACQUIRE_RELEASE)) { + if (controller_entity_id == entity_dynamic->acquiring_controller_id) { + entity_dynamic->acquire_status = RELEASED; + status = AECP_AEM_SUCCESS; + os_log(LOG_INFO, "aecp(%p) Controller %"PRIx64" released entity %"PRIx64"\n", + aecp, ntohll(entity_dynamic->acquiring_controller_id), ntohll(entity_id)); + } + else + status = AECP_AEM_ENTITY_ACQUIRED; //FIXME send a CONTROLLER_AVAILABLE to owner + } + else { + if (entity_dynamic->acquire_status == RELEASED) { + entity_dynamic->acquire_status = ACQUIRED; + status = AECP_AEM_SUCCESS; + entity_dynamic->acquiring_controller_id = controller_entity_id; + os_log(LOG_INFO, "aecp(%p) Controller %"PRIx64" acquired entity %"PRIx64"\n", + aecp, ntohll(entity_dynamic->acquiring_controller_id), ntohll(entity_id)); + } + else { + if (controller_entity_id == entity_dynamic->acquiring_controller_id) + status = AECP_AEM_SUCCESS; /* aquired again by the same controller */ + else + status = AECP_AEM_ENTITY_ACQUIRED; + } + } + + if (entity_dynamic->acquire_status == ACQUIRED) + copy_64(&acquire_rsp->owner_id, &entity_dynamic->acquiring_controller_id); + break; + } + case AECP_AEM_CMD_LOCK_ENTITY: + { + struct aecp_aem_lock_entity_pdu *lock_cmd = (struct aecp_aem_lock_entity_pdu *)(pdu + 1); + struct aecp_aem_lock_entity_pdu *lock_rsp = (struct aecp_aem_lock_entity_pdu *)(aecp_rsp + 1); + struct entity_dynamic_desc *entity_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_ENTITY, 0, NULL); + + len += sizeof(struct aecp_aem_lock_entity_pdu); + os_memset(lock_rsp, 0, sizeof(struct aecp_aem_lock_entity_pdu)); + + lock_rsp->descriptor_type = lock_cmd->descriptor_type; + lock_rsp->descriptor_index = lock_cmd->descriptor_index; + + if (lock_cmd->descriptor_type != ntohs(AEM_DESC_TYPE_ENTITY)) { + status = AECP_AEM_NOT_SUPPORTED; + break; + } + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + break; + } + + if (lock_cmd->flags & ntohl(AECP_AEM_LOCK_UNLOCK)) { + if (entity_dynamic->lock_status == UNLOCKED) { + status = AECP_AEM_SUCCESS; /* Already Unlocked */ + } else { + if (controller_entity_id == entity_dynamic->locking_controller_id) { + entity_dynamic->lock_status = UNLOCKED; + status = AECP_AEM_SUCCESS; + send_unsolicited_notification = true; + + if (timer_is_running(&entity_dynamic->lock_timer)) + timer_stop(&entity_dynamic->lock_timer); + os_log(LOG_INFO, "aecp(%p) Controller %"PRIx64" unlocked entity %"PRIx64"\n", + aecp, ntohll(controller_entity_id), ntohll(entity_id)); + } else { + status = AECP_AEM_ENTITY_LOCKED; + } + } + } else { + if (entity_dynamic->lock_status == UNLOCKED) { + entity_dynamic->lock_status = LOCKED; + status = AECP_AEM_SUCCESS; + send_unsolicited_notification = true; + + /* Start the 1 min lock timer. */ + timer_start(&entity_dynamic->lock_timer, AVDECC_CFG_ENTITY_LOCK_TIMER_MS); + entity_dynamic->locking_controller_id = controller_entity_id; + os_log(LOG_INFO, "aecp(%p) Controller %"PRIx64" locked entity %"PRIx64"\n", + aecp, ntohll(entity_dynamic->locking_controller_id), ntohll(entity_id)); + } else { + if (controller_entity_id == entity_dynamic->locking_controller_id) { + status = AECP_AEM_SUCCESS; /* locked again by the same controller, restart the locking timer */ + if (timer_is_running(&entity_dynamic->lock_timer)) + timer_stop(&entity_dynamic->lock_timer); + timer_start(&entity_dynamic->lock_timer, AVDECC_CFG_ENTITY_LOCK_TIMER_MS); + } else { + status = AECP_AEM_ENTITY_LOCKED; + } + } + } + + if (entity_dynamic->lock_status == LOCKED) + copy_64(&lock_rsp->locked_id, &entity_dynamic->locking_controller_id); + + break; + } + case AECP_AEM_CMD_SET_CONFIGURATION: + { + struct aecp_aem_set_configuration_pdu *set_configuration_cmd = (struct aecp_aem_set_configuration_pdu *)(pdu + 1); + struct aecp_aem_set_configuration_pdu *set_configuration_rsp = (struct aecp_aem_set_configuration_pdu *)(aecp_rsp + 1); + struct entity_descriptor *entity_desc = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_ENTITY, 0, NULL); + unsigned int num_streams, i; + + len += sizeof(struct aecp_aem_set_configuration_pdu); + os_memset(set_configuration_rsp, 0, sizeof(struct aecp_aem_set_configuration_pdu)); + + /* The response always contains the current value, even on failure (IEEE1722.1-2013 7.4.7.1). Init to current value, and change later on success if needed */ + set_configuration_rsp->configuration_index = entity_desc->current_configuration; + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + break; + } + + if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + break; + } + + num_streams = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT); + for (i = 0; i < num_streams; i++) { + if (acmp_is_stream_running(entity, AEM_DESC_TYPE_STREAM_INPUT, i)) { + status = AECP_AEM_STREAM_IS_RUNNING; + goto send_rsp; + } + } + + num_streams = aem_get_descriptor_max(entity->aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT); + for (i = 0; i < num_streams; i++) { + if (acmp_is_stream_running(entity, AEM_DESC_TYPE_STREAM_OUTPUT, i)) { + status = AECP_AEM_STREAM_IS_RUNNING; + goto send_rsp; + } + } + + /* FIXME only support one configuration currently */ + if (entity_desc->current_configuration == set_configuration_cmd->configuration_index) { + status = AECP_AEM_SUCCESS; + } else { + status = AECP_AEM_NOT_SUPPORTED; + } + + break; + } + case AECP_AEM_CMD_GET_CONFIGURATION: + { + struct aecp_aem_get_configuration_rsp_pdu *get_configuration_rsp = (struct aecp_aem_get_configuration_rsp_pdu *)(aecp_rsp + 1); + struct entity_descriptor *entity_desc = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_ENTITY, 0, NULL); + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_get_configuration_rsp_pdu); + os_memset(get_configuration_rsp, 0, sizeof(struct aecp_aem_get_configuration_rsp_pdu)); + + get_configuration_rsp->configuration_index = entity_desc->current_configuration; + + break; + } + case AECP_AEM_CMD_SET_STREAM_FORMAT: + { + struct aecp_aem_set_stream_format_pdu *set_stream_format_cmd = (struct aecp_aem_set_stream_format_pdu *)(pdu + 1); + struct aecp_aem_set_stream_format_pdu *set_stream_format_rsp = (struct aecp_aem_set_stream_format_pdu *)(aecp_rsp + 1); + u16 descriptor_type = ntohs(set_stream_format_cmd->descriptor_type); + struct stream_descriptor *stream_desc; + unsigned int i; + + len += sizeof(struct aecp_aem_set_stream_format_pdu); + os_memset(set_stream_format_rsp, 0, sizeof(struct aecp_aem_set_stream_format_pdu)); + + set_stream_format_rsp->descriptor_type = set_stream_format_cmd->descriptor_type; + set_stream_format_rsp->descriptor_index = set_stream_format_cmd->descriptor_index; + + if (descriptor_type != AEM_DESC_TYPE_STREAM_INPUT && descriptor_type != AEM_DESC_TYPE_STREAM_OUTPUT) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + stream_desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(set_stream_format_cmd->descriptor_index), NULL); + if (!stream_desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + /* The response always contains the current value, even on failure (IEEE1722.1-2013 7.4.9.1). Init to current value, and change later on success if needed */ + copy_64(&set_stream_format_rsp->stream_format, &stream_desc->current_format); + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + break; + } + + if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + break; + } + + if (acmp_is_stream_running(entity, descriptor_type, ntohs(set_stream_format_cmd->descriptor_index))) { + status = AECP_AEM_STREAM_IS_RUNNING; + break; + } + + status = AECP_AEM_NOT_SUPPORTED; + for (i = 0; i < ntohs(stream_desc->number_of_formats); i++) { + if (cmp_64(&set_stream_format_cmd->stream_format, &stream_desc->formats[i])) { + /* FIXME check audio mappings referencing channels of the old stream format per AVNU.IO.CONTROL 7.3.7 */ + /* Update the descriptor format and send the unsolicited notification (AVNU.IO.CONTROL 7.5.2) only on format change*/ + if (!cmp_64(&stream_desc->current_format, &set_stream_format_cmd->stream_format)) { + copy_64(&stream_desc->current_format, &set_stream_format_cmd->stream_format); + send_unsolicited_notification = true; + } + + copy_64(&set_stream_format_rsp->stream_format, &stream_desc->current_format); + + status = AECP_AEM_SUCCESS; + break; + } + } + + break; + } + case AECP_AEM_CMD_GET_STREAM_FORMAT: + { + struct aecp_aem_get_stream_format_cmd_pdu *get_stream_format_cmd = (struct aecp_aem_get_stream_format_cmd_pdu *)(pdu + 1); + struct aecp_aem_get_stream_format_rsp_pdu *get_stream_format_rsp = (struct aecp_aem_get_stream_format_rsp_pdu *)(aecp_rsp + 1); + u16 descriptor_type = ntohs(get_stream_format_cmd->descriptor_type); + struct stream_descriptor *stream_desc; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_get_stream_format_rsp_pdu); + os_memset(get_stream_format_rsp, 0, sizeof(struct aecp_aem_get_stream_format_rsp_pdu)); + + get_stream_format_rsp->descriptor_type = get_stream_format_cmd->descriptor_type; + get_stream_format_rsp->descriptor_index = get_stream_format_cmd->descriptor_index; + + if (descriptor_type != AEM_DESC_TYPE_STREAM_INPUT && descriptor_type != AEM_DESC_TYPE_STREAM_OUTPUT) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + stream_desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(get_stream_format_cmd->descriptor_index), NULL); + if (!stream_desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + copy_64(&get_stream_format_rsp->stream_format, &stream_desc->current_format); + + break; + } + case AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION: + { + if (aecp_unsolicited_add(aecp, mac_src, controller_entity_id, port_id) < 0) { + os_log(LOG_ERR, "aecp(%p) Reached max unsolicited registrations (%d), ignoring REGISTER_UNSOLICITED_NOTIFICATION from controller %"PRIx64".\n", + aecp, aecp->max_unsolicited_registrations, ntohll(controller_entity_id)); + + status = AECP_AEM_NO_RESOURCES; + } else + status = AECP_AEM_SUCCESS; + break; + } + case AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION: + { + if (aecp_unsolicited_remove(aecp, controller_entity_id, port_id) < 0) { + os_log(LOG_ERR, "aecp(%p) received DEREGISTER_UNSOLICITED_NOTIFICATION from controller %"PRIx64" but mac address not previously registered, ignoring.\n", + aecp, ntohll(controller_entity_id)); + + status = AECP_AEM_BAD_ARGUMENTS; + } else + status = AECP_AEM_SUCCESS; + break; + } + case AECP_AEM_CMD_SET_CONTROL: + { + struct aecp_aem_set_get_control_pdu *set_control_cmd = (struct aecp_aem_set_get_control_pdu *)(pdu + 1); + struct aecp_aem_set_get_control_pdu *set_control_rsp = (struct aecp_aem_set_get_control_pdu *)(aecp_rsp + 1); + void *values_cmd = set_control_cmd + 1; + void *values_rsp = set_control_rsp + 1; + struct control_descriptor *desc; + int rc; + int desc_values_len; + + status = AECP_AEM_IN_PROGRESS; + + os_memcpy(set_control_rsp, set_control_cmd, avtp_len - len); + len = avtp_len; + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + break; + } + + if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, ntohs(set_control_cmd->descriptor_type), ntohs(set_control_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + if ((desc_values_len = aecp_aem_control_desc_to_pdu(desc, values_rsp, AVDECC_AECP_MAX_SIZE)) < 0) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + + len = desc_values_len; + len += sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu); + + if (AEM_CONTROL_GET_R(ntohs(desc->control_value_type))) { + status = AECP_AEM_NOT_SUPPORTED; + break; + } + + if (!aecp_aem_validate_control_value(desc, values_cmd, avtp_len - sizeof(struct aecp_aem_pdu) - sizeof(struct aecp_aem_set_get_control_pdu))) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + /* Send AECP command to external control application */ + if (aecp_aem_ipc_tx_command(aecp, pdu, avtp_len, &entity->avdecc->ipc_tx_controlled, IPC_DST_ALL) < 0) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + os_log(LOG_INFO, "aecp(%p) successfully sent AVB_MSG_AECP IPC command type (%x)\n", aecp, cmd_type); + + /* Add to application inflight list */ + rc = aecp_application_inflight_add(aecp, pdu, avtp_len, mac_src, port_id); + if (rc != AECP_AEM_SUCCESS) { + os_log(LOG_ERR, "aecp(%p) Could not add to application inflight\n", aecp); + status = rc; + break; + } + + break; + } + case AECP_AEM_CMD_GET_CONTROL: + { + struct aecp_aem_set_get_control_pdu *set_control_cmd = (struct aecp_aem_set_get_control_pdu *)(pdu + 1); + struct aecp_aem_set_get_control_pdu * set_control_rsp = (struct aecp_aem_set_get_control_pdu *)(aecp_rsp + 1); + void * values_rsp = set_control_rsp + 1; + struct control_descriptor *desc; + int desc_values_len; + + status = AECP_AEM_SUCCESS; + + os_memcpy(set_control_rsp, set_control_cmd, avtp_len - len); + len = avtp_len; + + desc = aem_get_descriptor(entity->aem_descs, ntohs(set_control_cmd->descriptor_type), ntohs(set_control_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + if ((desc_values_len = aecp_aem_control_desc_to_pdu(desc, values_rsp, AVDECC_AECP_MAX_SIZE)) < 0) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + + } + + len = desc_values_len; + len += sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu); + + break; + } + case AECP_AEM_CMD_GET_AVB_INFO: + { + struct aecp_aem_get_avb_info_cmd_pdu *get_avb_info_cmd = (struct aecp_aem_get_avb_info_cmd_pdu *)(pdu + 1); + struct aecp_aem_get_avb_info_rsp_pdu *get_avb_info_rsp = (struct aecp_aem_get_avb_info_rsp_pdu *)(aecp_rsp + 1); + struct aecp_aem_get_avb_info_msrp_mappings_format *msrp_mappings_rsp = (struct aecp_aem_get_avb_info_msrp_mappings_format *)(get_avb_info_rsp + 1); + struct avb_interface_descriptor *desc; + struct avb_interface_dynamic_desc *dynamic_desc; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_get_avb_info_rsp_pdu); + len += CFG_SR_CLASS_MAX * sizeof(struct aecp_aem_get_avb_info_msrp_mappings_format); + + os_memset(get_avb_info_rsp, 0, + sizeof(struct aecp_aem_get_avb_info_rsp_pdu) + CFG_SR_CLASS_MAX * sizeof(struct aecp_aem_get_avb_info_msrp_mappings_format)); + + get_avb_info_rsp->descriptor_type = get_avb_info_cmd->descriptor_type; + get_avb_info_rsp->descriptor_index = get_avb_info_cmd->descriptor_index; + + if (get_avb_info_cmd->descriptor_type != ntohs(AEM_DESC_TYPE_AVB_INTERFACE)) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, ntohs(get_avb_info_cmd->descriptor_type), ntohs(get_avb_info_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + dynamic_desc = aem_get_descriptor(entity->aem_dynamic_descs, ntohs(get_avb_info_cmd->descriptor_type), ntohs(get_avb_info_cmd->descriptor_index), NULL); + if (!dynamic_desc) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + + get_avb_info_rsp->flags = (AECP_AEM_AVB_INFO_AS_CAPABLE) | (AECP_AEM_AVB_INFO_GPTP_ENABLED) | (AECP_AEM_AVB_INFO_SRP_ENABLED); + copy_64(&get_avb_info_rsp->gptp_grandmaster_id, &dynamic_desc->gptp_grandmaster_id); + get_avb_info_rsp->gptp_domain_number = desc->domain_number; + + /* FIXME : Default values for SR class A mapping, but should be from SRP domain indication */ + if (entity->milan_mode) { + get_avb_info_rsp->msrp_mappings_count = htons(1); + + msrp_mappings_rsp->traffic_class = sr_class_id(SR_CLASS_A); + msrp_mappings_rsp->priority = sr_class_pcp(SR_CLASS_A); + msrp_mappings_rsp->vlan_id = MRP_DEFAULT_VID; + } + + //FIXME needs to hook propagation delay and msrp_mappings. + + break; + } + case AECP_AEM_CMD_SET_STREAM_INFO: + { + struct aecp_aem_set_stream_info_pdu *set_stream_info_cmd = (struct aecp_aem_set_stream_info_pdu *)(pdu + 1); + struct aecp_aem_set_stream_info_pdu *set_stream_info_rsp = (struct aecp_aem_set_stream_info_pdu *)(aecp_rsp + 1); + u16 descriptor_type = ntohs(set_stream_info_cmd->descriptor_type); + struct stream_descriptor *desc; + struct stream_output_dynamic_desc *stream_out_dynamic_desc; + unsigned int i; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_set_stream_info_pdu); + os_memset(set_stream_info_rsp, 0, sizeof(struct aecp_aem_set_stream_info_pdu)); + + set_stream_info_rsp->descriptor_type = set_stream_info_cmd->descriptor_type; + set_stream_info_rsp->descriptor_index = set_stream_info_cmd->descriptor_index; + + if (descriptor_type != AEM_DESC_TYPE_STREAM_INPUT && descriptor_type != AEM_DESC_TYPE_STREAM_OUTPUT) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + /* For Milan, SET_STREAM_INFO is not supported for STREAM_INPUT. Per AVNU.IO.CONTROL 7.3.9. + * FIXME: IEEE 1722.1 should implement it. + */ + if (descriptor_type == AEM_DESC_TYPE_STREAM_INPUT) { + status = AEM_NOT_SUPPORTED; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(set_stream_info_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + stream_out_dynamic_desc = aem_get_descriptor(entity->aem_dynamic_descs, descriptor_type, ntohs(set_stream_info_cmd->descriptor_index), NULL); + if (!stream_out_dynamic_desc) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + + } else if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + + } else if (acmp_is_stream_running(entity, descriptor_type, ntohs(set_stream_info_cmd->descriptor_index))) { + status = AECP_AEM_STREAM_IS_RUNNING; + } + + /* Do the sub-commands check only if not failed already. */ + if (status == AECP_AEM_SUCCESS) { + if (set_stream_info_cmd->flags & htonl(AECP_STREAM_FLAG_STREAM_FORMAT_VALID)) { + status = AECP_AEM_NOT_SUPPORTED; + + for (i = 0; i < ntohs(desc->number_of_formats); i++) { + if (cmp_64(&set_stream_info_cmd->stream_format, &desc->formats[i])) { + status = AECP_AEM_SUCCESS; + break; + } + } + } + + if (set_stream_info_cmd->flags & htonl(AECP_STREAM_FLAG_STREAM_ID_VALID)) { + status = AECP_AEM_NOT_SUPPORTED; + } + + if (set_stream_info_cmd->flags & htonl(AECP_STREAM_FLAG_STREAM_DEST_MAC_VALID)) { + /* FIXME only support automatic mode for MAC attribution (MAAP or self assigned addresses) for now */ + if (!is_invalid_mac_addr(set_stream_info_cmd->stream_dest_mac)) { + status = AECP_AEM_NOT_SUPPORTED; + } + } + + if (set_stream_info_cmd->flags & htonl(AECP_STREAM_FLAG_STREAM_VLAN_ID_VALID)) { + status = AECP_AEM_NOT_SUPPORTED; + } + + if (set_stream_info_cmd->flags & htonl(AECP_STREAM_FLAG_MSRP_ACC_LAT_VALID)) { + if (!entity->milan_mode) + status = AECP_AEM_NOT_SUPPORTED; + else if (ntohl(set_stream_info_cmd->msrp_accumulated_latency) > STREAM_PRESENTATION_TIME_OFFSET_MAX) /* AVNU.IO.CONTROL 7.3.9 */ + status = AECP_AEM_BAD_ARGUMENTS; + } + } + + /* If all sub commands are successful, do all updates at once: + * This behavior is specified in AVNU.IO.CONTROL 7.3.9 for Milan, so adapt it also + * for legacy IEEE1722.1 + */ + if (status == AECP_AEM_SUCCESS) { + if (set_stream_info_cmd->flags & htonl(AECP_STREAM_FLAG_STREAM_FORMAT_VALID)) { + /* Update the descriptor format and send the unsolicited notification (AVNU.IO.CONTROL 7.5.2) only on format change*/ + if (!cmp_64(&desc->current_format, &set_stream_info_cmd->stream_format)) { + copy_64(&desc->current_format, &set_stream_info_cmd->stream_format); + send_unsolicited_notification = true; + } + } + + if (set_stream_info_cmd->flags & htonl(AECP_STREAM_FLAG_STREAM_ID_VALID)) { + /* Update the dynamic descriptor stream_id and send the unsolicited notification (AVNU.IO.CONTROL 7.5.2) only on stream_id change*/ + if (!cmp_64(&stream_out_dynamic_desc->stream_id, &set_stream_info_cmd->stream_id)) { + copy_64(&stream_out_dynamic_desc->stream_id, &set_stream_info_cmd->stream_id); + send_unsolicited_notification = true; + } + } + + if (set_stream_info_cmd->flags & htonl(AECP_STREAM_FLAG_MSRP_ACC_LAT_VALID)) { + /* Update the dynamic descriptor msrp_accumulated_latency and send the unsolicited notification (AVNU.IO.CONTROL 7.5.2) only on msrp_accumulated_latency change*/ + if (stream_out_dynamic_desc->presentation_time_offset != ntohl(set_stream_info_cmd->msrp_accumulated_latency)) { + stream_out_dynamic_desc->presentation_time_offset = ntohl(set_stream_info_cmd->msrp_accumulated_latency); + send_unsolicited_notification = true; + } + } + } + + /* Always set valid values, even on failures. */ + + copy_64(&set_stream_info_rsp->stream_format, &desc->current_format); + set_stream_info_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_FORMAT_VALID); + + /* For Milan STREAM_OUTPUT, the stream_id and vlan_id are generated on init and always valid. + * FIXME: Make the same behavior for IEEE1722.1 (currently stream_id only valid when the stream has been + * connected and vlan_id is set to an internally default value) + */ + if (entity->milan_mode) { + copy_64(&set_stream_info_rsp->stream_id, &stream_out_dynamic_desc->stream_id); + set_stream_info_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_ID_VALID); + + set_stream_info_rsp->stream_vlan_id = stream_out_dynamic_desc->stream_vlan_id; + set_stream_info_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_VLAN_ID_VALID); + + /* Per AVNU.IO.CONTROL 7.3.10.2: For Milan STREAM_OUTPUT, msrp_accumulated_latency is always valid */ + set_stream_info_rsp->msrp_accumulated_latency = htonl(stream_out_dynamic_desc->presentation_time_offset); + set_stream_info_rsp->flags |= htonl(AECP_STREAM_FLAG_MSRP_ACC_LAT_VALID); + } + + if (!is_invalid_mac_addr(stream_out_dynamic_desc->stream_dest_mac)) { + os_memcpy(set_stream_info_rsp->stream_dest_mac, stream_out_dynamic_desc->stream_dest_mac, 6); + set_stream_info_rsp->flags |= htonl(AECP_STREAM_FLAG_STREAM_DEST_MAC_VALID); + } + + break; + } + case AECP_AEM_CMD_GET_STREAM_INFO: + { + struct aecp_aem_get_stream_info_cmd_pdu *get_stream_info_cmd = (struct aecp_aem_get_stream_info_cmd_pdu *)(pdu + 1); + + status = aecp_aem_get_stream_info_response(entity, aecp_rsp, &len, ntohs(get_stream_info_cmd->descriptor_type), ntohs(get_stream_info_cmd->descriptor_index)); + + break; + } + case AECP_AEM_CMD_SET_NAME: + { + struct aecp_aem_set_name_pdu *set_name_cmd = (struct aecp_aem_set_name_pdu *)(pdu + 1); + struct aecp_aem_set_name_pdu *set_name_rsp = (struct aecp_aem_set_name_pdu *)(aecp_rsp + 1); + struct entity_descriptor *entity_desc = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_ENTITY, 0, NULL); + void *object_name = NULL; + void *desc; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_set_name_pdu); + os_memset(set_name_rsp, 0, sizeof(struct aecp_aem_set_name_pdu)); + + set_name_rsp->descriptor_type = set_name_cmd->descriptor_type; + set_name_rsp->descriptor_index = set_name_cmd->descriptor_index; + set_name_rsp->name_index = set_name_cmd->name_index; + set_name_rsp->configuration_index = set_name_cmd->configuration_index; + + /* FIXME we currently support only one configuration */ + if (entity_desc->current_configuration != set_name_cmd->configuration_index) { + status = AECP_AEM_NOT_SUPPORTED; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, ntohs(set_name_cmd->descriptor_type), ntohs(set_name_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + switch (ntohs(set_name_cmd->descriptor_type)) { + case AEM_DESC_TYPE_ENTITY: + if (ntohs(set_name_cmd->name_index) == 0) { + object_name = (void *)((struct entity_descriptor *)desc)->entity_name; + + } else if (ntohs(set_name_cmd->name_index) == 1) { + object_name = (void *)((struct entity_descriptor *)desc)->group_name; + + } else { + status = AECP_AEM_BAD_ARGUMENTS; + } + break; + + case AEM_DESC_TYPE_CONFIGURATION: + object_name = (void *)((struct configuration_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_AUDIO_UNIT: + object_name = (void *)((struct audio_unit_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_VIDEO_UNIT: + object_name = (void *)((struct video_unit_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_STREAM_INPUT: + case AEM_DESC_TYPE_STREAM_OUTPUT: + object_name = (void *)((struct stream_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_JACK_INPUT: + case AEM_DESC_TYPE_JACK_OUTPUT: + object_name = (void *)((struct jack_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_AVB_INTERFACE: + object_name = (void *)((struct avb_interface_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_CLOCK_SOURCE: + object_name = (void *)((struct clock_source_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_CLOCK_DOMAIN: + object_name = (void *)((struct clock_domain_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_AUDIO_CLUSTER: + object_name = (void *)((struct audio_cluster_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_VIDEO_CLUSTER: + object_name = (void *)((struct video_cluster_descriptor *)desc)->object_name; + break; + case AEM_DESC_TYPE_CONTROL: + object_name = (void *)((struct control_descriptor *)desc)->object_name; + break; + default: + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + + } else if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + } + + if (object_name) { + /* Update the descriptor object_name and send the unsolicited notification (AVNU.IO.CONTROL 7.5.2) only on object_name change*/ + if (status == AECP_AEM_SUCCESS && os_memcmp(object_name, set_name_cmd->name, AEM_STR_LEN_MAX)) { + os_memcpy(object_name, set_name_cmd->name, AEM_STR_LEN_MAX); + + send_unsolicited_notification = true; + } + + /* The response always contains the current value, even on failure (IEEE1722.1-2013 7.4.17.1) */ + os_memcpy(set_name_rsp->name, object_name, AEM_STR_LEN_MAX); + } + + break; + } + case AECP_AEM_CMD_GET_NAME: + { + struct aecp_aem_get_name_cmd_pdu *get_name_cmd = (struct aecp_aem_get_name_cmd_pdu *)(pdu + 1); + struct aecp_aem_get_name_rsp_pdu *get_name_rsp = (struct aecp_aem_get_name_rsp_pdu *)(aecp_rsp + 1); + struct entity_descriptor *entity_desc = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_ENTITY, 0, NULL); + void *desc; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_get_name_rsp_pdu); + os_memset(get_name_rsp, 0, sizeof(struct aecp_aem_get_name_rsp_pdu)); + + get_name_rsp->descriptor_type = get_name_cmd->descriptor_type; + get_name_rsp->descriptor_index = get_name_cmd->descriptor_index; + get_name_rsp->name_index = get_name_cmd->name_index; + get_name_rsp->configuration_index = get_name_cmd->configuration_index; + + /* FIXME we currently support only one configuration */ + if (entity_desc->current_configuration != get_name_cmd->configuration_index) { + status = AECP_AEM_NOT_SUPPORTED; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, ntohs(get_name_cmd->descriptor_type), ntohs(get_name_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + switch (ntohs(get_name_cmd->descriptor_type)) { + case AEM_DESC_TYPE_ENTITY: + if (ntohs(get_name_cmd->name_index) == 0) { + os_memcpy(get_name_rsp->name, ((struct entity_descriptor *)desc)->entity_name, AEM_STR_LEN_MAX); + + } else if (ntohs(get_name_cmd->name_index) == 1) { + os_memcpy(get_name_rsp->name, ((struct entity_descriptor *)desc)->group_name, AEM_STR_LEN_MAX); + + } else { + status = AECP_AEM_BAD_ARGUMENTS; + } + break; + + case AEM_DESC_TYPE_CONFIGURATION: + os_memcpy(get_name_rsp->name, ((struct configuration_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_AUDIO_UNIT: + os_memcpy(get_name_rsp->name, ((struct audio_unit_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_VIDEO_UNIT: + os_memcpy(get_name_rsp->name, ((struct video_unit_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_STREAM_INPUT: + case AEM_DESC_TYPE_STREAM_OUTPUT: + os_memcpy(get_name_rsp->name, ((struct stream_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_JACK_INPUT: + case AEM_DESC_TYPE_JACK_OUTPUT: + os_memcpy(get_name_rsp->name, ((struct jack_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_AVB_INTERFACE: + os_memcpy(get_name_rsp->name, ((struct avb_interface_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_CLOCK_SOURCE: + os_memcpy(get_name_rsp->name, ((struct clock_source_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_CLOCK_DOMAIN: + os_memcpy(get_name_rsp->name, ((struct clock_domain_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_AUDIO_CLUSTER: + os_memcpy(get_name_rsp->name, ((struct audio_cluster_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_VIDEO_CLUSTER: + os_memcpy(get_name_rsp->name, ((struct video_cluster_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + case AEM_DESC_TYPE_CONTROL: + os_memcpy(get_name_rsp->name, ((struct control_descriptor *)desc)->object_name, AEM_STR_LEN_MAX); + break; + default: + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + break; + } + case AECP_AEM_CMD_SET_SAMPLING_RATE: + { + struct aecp_aem_set_sampling_rate_pdu *set_rate_cmd = (struct aecp_aem_set_sampling_rate_pdu *)(pdu + 1); + struct aecp_aem_set_sampling_rate_pdu *set_rate_rsp = (struct aecp_aem_set_sampling_rate_pdu *)(aecp_rsp + 1); + u32 current_sampling_rate = 0; + void *desc; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_set_sampling_rate_pdu); + os_memset(set_rate_rsp, 0, sizeof(struct aecp_aem_set_sampling_rate_pdu)); + + set_rate_rsp->descriptor_type = set_rate_cmd->descriptor_type; + set_rate_rsp->descriptor_index = set_rate_cmd->descriptor_index; + + desc = aem_get_descriptor(entity->aem_descs, ntohs(set_rate_cmd->descriptor_type), ntohs(set_rate_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + /* FIXME only support one sampling rate */ + switch (ntohs(set_rate_cmd->descriptor_type)) { + case AEM_DESC_TYPE_AUDIO_UNIT: + if (((struct audio_unit_descriptor *)desc)->current_sampling_rate != set_rate_cmd->sampling_rate) + status = AECP_AEM_NOT_SUPPORTED; + + current_sampling_rate = ((struct audio_unit_descriptor *)desc)->current_sampling_rate; + break; + case AEM_DESC_TYPE_VIDEO_CLUSTER: + if (((struct video_cluster_descriptor *)desc)->current_sampling_rate != set_rate_cmd->sampling_rate) + status = AECP_AEM_NOT_SUPPORTED; + + current_sampling_rate = ((struct video_cluster_descriptor *)desc)->current_sampling_rate; + break; + case AEM_DESC_TYPE_SENSOR_CLUSTER: + status = AECP_AEM_NOT_SUPPORTED; /* FIXME SENSOR_CLUSTER descriptor not implemented yet */ + break; + default: + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + + } else if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + } + + /* The response always contains the current value, even on failure (IEEE1722.1-2013 7.4.21.1). */ + set_rate_rsp->sampling_rate = current_sampling_rate; + + break; + } + case AECP_AEM_CMD_GET_SAMPLING_RATE: + { + struct aecp_aem_get_sampling_rate_cmd_pdu *get_rate_cmd = (struct aecp_aem_get_sampling_rate_cmd_pdu *)(pdu + 1); + struct aecp_aem_get_sampling_rate_rsp_pdu *get_rate_rsp = (struct aecp_aem_get_sampling_rate_rsp_pdu *)(aecp_rsp + 1); + void *desc; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_get_sampling_rate_rsp_pdu); + os_memset(get_rate_rsp, 0, sizeof(struct aecp_aem_get_sampling_rate_rsp_pdu)); + + get_rate_rsp->descriptor_type = get_rate_cmd->descriptor_type; + get_rate_rsp->descriptor_index = get_rate_cmd->descriptor_index; + + desc = aem_get_descriptor(entity->aem_descs, ntohs(get_rate_cmd->descriptor_type), ntohs(get_rate_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + switch (ntohs(get_rate_cmd->descriptor_type)) { + case AEM_DESC_TYPE_AUDIO_UNIT: + get_rate_rsp->sampling_rate = ((struct audio_unit_descriptor *)desc)->current_sampling_rate; + break; + case AEM_DESC_TYPE_VIDEO_CLUSTER: + get_rate_rsp->sampling_rate = ((struct video_cluster_descriptor *)desc)->current_sampling_rate; + break; + case AEM_DESC_TYPE_SENSOR_CLUSTER: + status = AECP_AEM_NOT_SUPPORTED; /* FIXME SENSOR_CLUSTER descriptor not implemented yet */ + break; + default: + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + break; + } + case AECP_AEM_CMD_SET_CLOCK_SOURCE: + { + struct aecp_aem_set_clock_source_pdu *set_clock_source_cmd = (struct aecp_aem_set_clock_source_pdu *)(pdu + 1); + struct aecp_aem_set_clock_source_pdu *set_clock_source_rsp = (struct aecp_aem_set_clock_source_pdu *)(aecp_rsp + 1); + u16 descriptor_type = ntohs(set_clock_source_cmd->descriptor_type); + struct clock_domain_descriptor *desc; + + len += sizeof(struct aecp_aem_set_clock_source_pdu); + os_memset(set_clock_source_rsp, 0, sizeof(struct aecp_aem_set_clock_source_pdu)); + + set_clock_source_rsp->descriptor_type = set_clock_source_cmd->descriptor_type; + set_clock_source_rsp->descriptor_index = set_clock_source_cmd->descriptor_index; + + if (descriptor_type != AEM_DESC_TYPE_CLOCK_DOMAIN) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(set_clock_source_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + /* The response always contains the current value, even on failure (IEEE1722.1-2013 7.4.24.1). Init to current value, and change later on success if needed */ + set_clock_source_rsp->clock_source_index = desc->clock_source_index; + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + break; + } + + if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + break; + } + + /* FIXME we currently support only one clock source */ + /* FIXME we should pass the command to app to handle clock domain changes */ + if (set_clock_source_cmd->clock_source_index == desc->clock_source_index) + status = AECP_AEM_SUCCESS; + else + status = AECP_AEM_NOT_SUPPORTED; + + /* FIXME send unsolicited notification on aecp_ipc_rx_controlled() */ + + break; + } + case AECP_AEM_CMD_GET_CLOCK_SOURCE: + { + struct aecp_aem_get_clock_source_cmd_pdu *get_clock_source_cmd = (struct aecp_aem_get_clock_source_cmd_pdu *)(pdu + 1); + struct aecp_aem_get_clock_source_rsp_pdu *get_clock_source_rsp = (struct aecp_aem_get_clock_source_rsp_pdu *)(aecp_rsp + 1); + u16 descriptor_type = ntohs(get_clock_source_cmd->descriptor_type); + struct clock_domain_descriptor *desc; + + status = AECP_AEM_SUCCESS; + + len += sizeof(struct aecp_aem_get_clock_source_rsp_pdu); + os_memset(get_clock_source_rsp, 0, sizeof(struct aecp_aem_get_clock_source_rsp_pdu)); + + get_clock_source_rsp->descriptor_type = get_clock_source_cmd->descriptor_type; + get_clock_source_rsp->descriptor_index = get_clock_source_cmd->descriptor_index; + + if (descriptor_type != AEM_DESC_TYPE_CLOCK_DOMAIN) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(get_clock_source_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + get_clock_source_rsp->clock_source_index = desc->clock_source_index; + + break; + } + case AECP_AEM_CMD_START_STREAMING: + { + struct aecp_aem_start_streaming_cmd_pdu *start_streaming_cmd = (struct aecp_aem_start_streaming_cmd_pdu *)(pdu + 1); + struct aecp_aem_start_streaming_cmd_pdu *start_streaming_rsp = (struct aecp_aem_start_streaming_cmd_pdu *)(aecp_rsp + 1); + u16 descriptor_type = ntohs(start_streaming_cmd->descriptor_type); + struct stream_descriptor *desc; + + os_log(LOG_DEBUG, "aecp(%p) received command type START_STREAMING (%x)\n", aecp, cmd_type); + status = AECP_AEM_IN_PROGRESS; + + len += sizeof(struct aecp_aem_start_streaming_cmd_pdu); + os_memset(start_streaming_rsp, 0, sizeof(struct aecp_aem_start_streaming_cmd_pdu)); + + start_streaming_rsp->descriptor_type = start_streaming_cmd->descriptor_type; + start_streaming_rsp->descriptor_index = start_streaming_cmd->descriptor_index; + + if (descriptor_type != AEM_DESC_TYPE_STREAM_INPUT && descriptor_type != AEM_DESC_TYPE_STREAM_OUTPUT) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + if (entity->milan_mode && descriptor_type == AEM_DESC_TYPE_STREAM_OUTPUT) { + status = AECP_AEM_NOT_SUPPORTED; + break; + } + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + break; + } + + if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(start_streaming_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + /* Send AECP command to external control application */ + if (aecp_aem_ipc_tx_command(aecp, pdu, len, &entity->avdecc->ipc_tx_controlled, IPC_DST_ALL) < 0) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + os_log(LOG_DEBUG, "aecp(%p) successfully sent AVB_MSG_AECP IPC command type (%x)\n", aecp, cmd_type); + + /* Add to application inflight list */ + rc = aecp_application_inflight_add(aecp, pdu, avtp_len, mac_src, port_id); + if (rc != AECP_AEM_SUCCESS) { + os_log(LOG_ERR, "aecp(%p) Could not add to application inflight\n", aecp); + status = rc; + break; + } + os_log(LOG_DEBUG, "aecp(%p) successfully added entity (%p) to application inflight list \n", aecp, entity); + + break; + } + case AECP_AEM_CMD_STOP_STREAMING: + { + struct aecp_aem_stop_streaming_cmd_pdu *stop_streaming_cmd = (struct aecp_aem_stop_streaming_cmd_pdu *)(pdu + 1); + struct aecp_aem_stop_streaming_cmd_pdu *stop_streaming_rsp = (struct aecp_aem_stop_streaming_cmd_pdu *)(aecp_rsp + 1); + u16 descriptor_type = ntohs(stop_streaming_cmd->descriptor_type); + struct stream_descriptor *desc; + + os_log(LOG_DEBUG, "aecp(%p) received command type STOP_STREAMING (%x)\n", aecp, cmd_type); + status = AECP_AEM_IN_PROGRESS; + + len += sizeof(struct aecp_aem_stop_streaming_cmd_pdu); + os_memset(stop_streaming_rsp, 0, sizeof(struct aecp_aem_stop_streaming_cmd_pdu)); + + stop_streaming_rsp->descriptor_type = stop_streaming_cmd->descriptor_type; + stop_streaming_rsp->descriptor_index = stop_streaming_cmd->descriptor_index; + + if (descriptor_type != AEM_DESC_TYPE_STREAM_INPUT && descriptor_type != AEM_DESC_TYPE_STREAM_OUTPUT) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + if (entity->milan_mode && descriptor_type == AEM_DESC_TYPE_STREAM_OUTPUT) { + status = AECP_AEM_NOT_SUPPORTED; + break; + } + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + break; + } + + if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + break; + } + + desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(stop_streaming_cmd->descriptor_index), NULL); + if (!desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + /* Send AECP command to external control application */ + if (aecp_aem_ipc_tx_command(aecp, pdu, len, &entity->avdecc->ipc_tx_controlled, IPC_DST_ALL) < 0) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + os_log(LOG_DEBUG, "aecp(%p) successfully sent AVB_MSG_AECP IPC command type (%x)\n", aecp, cmd_type); + + /* Add to application inflight list */ + rc = aecp_application_inflight_add(aecp, pdu, avtp_len, mac_src, port_id); + if (rc != AECP_AEM_SUCCESS) { + os_log(LOG_ERR, "aecp(%p) Could not add to application inflight\n", aecp); + status = rc; + break; + } + os_log(LOG_DEBUG, "aecp(%p) successfully added entity (%p) to application inflight list \n", aecp, entity); + + break; + } + case AECP_AEM_CMD_GET_COUNTERS: + { + struct aecp_aem_get_counters_cmd_pdu *get_counters_cmd = (struct aecp_aem_get_counters_cmd_pdu *)(pdu + 1); + struct aecp_aem_get_counters_rsp_pdu *get_counters_rsp = (struct aecp_aem_get_counters_rsp_pdu *)(aecp_rsp + 1); + + status = AECP_AEM_SUCCESS; + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + } else if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + } + + if (status != AECP_AEM_SUCCESS) { + len += sizeof(struct aecp_aem_get_counters_rsp_pdu); + + os_memset(get_counters_rsp, 0, sizeof(*get_counters_rsp)); + + get_counters_rsp->descriptor_type = get_counters_cmd->descriptor_type; + get_counters_rsp->descriptor_index = get_counters_cmd->descriptor_index; + + break; + } + + status = aecp_aem_get_counters_response(entity, aecp_rsp, &len, ntohs(get_counters_cmd->descriptor_type), ntohs(get_counters_cmd->descriptor_index)); + + break; + } + case AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS: + case AECP_AEM_CMD_ADD_AUDIO_MAPPINGS: + { + struct aecp_aem_modify_audio_mappings_cmd_pdu *audio_map_cmd = (struct aecp_aem_modify_audio_mappings_cmd_pdu *)(pdu + 1); + struct aecp_aem_modify_audio_mappings_cmd_pdu *audio_map_rsp = (struct aecp_aem_modify_audio_mappings_cmd_pdu *)(aecp_rsp + 1); + struct aecp_aem_get_audio_map_mappings_format *audio_mappings = (struct aecp_aem_get_audio_map_mappings_format *)(audio_map_cmd + 1); + u16 mapping_stream_index, mapping_stream_channel, mapping_cluster_offset, mapping_cluster_channel; + u16 number_of_mappings = ntohs(audio_map_cmd->number_of_mappings); + u16 descriptor_type = ntohs(audio_map_cmd->descriptor_type); + struct stream_port_descriptor *stream_port_desc; + struct stream_descriptor *stream_desc; + unsigned int i, j; + + os_log(LOG_DEBUG, "aecp(%p) received command type ADD/REMOVE_AUDIO_MAPPINGS (%x)\n", aecp, cmd_type); + status = AECP_AEM_IN_PROGRESS; + + os_memcpy(audio_map_rsp, audio_map_cmd, avtp_len - len); + len = avtp_len; + + if (descriptor_type != AEM_DESC_TYPE_STREAM_PORT_INPUT && descriptor_type != AEM_DESC_TYPE_STREAM_PORT_OUTPUT) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + if (avdecc_entity_is_acquired(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_ACQUIRED; + break; + } + + if (avdecc_entity_is_locked(entity, controller_entity_id)) { + status = AECP_AEM_ENTITY_LOCKED; + break; + } + + stream_port_desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(audio_map_cmd->descriptor_index), NULL); + if (!stream_port_desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + /* Check validity of audio_mappings in the command as per MILAN Specification v1.2 5.4.2.27 */ + for (i = 0; i < number_of_mappings; i++) { + mapping_stream_index = audio_mappings[i].mapping_stream_index; + mapping_stream_channel = audio_mappings[i].mapping_stream_channel; + mapping_cluster_offset = audio_mappings[i].mapping_cluster_offset; + mapping_cluster_channel = audio_mappings[i].mapping_cluster_channel; + + stream_desc = aem_get_descriptor(entity->aem_descs, (descriptor_type == AEM_DESC_TYPE_STREAM_PORT_OUTPUT) ? AEM_DESC_TYPE_STREAM_OUTPUT : AEM_DESC_TYPE_STREAM_INPUT, + ntohs(mapping_stream_index), NULL); + + /* A mapping is invalid if it references a channel of a Stream Input/Output that does not exist */ + if (!stream_desc) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + if (descriptor_type == AEM_DESC_TYPE_STREAM_PORT_OUTPUT) { + /* Changing mapping while Stream Output is streaming not supported */ + if (acmp_is_stream_running(entity, AEM_DESC_TYPE_STREAM_OUTPUT, ntohs(mapping_stream_index))) { + status = AECP_AEM_STREAM_IS_RUNNING; + break; + } + } + + /* TODO: check that the sampling rate of the stream input/output (through its current_format) that is referrenced by the mapping + * is the same as the current_sampling_rate of the Audio Unit, otherwise the command is invalid + */ + + for (j = i + 1; j < number_of_mappings; j++) { + if (descriptor_type == AEM_DESC_TYPE_STREAM_PORT_INPUT) { + /* The command is invalid on a Stream Port Input + * if it contains two different mappings that reference the same cluster’s channel + * and two different stream’s channels that are not redundant. + * As per MILAN Specification v1.2 5.4.2.27 + */ + /* TODO: exonerate redundant streams and accept the mapping */ + if ((audio_mappings[j].mapping_cluster_offset == mapping_cluster_offset) && + (audio_mappings[j].mapping_cluster_channel == mapping_cluster_channel) && + ((audio_mappings[j].mapping_stream_index != mapping_stream_index) || + (audio_mappings[j].mapping_stream_channel != mapping_stream_channel))) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + } else { /* AEM_DESC_TYPE_STREAM_PORT_OUTPUT */ + /* The command is invalid on a Stream Port Output + * if it contains two different mappings that reference the same stream’s channel + * and two different cluster’s channels. + * As per MILAN Specification v1.2 5.4.2.27 + */ + if ((audio_mappings[j].mapping_stream_index == mapping_stream_index) && + (audio_mappings[j].mapping_stream_channel == mapping_stream_channel) && + ((audio_mappings[j].mapping_cluster_offset != mapping_cluster_offset) || + (audio_mappings[j].mapping_cluster_channel != mapping_cluster_channel))) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + } + } + + if (status != AECP_AEM_IN_PROGRESS) + break; + } + + if (status != AECP_AEM_IN_PROGRESS) + break; + + + /* Send AECP command to external control application */ + if (aecp_aem_ipc_tx_command(aecp, pdu, len, &entity->avdecc->ipc_tx_controlled, IPC_DST_ALL) < 0) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + os_log(LOG_DEBUG, "aecp(%p) successfully sent AVB_MSG_AECP IPC command type (%x)\n", aecp, cmd_type); + + /* Add to application inflight list */ + rc = aecp_application_inflight_add(aecp, aecp_rsp, len, mac_src, port_id); + if (rc != AECP_AEM_SUCCESS) { + os_log(LOG_ERR, "aecp(%p) Could not add to application inflight\n", aecp); + status = rc; + break; + } + os_log(LOG_DEBUG, "aecp(%p) successfully added entity (%p) to application inflight list \n", aecp, entity); + + break; + } + case AECP_AEM_CMD_GET_AUDIO_MAP: + { + struct aecp_aem_get_audio_map_cmd_pdu *get_audio_map_cmd = (struct aecp_aem_get_audio_map_cmd_pdu *)(pdu + 1); + struct aecp_aem_get_audio_map_rsp_pdu *get_audio_map_rsp = (struct aecp_aem_get_audio_map_rsp_pdu *)(aecp_rsp + 1); + u16 descriptor_type = ntohs(get_audio_map_cmd->descriptor_type); + struct stream_port_descriptor *stream_port_desc; + + os_log(LOG_DEBUG, "aecp(%p) received command type GET_AUDIO_MAP (%x)\n", aecp, cmd_type); + status = AECP_AEM_IN_PROGRESS; + + len += sizeof(struct aecp_aem_get_audio_map_rsp_pdu); + os_memset(get_audio_map_rsp, 0, sizeof(struct aecp_aem_get_audio_map_rsp_pdu)); + + get_audio_map_rsp->descriptor_type = get_audio_map_cmd->descriptor_type; + get_audio_map_rsp->descriptor_index = get_audio_map_cmd->descriptor_index; + get_audio_map_rsp->map_index = get_audio_map_cmd->map_index; + + if (descriptor_type != AEM_DESC_TYPE_STREAM_PORT_INPUT && descriptor_type != AEM_DESC_TYPE_STREAM_PORT_OUTPUT) { + status = AECP_AEM_BAD_ARGUMENTS; + break; + } + + stream_port_desc = aem_get_descriptor(entity->aem_descs, descriptor_type, ntohs(get_audio_map_cmd->descriptor_index), NULL); + if (!stream_port_desc) { + status = AECP_AEM_NO_SUCH_DESCRIPTOR; + break; + } + + /* If STREAM_PORT has static mappings: return NOT_SUPPORTED. (Per AVNU.IO.CONTROL 7.3.26 for STREAM_PORT_OUTPUT) */ + if (ntohs(stream_port_desc->number_of_maps) > 0) { + status = AECP_AEM_NOT_SUPPORTED; + break; + } + + /* Send AECP command to external control application */ + if (aecp_aem_ipc_tx_command(aecp, pdu, avtp_len, &entity->avdecc->ipc_tx_controlled, IPC_DST_ALL) < 0) { + status = AECP_AEM_ENTITY_MISBEHAVING; + break; + } + os_log(LOG_DEBUG, "aecp(%p) successfully sent AVB_MSG_AECP IPC command type (%x)\n", aecp, cmd_type); + + /* Add to application inflight list */ + rc = aecp_application_inflight_add(aecp, aecp_rsp, len, mac_src, port_id); + if (rc != AECP_AEM_SUCCESS) { + os_log(LOG_ERR, "aecp(%p) Could not add to application inflight\n", aecp); + status = rc; + break; + } + os_log(LOG_DEBUG, "aecp(%p) successfully added entity (%p) to application inflight list \n", aecp, entity); + + break; + } + case AECP_AEM_CMD_GET_AS_PATH: + { + struct aecp_aem_get_as_path_cmd_pdu *get_as_path_cmd = (struct aecp_aem_get_as_path_cmd_pdu *)(pdu + 1); + + status = aecp_aem_get_as_path_response(entity, aecp_rsp, &len, ntohs(get_as_path_cmd->descriptor_index)); + + break; + } + default: + status = AECP_AEM_NOT_IMPLEMENTED; + break; + + } + +send_rsp: + if (send_unsolicited_notification && aecp_need_sync_unsolicited_notifications(aecp, controller_entity_id)) { + /* status is SUCCESS and need to send a synchronous unsolicited notification. */ + + /* clone the desc_rsp and send the solicited notification/response before the unsolicited one */ + rc = aecp_aem_prepare_send_response(aecp, port_rsp, aecp_rsp, controller_entity_id, + ntohs(aecp_rsp->sequence_id), status, 0, mac_src, len); + if (rc < 0) { + os_log(LOG_ERR, "aecp(%p) Could not prepare and send the solicited response for command (%x, %s)\n", aecp, cmd_type, aecp_aem_cmdtype2string(cmd_type)); + net_tx_free(desc_rsp); + goto exit; + } + /* Send the original buffer as synchronous unsolicited notifications on commands that directly changed the PAAD-AE's state + * Commands relying on external apps to change the PAAD-AE's state sends the response and unsolicited notifications in aecp_ipc_rx_controlled + * Per AVNU.IO.CONTROL 7.5.2 + */ + aecp_aem_send_sync_unsolicited_notification_full(aecp, desc_rsp, aecp_rsp, controller_entity_id, len); + + } else { + /* Don't send response when expecting response from app first. */ + if (status != AECP_AEM_IN_PROGRESS) + rc = aecp_aem_net_tx_response(aecp, port_rsp, desc_rsp, status, mac_src, len); + else + net_tx_free(desc_rsp); + } + + unsolicited_entry = aecp_unsolicited_find(aecp, controller_entity_id, port_id); + if (unsolicited_entry) { + /* Restart monitor timer of the controller sending the (valid) command if he is registered. Per AVNU.IO.CONTROL 7.5.3 */ + os_log(LOG_DEBUG,"aecp(%p) port(%u) controller(%016"PRIx64") sent us an AECP command (%u), restarting departing monitor timer", + aecp, port_id, ntohll(controller_entity_id), cmd_type); + timer_restart(&unsolicited_entry->monitor_timer, MONITOR_TIMER_INTERVAL); + } + +exit: + return rc; +} + +/** Main AECP AEM receive function for entity's AECP command + * Follows the AVDECC entity model state machine (9.2.2.3.1.4). + * \return 0 on success, negative otherwise + * \param aecp pointer to the AECP context + * \param pdu pointer to the AECP PDU + * \param avtp_len length of the AVTP payload. + * \param mac_src source MAC address of the received PDU + * \param port_id avdecc port / interface index on which we received the PDU + */ +static int aecp_aem_received_entity_command(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, u16 avtp_len, u8 *mac_src, unsigned int port_id) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_port *port_rsp = &entity->avdecc->port[port_id]; + struct aecp_aem_pdu *aecp_rsp = NULL; + struct net_tx_desc *desc_rsp; + u8 status; + u16 cmd_type; + u16 len = sizeof(struct aecp_aem_pdu); //data size after AVTP control hdr + int rc = 0; + + desc_rsp = aecp_net_tx_prepare(port_rsp, pdu, &len, (void **)&aecp_rsp); //FIXME check if we can re-use same buf + if (!desc_rsp) { + os_log(LOG_ERR, "aecp(%p) Cannot alloc tx descriptor\n", aecp); + rc = -1; + goto exit; + } + + cmd_type = AECP_AEM_GET_CMD_TYPE(pdu); + + os_log(LOG_DEBUG, "aecp(%p) command (%x, %s) seq_id(%d)\n", aecp, cmd_type, aecp_aem_cmdtype2string(cmd_type), ntohs(pdu->sequence_id)); + + switch (cmd_type) { + case AECP_AEM_CMD_CONTROLLER_AVAILABLE: + { + status = AECP_AEM_SUCCESS; + break; + } + default: + status = AECP_AEM_NOT_IMPLEMENTED; + break; + } + + rc = aecp_aem_net_tx_response(aecp, port_rsp, desc_rsp, status, mac_src, len); + +exit: + return rc; +} + +/** Handle normal and unsolicited AECP AEM responses coming from the entity over the network + * + * \return 0 on success or -1 on failure. + * \param aecp AECP context that received the response. + * \param pdu Pointer to received AECP AEM PDU. + * \param status Status field from the AECP packet (contained within the AVTP part). + * \param len Length of the received PDU. + * \param port_id avdecc port / interface index on which we received the PDU + */ +static int aecp_aem_received_entity_response(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, u8 status, u16 len, unsigned int port_id) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avdecc_ctx *avdecc = entity->avdecc; + struct ipc_tx *ipc = &avdecc->ipc_tx_controller; + unsigned int ipc_dst = IPC_DST_ALL; + struct inflight_ctx *entry; + int rc; + + if (!AECP_AEM_GET_U(pdu)) { + entry = avdecc_inflight_find(&aecp->inflight_network, ntohs(pdu->sequence_id)); + if (entry) { + ipc = (void *)entry->data.priv[0]; + ipc_dst = (unsigned int)entry->data.priv[1]; + + // Handle IN_PROGRESS responses + if (status == AECP_AEM_IN_PROGRESS) { + avdecc_inflight_restart(entry); + rc = 0; + goto exit; + } + else + avdecc_inflight_remove(entity, entry); + + } else { + rc = -1; + goto exit; + } + } + + if (ipc) + rc = aecp_aem_ipc_tx_response(aecp, pdu, status, len, ipc, ipc_dst); + else + rc = -1; + +exit: + return rc; +} + +/** Handle normal and unsolicited AECP AEM responses coming from the controller over the network + * + * \return 0 on success or -1 on failure. + * \param aecp AECP context that received the response. + * \param pdu Pointer to received AECP AEM PDU. + * \param status Status field from the AECP packet (contained within the AVTP part). + * \param len Length of the received PDU. + * \param port_id avdecc port / interface index on which we received the PDU + */ +static int aecp_aem_received_controller_response(struct aecp_ctx *aecp, struct aecp_aem_pdu *pdu, u8 status, u16 len, unsigned int port_id) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct inflight_ctx *entry; + int rc = 0; + + if (!AECP_AEM_GET_U(pdu)) { + entry = avdecc_inflight_find(&aecp->inflight_network, ntohs(pdu->sequence_id)); + if (entry) { + switch (AECP_AEM_GET_CMD_TYPE(pdu)) { + case AECP_AEM_CMD_CONTROLLER_AVAILABLE: + { + struct unsolicited_ctx *unsolicited_entry; + + unsolicited_entry = aecp_unsolicited_find(aecp, pdu->entity_id, port_id); + if (!unsolicited_entry) { + os_log(LOG_ERR,"aecp(%p) port(%u) couldn't retrieve registered controller(%016"PRIx64").\n", + aecp, port_id, ntohll(pdu->entity_id)); + + break; + } + + /* Restart monitor timer */ + timer_restart(&unsolicited_entry->monitor_timer, MONITOR_TIMER_INTERVAL); + + os_log(LOG_DEBUG,"aecp(%p) port(%u) controller(%016"PRIx64") RECEIVED CONTROLLER_AVAILABLE, restarting monitor timer.\n", + aecp, port_id, ntohll(pdu->entity_id)); + + break; + } + default: + break; + } + + avdecc_inflight_remove(entity, entry); + + rc = 0; + goto exit; + } + else { + rc = -1; + goto exit; + } + } + +exit: + return rc; +} + +__init unsigned int aecp_data_size(struct avdecc_entity_config *cfg) +{ + return cfg->max_unsolicited_registrations * sizeof(struct unsolicited_ctx); +} + +__init int aecp_init_timers(struct entity *entity) +{ + struct avb_interface_dynamic_desc *avb_itf_dynamic; + unsigned int num_interfaces; + int i; + + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + for (i = 0; i < num_interfaces; i++) { + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, i, NULL); + + if (entity->milan_mode) { + avb_itf_dynamic->u.milan.async_get_counters_unsolicited_notification_timer.func = aecp_milan_get_counters_async_unsolicited_notification_timer_handler; + avb_itf_dynamic->u.milan.async_get_counters_unsolicited_notification_timer.data = avb_itf_dynamic; + + if (timer_create(entity->avdecc->timer_ctx, &avb_itf_dynamic->u.milan.async_get_counters_unsolicited_notification_timer, 0, + AECP_MILAN_GET_COUNTERS_ASYNC_UNSOLICITED_NOTIFICATION_GRANULARITY_MS) < 0) + goto err_counter_timer_create; + } + + avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer.func = aecp_get_as_path_async_unsolicited_notification_timer_handler; + avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer.data = avb_itf_dynamic; + avb_itf_dynamic->async_get_as_path_unsolicited_notification_pending = false; + avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer_running = false; + + if (timer_create(entity->avdecc->timer_ctx, &avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer, 0, + AECP_GET_AS_PATH_ASYNC_UNSOLICITED_NOTIFICATION_GRANULARITY_MS) < 0) + goto err_path_timer_create; + } + + return 0; + +err_path_timer_create: + if (entity->milan_mode) + timer_destroy(&avb_itf_dynamic->u.milan.async_get_counters_unsolicited_notification_timer); + +err_counter_timer_create: + while (i--) { + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, i, NULL); + + if (entity->milan_mode) + timer_destroy(&avb_itf_dynamic->u.milan.async_get_counters_unsolicited_notification_timer); + + timer_destroy(&avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer); + } + + return -1; +} + +__init int aecp_init(struct aecp_ctx *aecp, void *data, struct avdecc_entity_config *cfg) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + + list_head_init(&aecp->inflight_network); + list_head_init(&aecp->inflight_application); + + aecp->max_unsolicited_registrations = cfg->max_unsolicited_registrations; + aecp->unsolicited_storage = (struct unsolicited_ctx *)data; + + aecp_unsolicited_init(aecp); + + if (aecp_init_timers(entity) < 0) + goto err_timer_init; + + os_log(LOG_INIT, "aecp(%p) done\n", aecp); + + return 0; + +err_timer_init: + return -1; +} + +__exit static void aecp_exit_timers(struct aecp_ctx *aecp) +{ + struct entity *entity = container_of(aecp, struct entity, aecp); + struct avb_interface_dynamic_desc *avb_itf_dynamic; + unsigned int num_interfaces; + int i; + + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + for (i = 0; i < num_interfaces; i++) { + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, i, NULL); + + if (entity->milan_mode) + timer_destroy(&avb_itf_dynamic->u.milan.async_get_counters_unsolicited_notification_timer); + + timer_destroy(&avb_itf_dynamic->async_get_as_path_unsolicited_notification_timer); + } +} + +__exit int aecp_exit(struct aecp_ctx *aecp) +{ + aecp_exit_timers(aecp); + + os_log(LOG_INIT, "done\n"); + + return 0; +} + +/** Main AECP receive function. + * \return 0 on success, negative otherwise + * \param port pointer to the AVDECC port + * \param pdu pointer to the AECP PDU + * \param msg_type AECP message type (9.2.1.1.5) + * \param status AECP status (9.2.1.1.6) + * \param len length of the AECP PDU + * \param mac_src source MAC address of the received PDU + */ +int aecp_net_rx(struct avdecc_port *port, struct aecp_pdu *pdu, u8 msg_type, u8 status, u16 len, u8 *mac_src) +{ + u64 controller_entity_id = pdu->controller_entity_id; + u64 entity_id = pdu->entity_id; + struct entity *entity; + struct aecp_ctx *aecp = NULL; + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + + os_log(LOG_DEBUG, "port(%u) AECP message len(%u) source mac(%012"PRIx64")\n", port->port_id, len, NTOH_MAC_VALUE(mac_src)); + + switch(msg_type) { + case AECP_AEM_COMMAND: + entity = avdecc_get_entity(avdecc, entity_id); + if (!entity || !avdecc_entity_port_valid(entity, port->port_id)) { + os_log(LOG_ERR, "avdecc(%p) port(%u) aecp command does not match any local entity (message type(%d), entity(%"PRIx64"), controller(%"PRIx64")) \n", + avdecc, port->port_id, msg_type, ntohll(entity_id), ntohll(controller_entity_id)); + goto exit; + } + aecp = &entity->aecp; + + if (AECP_AEM_GET_CMD_TYPE((struct aecp_aem_pdu *)pdu) == AECP_AEM_CMD_CONTROLLER_AVAILABLE) { + /* Entity sent a CONTROLLER_AVAILABLE command to the controller */ + aecp_aem_received_entity_command(aecp, (struct aecp_aem_pdu *)pdu, len, mac_src, port->port_id); + } else { + /* Controller's commands sent to entity */ + aecp_aem_received_controller_command(aecp, (struct aecp_aem_pdu *)pdu, len, mac_src, port->port_id); + } + + break; + + case AECP_AEM_RESPONSE: + /* Response to command sent from the controller entity */ + entity = avdecc_get_entity(avdecc, controller_entity_id); + + if (!entity || !avdecc_entity_port_valid(entity, port->port_id)) { + os_log(LOG_ERR, "avdecc(%p) port(%u) aecp response does not match any local controller (message type(%d), entity(%"PRIx64"), controller(%"PRIx64")) \n", + avdecc, port->port_id, msg_type, ntohll(entity_id), ntohll(controller_entity_id)); + // TODO handle IDENTITY_NOTIFICATION responses + goto exit; + } + + aecp = &entity->aecp; + + if (AECP_AEM_GET_CMD_TYPE((struct aecp_aem_pdu *)pdu) == AECP_AEM_CMD_CONTROLLER_AVAILABLE) { + /* Controller responded to entity's CONTROLLER_AVAILABLE command */ + aecp_aem_received_controller_response(aecp, (struct aecp_aem_pdu *)pdu, status, len, port->port_id); + } else { + /* Entity responded to controller's command */ + aecp_aem_received_entity_response(aecp, (struct aecp_aem_pdu *)pdu, status, len, port->port_id); + } + + break; + + case AECP_VENDOR_UNIQUE_COMMAND: + entity = avdecc_get_entity(avdecc, entity_id); + if (!entity || !avdecc_entity_port_valid(entity, port->port_id)) { + os_log(LOG_ERR, "avdecc(%p) port(%u) aecp vendor unique command does not match any local entity (message type(%d), entity(%"PRIx64"), controller(%"PRIx64")) \n", + avdecc, port->port_id, msg_type, ntohll(entity_id), ntohll(controller_entity_id)); + goto exit; + } + + aecp = &entity->aecp; + aecp_vendor_specific_received_command(aecp, (struct aecp_vuf_pdu *)pdu, len, mac_src, port->port_id); + break; + + default: + os_log(LOG_ERR, "avdecc(%p) port(%u) aecp message type (%d) not supported\n", avdecc, port->port_id, msg_type); + break; + } + +exit: + debug_dump_aecp_aem(aecp, (struct aecp_aem_pdu *)pdu, msg_type, status); + return 0; +} + +/** Main AECP IPC receive function, for controller entities. + * \return 0 on success or negative value otherwise. + * \param entity Controller entity the IPC was received for. + * \param aecp_msg Pointer to the received AECP message. + * \param len Length of the received IPC message payload. + * \param ipc IPC the message was received through. + */ +int aecp_ipc_rx_controller(struct entity *entity, struct ipc_aecp_msg *aecp_msg, u32 len, struct ipc_tx *ipc, unsigned int ipc_dst) +{ + struct aecp_aem_pdu *aecp_msg_pdu = (struct aecp_aem_pdu *)aecp_msg->buf; + struct avdecc_ctx *avdecc = entity->avdecc; + struct aecp_aem_pdu *aecp_cmd = NULL; + struct net_tx_desc *tx_desc = NULL; + struct entity_discovery *entity_disc; + unsigned int num_interfaces; + struct avdecc_port *port; + u8 *mac_dst; + u64 entity_id; + int rc; + + os_log(LOG_DEBUG, "avdecc(%p) ipc_tx(%p) aecp_msg(%p) len(%u)\n", avdecc, ipc, aecp_msg, len); + + /* Only AEM messages are supported, so check that the provided message is big enough for at least the base AEM fields. */ + if (len < (offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu))) { + os_log(LOG_ERR, "avdecc(%p) Invalid IPC AECP AEM message size (%u instead of at least %lu)\n", + avdecc, len, offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu)); + rc = -1; + goto exit; + } + + debug_dump_aecp_aem(&entity->aecp, aecp_msg_pdu, aecp_msg->msg_type, aecp_msg->status); + + entity_id = aecp_msg_pdu->entity_id; + + /* Get number of supported interfaces for the controller entity. */ + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + entity_disc = adp_find_entity_discovery_any(avdecc, entity_id, num_interfaces); + if (!entity_disc) { + os_log(LOG_ERR, "avdecc(%p) Cannot send command, receiving entity(%"PRIx64") not visible on the network.\n", + avdecc, htonll(aecp_msg_pdu->entity_id)); + rc = -1; + goto exit; + } + + /* Send command on the port on which we discovered the entity. */ + port = discovery_to_avdecc_port(entity_disc->disc); + + tx_desc = aecp_net_tx_prepare(port, aecp_msg->buf, &aecp_msg->len, (void **)&aecp_cmd); + if (!tx_desc) { + os_log(LOG_ERR, "avdecc(%p) Cannot alloc tx descriptor\n", avdecc); + rc = -1; + goto exit; + } + + copy_64(&aecp_cmd->controller_entity_id, &entity->desc->entity_id); + copy_64(&aecp_cmd->entity_id, &entity_disc->info.entity_id); + mac_dst = entity_disc->info.mac_addr; + + rc = aecp_aem_send_command(&entity->aecp, port, aecp_cmd, tx_desc, mac_dst, aecp_msg->len, ipc, ipc_dst); + if (rc < 0) { + os_log(LOG_ERR, "avdecc(%p) Cannot send aecp command\n", avdecc); + rc = -1; + goto exit; + } + +exit: + return rc; +} + +void aecp_ipc_rx_controlled(struct entity *entity, struct ipc_aecp_msg *aecp_msg, u32 len) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + struct aecp_ctx *aecp = &entity->aecp; + struct aecp_aem_pdu *aecp_rx_rsp; + struct control_descriptor *ctrl_desc; + struct inflight_ctx *entry = NULL; + u64 inflight_controller_id = 0; + u16 cmd_type; + bool desc_changed = false; + + os_log(LOG_DEBUG, "entity(%p) aecp_msg(%p) len(%u)\n", entity, aecp_msg, len); + + /* Only AEM messages are supported, so check that the provided message is big enough for at least the base AEM fields. */ + if (len < (offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu))) { + os_log(LOG_ERR, "avdecc(%p) Invalid IPC AECP AEM message size (%u instead of at least %lu)\n", + avdecc, len, offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu)); + return; + } + + aecp_rx_rsp = (struct aecp_aem_pdu *)aecp_msg->buf; + + debug_dump_aecp_aem(&entity->aecp, aecp_rx_rsp, aecp_msg->msg_type, aecp_msg->status); + + if (aecp_msg->msg_type != AECP_AEM_RESPONSE) { + os_log(LOG_ERR, "avdecc(%p) Received message type %d but only AECP_AEM_RESPONSE allowed on channel\n", avdecc, aecp_msg->msg_type); + return; + } + + /* Get the in-flight entry if regular AECP response (Not Unsolicited)*/ + if (!AECP_AEM_GET_U(aecp_rx_rsp)) { + entry = aem_inflight_find_controller(&aecp->inflight_application, ntohs(aecp_rx_rsp->sequence_id), aecp_rx_rsp->controller_entity_id); + if (!entry) { + os_log(LOG_ERR, "avdecc(%p) Received regular AECP response from application with sequence id %d," + "but no command was received with that sequence id.\n", avdecc, ntohs(aecp_rx_rsp->sequence_id)); + return; + } + } + + /* Update AEM structures only on successful responses + * FIXME rely on app to maintain descriptor values instead? + */ + + cmd_type = AECP_AEM_GET_CMD_TYPE(aecp_rx_rsp); + switch (cmd_type) { + case AECP_AEM_CMD_SET_CONTROL: + { + struct aecp_aem_set_get_control_pdu *set_control_rsp; + void *values_rsp; + u16 values_len, values_len_max_rsp; + int rc; + + if (len < (offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu))) { + os_log(LOG_ERR, "avdecc(%p) Invalid AEM SET_CONTROL message size (%u < %lu)\n", + avdecc, len, offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + + sizeof(struct aecp_aem_set_get_control_pdu)); + return; + } + + set_control_rsp = (struct aecp_aem_set_get_control_pdu *)(aecp_rx_rsp + 1); + values_rsp = set_control_rsp + 1; + values_len = aecp_msg->len - sizeof(struct aecp_aem_pdu) - sizeof(struct aecp_aem_set_get_control_pdu); + values_len_max_rsp = len - (offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu)); + + ctrl_desc = aem_get_descriptor(entity->aem_descs, ntohs(set_control_rsp->descriptor_type), + ntohs(set_control_rsp->descriptor_index), NULL); + + if (!ctrl_desc) { + os_log(LOG_ERR, "avdecc(%p) Control descriptor (type = %d, index = %d) reported by application not found.\n", + avdecc, ntohs(set_control_rsp->descriptor_type), ntohs(set_control_rsp->descriptor_index)); + return; + } + + if (aecp_msg->status == AECP_AEM_SUCCESS) { + u16 new_values_len; + void *new_values; + + /* On successful regular response: get values from the inflight (coming from the command) + and write it back into the response msg */ + if (!AECP_AEM_GET_U(aecp_rx_rsp)) { + + new_values_len = entry->data.len - (sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu)); + new_values = entry->data.pdu.buf + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu); + /* Check if we have enough space in the response msg*/ + if (values_len_max_rsp < new_values_len) { + os_log(LOG_ERR, "avdecc(%p) Not enough space for values in the IPC AECP AEM message" + "(%u instead of at least %u)\n", + avdecc, values_len_max_rsp, new_values_len); + return; + } else { + /* Copy the new value and update the AECP msg length */ + os_memcpy(values_rsp, new_values, new_values_len); + aecp_msg->len = entry->data.len; + } + } else { + /* On unsolicited notification, take the value coming from response msg */ + new_values_len = values_len; + new_values = values_rsp; + } + + /* Validate and copy values to descriptor */ + rc = aecp_aem_control_pdu_to_desc(ctrl_desc, new_values, new_values_len); + + if (rc < 0) { + os_log(LOG_ERR, "avdecc(%p) Application reported invalid Control value for descriptor %d\n", + avdecc, ntohs(set_control_rsp->descriptor_index)); + return; + } else if (rc > 0) { + /* Value changed: send an unsolicited notification later*/ + desc_changed = true; + + } + } else { + /* On failed regular response: the response msg should contain the current value in the descriptor*/ + if (!AECP_AEM_GET_U(aecp_rx_rsp)) { + + rc = aecp_aem_control_desc_to_pdu(ctrl_desc, values_rsp, values_len_max_rsp); + if (rc < 0) { + os_log(LOG_ERR, "avdecc(%p) Cannot copy control descriptor values to IPC AECP AEM message\n", avdecc); + return; + } + + aecp_msg->len = rc + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_set_get_control_pdu); + } else { + /* Unsolicited notification with error status have no meaning*/ + os_log(LOG_ERR, "avdecc(%p) Unsolicited notifications can not have error status.\n", avdecc); + return; + } + } + break; + } + case AECP_AEM_CMD_START_STREAMING: + { + struct aecp_aem_start_streaming_cmd_pdu *start_streaming_rsp; + int rc = 0; + + if (len < (offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_start_streaming_cmd_pdu))) { + os_log(LOG_ERR, "avdecc(%p) Invalid AEM START_STREAMING message size (%u < %lu)\n", + avdecc, len, offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + + sizeof(struct aecp_aem_start_streaming_cmd_pdu)); + return; + } + + start_streaming_rsp = (struct aecp_aem_start_streaming_cmd_pdu *)(aecp_rx_rsp + 1); + + if (aecp_msg->status == AECP_AEM_SUCCESS) { + rc = acmp_start_streaming(entity, ntohs(start_streaming_rsp->descriptor_type), ntohs(start_streaming_rsp->descriptor_index)); + if (rc < 0) { + os_log(LOG_ERR, "avdecc(%p) Cannot start stream and update binding parameters\n", avdecc); + return; + } + + /* Value changed: send an unsolicited notification later*/ + if (rc > 0) + desc_changed = true; + } + + break; + } + case AECP_AEM_CMD_STOP_STREAMING: + { + struct aecp_aem_stop_streaming_cmd_pdu *stop_streaming_rsp; + int rc = 0; + + if (len < (offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_stop_streaming_cmd_pdu))) { + os_log(LOG_ERR, "avdecc(%p) Invalid AEM STOP_STREAMING message size (%u < %lu)\n", + avdecc, len, offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + + sizeof(struct aecp_aem_stop_streaming_cmd_pdu)); + return; + } + + stop_streaming_rsp = (struct aecp_aem_stop_streaming_cmd_pdu *)(aecp_rx_rsp + 1); + + if (aecp_msg->status == AECP_AEM_SUCCESS) { + rc = acmp_stop_streaming(entity, ntohs(stop_streaming_rsp->descriptor_type), ntohs(stop_streaming_rsp->descriptor_index)); + if (rc < 0) { + os_log(LOG_ERR, "avdecc(%p) Cannot stop stream and update binding parameters\n", avdecc); + return; + } + + /* Value changed: send an unsolicited notification later*/ + if (rc > 0) + desc_changed = true; + } + + break; + } + case AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS: + case AECP_AEM_CMD_ADD_AUDIO_MAPPINGS: + { + if (len < (offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_modify_audio_mappings_cmd_pdu))) { + os_log(LOG_ERR, "avdecc(%p) Invalid AEM ADD/REMOVE_AUDIO_MAPPINGS message size (%u < %lu)\n", + avdecc, len, offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + + sizeof(struct aecp_aem_modify_audio_mappings_cmd_pdu)); + return; + } + + if (aecp_msg->status == AECP_AEM_SUCCESS) { + desc_changed = true; + } + + break; + } + case AECP_AEM_CMD_GET_AUDIO_MAP: + { + if (len < (offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + sizeof(struct aecp_aem_get_audio_map_rsp_pdu))) { + os_log(LOG_ERR, "avdecc(%p) Invalid AEM GET_AUDIO_MAP message size (%u < %lu)\n", + avdecc, len, offsetof(struct ipc_aecp_msg, buf) + sizeof(struct aecp_aem_pdu) + + sizeof(struct aecp_aem_get_audio_map_rsp_pdu)); + return; + } + + break; + } + default: + break; + } + + // Respond to the in-flight command if present + if (entry) { + struct avdecc_port *port = &avdecc->port[entry->data.port_id]; + + inflight_controller_id = entry->data.pdu.aem.controller_entity_id; + + if (aecp_aem_prepare_send_response(aecp, port, aecp_msg->buf, inflight_controller_id, ntohs(aecp_rx_rsp->sequence_id), aecp_msg->status, 0, + entry->data.mac_dst, aecp_msg->len) < 0) { + os_log(LOG_ERR, "avdecc(%p) port(%u) couldn't send response back to requesting controller(%016"PRIx64")\n", + avdecc, port->port_id, ntohll(inflight_controller_id)); + avdecc_inflight_remove(entity, entry); + return; + } + + avdecc_inflight_remove(entity, entry); + } + + if ((aecp_msg->status == AECP_AEM_SUCCESS) && desc_changed) + { + switch (cmd_type) { + case AECP_AEM_CMD_WRITE_DESCRIPTOR: + case AECP_AEM_CMD_SET_CONFIGURATION: + case AECP_AEM_CMD_SET_STREAM_FORMAT: + case AECP_AEM_CMD_SET_VIDEO_FORMAT: + case AECP_AEM_CMD_SET_SENSOR_FORMAT: + case AECP_AEM_CMD_SET_STREAM_INFO: + case AECP_AEM_CMD_SET_NAME: + case AECP_AEM_CMD_SET_ASSOCIATION_ID: + case AECP_AEM_CMD_SET_SAMPLING_RATE: + case AECP_AEM_CMD_SET_CLOCK_SOURCE: + case AECP_AEM_CMD_SET_CONTROL: + case AECP_AEM_CMD_INCREMENT_CONTROL: + case AECP_AEM_CMD_DECREMENT_CONTROL: + case AECP_AEM_CMD_SET_SIGNAL_SELECTOR: + case AECP_AEM_CMD_SET_MIXER: + case AECP_AEM_CMD_SET_MATRIX: + case AECP_AEM_CMD_START_STREAMING: + case AECP_AEM_CMD_STOP_STREAMING: + case AECP_AEM_CMD_REBOOT: + case AECP_AEM_CMD_ADD_AUDIO_MAPPINGS: + case AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS: + case AECP_AEM_CMD_ADD_VIDEO_MAPPINGS: + case AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS: + case AECP_AEM_CMD_ADD_SENSOR_MAPPINGS: + case AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS: + { + // TODO check for acquired, send to controller ( if != from previous) + aecp_aem_send_sync_unsolicited_notification(aecp, (struct aecp_aem_pdu *)aecp_msg->buf, inflight_controller_id, aecp_msg->len); + + break; + } + default: + break; + } + } +} diff --git a/avdecc/aecp.h b/avdecc/aecp.h new file mode 100644 index 0000000..39a231b --- /dev/null +++ b/avdecc/aecp.h @@ -0,0 +1,75 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AECP common definitions +*/ + +#ifndef _AECP_H_ +#define _AECP_H_ + +#include "common/types.h" +#include "common/list.h" +#include "common/aecp.h" +#include "common/net.h" +#include "common/ipc.h" +#include "common/random.h" +#include "common/timer.h" + +#define MILAN_PROTOCOL_VERSION 1 +#define MONITOR_TIMER_GRANULARITY 100 /* Software timer granularity */ +#define MONITOR_TIMER_INTERVAL_MIN 30000 /* 30sec */ +#define MONITOR_TIMER_INTERVAL_MAX 60000 /* 60sec */ +#define MONITOR_TIMER_INTERVAL ((unsigned int)random_range(MONITOR_TIMER_INTERVAL_MIN, MONITOR_TIMER_INTERVAL_MAX - MONITOR_TIMER_GRANULARITY)) + +/* Used for GET_COUNTERS unsolicited notifications: Per AVNU.IO.CONTROL 7.5.2 : we should not send more than one unsolicited notification per descriptor per second. */ +#define AECP_MILAN_GET_COUNTERS_ASYNC_UNSOLICITED_NOTIFICATION_MS 1000 /* 1sec */ +#define AECP_MILAN_GET_COUNTERS_ASYNC_UNSOLICITED_NOTIFICATION_GRANULARITY_MS 100 + +#define AECP_GET_AS_PATH_ASYNC_UNSOLICITED_NOTIFICATION_MS 1000 /* 1sec */ +#define AECP_GET_AS_PATH_ASYNC_UNSOLICITED_NOTIFICATION_GRANULARITY_MS 100 + +#define MILAN_CERTIFICATION_VERSION(a, b, c, d) ((u32)((u32)a << 24 | (u32)b << 16 | (u32)c << 8 | (u32)d)) + +struct unsolicited_ctx { + struct list_head list; + u8 mac_dst[6]; /**< Controller MAC address. */ + u64 controller_id; /**< Controller entity ID. */ + u16 port_id; /**< Port on which the controller has registered. */ + u16 sequence_id; /**< Sequence id of the next unsolicited notifications. */ + struct timer monitor_timer; /**< Timer to monitor if the controller is still available. Per AVNU.IO.CONTROL 7.5.3 */ + struct aecp_ctx *aecp; /**< Parent AECP context. */ +}; + +/** + * Context variables for the AECP protocol. + */ +struct aecp_ctx { + struct list_head inflight_network; /**< List of AECP commands in-flight on the network (response expected from the network) */ + struct list_head inflight_application; /**< List of AECP commands in-flight within the application (response expected from the local application) */ + struct list_head unsolicited; /**< List of controllers that have registered to received unsolicited notifications from this entity. */ + u16 sequence_id; /**< Sequence ID to use for commands, in host byte order. */ + struct unsolicited_ctx *unsolicited_storage; + struct list_head free_unsolicited; + unsigned int max_unsolicited_registrations; +}; + +struct avdecc_port; +struct entity; + + +int aecp_init(struct aecp_ctx *aecp, void *data, struct avdecc_entity_config *cfg); +int aecp_exit(struct aecp_ctx *aecp); +unsigned int aecp_data_size(struct avdecc_entity_config *cfg); +int aecp_net_rx(struct avdecc_port *port, struct aecp_pdu *pdu, u8 msg_type, u8 status, u16 len, u8 *mac_src); +int aecp_ipc_rx_controller(struct entity *entity, struct ipc_aecp_msg *aecp_msg, u32 len, struct ipc_tx *ipc, unsigned int ipc_dst); +void aecp_ipc_rx_controlled(struct entity *entity, struct ipc_aecp_msg *aecp_msg, u32 len); +int aecp_aem_send_async_unsolicited_notification(struct aecp_ctx *aecp, u16 response_type, u16 descriptor_type, u16 descriptor_index); +void aecp_milan_register_get_counters_async_notification(struct entity *entity, unsigned int port_id); +void aecp_trigger_async_get_as_path_notification(struct entity *entity, unsigned int port_id); + +#endif /* _AECP_H_ */ diff --git a/avdecc/aem.c b/avdecc/aem.c new file mode 100644 index 0000000..2d4a5d4 --- /dev/null +++ b/avdecc/aem.c @@ -0,0 +1,168 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AEM common code + @details Implements AEM related functions +*/ + +#include "os/stdlib.h" + +#include "common/log.h" +#include "common/net.h" + +#include "genavb/aem.h" + +#include "aem.h" + + +/* According to IEEE 1722.1-20013, section 6.2.1.8, the entity ID is a unique EUI-64 assigned + * by the vendor manufacturing the device. We can only provide valid EUI-64 for OUIs we own, + * so we use the Freescale OUI-24, which also happens to be the OUI used to generate i.MX + * MAC addresses (meaning that at least for i.MX devices, the first 6 bytes of the entity ID + * should be equal to the MAC address of the first Ethernet interface). + * + * TODO: Add an API to allow a customer to provide its own entity IDs, without relying on + * the ones we (GenAVB) generate. + */ +static u8 FSL_OUI[3] = { 0x00, 0x04, 0x9f }; +static inline void compute_entity_id_from_mac(u8 *eui, u8 *mac_addr, u8 mod) +{ + eui[0] = FSL_OUI[0]; + eui[1] = FSL_OUI[1]; + eui[2] = FSL_OUI[2]; + eui[3] = mac_addr[3]; + eui[4] = mac_addr[4]; + eui[5] = mac_addr[5]; + eui[6] = 0x00; + eui[7] = mod; +} + +__init static void aem_single_dynamic_desc_init(struct aem_desc_hdr *aem_dynamic_descs, u16 type, void *data, u16 size, u16 total) +{ + struct aem_desc_hdr *desc = &aem_dynamic_descs[type]; + + desc->ptr = data; + desc->size = size; + desc->total = total; +} + +/** + * Allocates the AVDECC dynamic states memory and init the dynamic states descriptors + * \return pointer to the dynamic descriptors memory + * \param aem_descs pointer to the entity static descriptors + */ +__init void *aem_dynamic_descs_init(struct aem_desc_hdr *aem_descs, struct avdecc_entity_config *cfg) +{ + unsigned int size, path_sequence_size; + unsigned int num_interfaces, num_stream_inputs, num_stream_outputs; + void *aem_dynamic_descs; + unsigned int offset = 0; + + if (!aem_descs) + goto err; + + size = AEM_NUM_DESC_TYPES * sizeof(struct aem_desc_hdr); + + size += sizeof(struct entity_dynamic_desc); + + num_interfaces = aem_get_descriptor_max(aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + path_sequence_size = cfg->max_ptlv_entries * sizeof(struct ptp_clock_identity); + size += num_interfaces * (sizeof(struct avb_interface_dynamic_desc) + path_sequence_size); + + num_stream_outputs = aem_get_talker_streams(aem_descs); + size += num_stream_outputs * sizeof(struct stream_output_dynamic_desc); + + num_stream_inputs = aem_get_listener_streams(aem_descs); + size += num_stream_inputs * sizeof(struct stream_input_dynamic_desc); + + aem_dynamic_descs = os_malloc(size); + if (!aem_dynamic_descs) + goto err; + + os_memset(aem_dynamic_descs, 0, size); + + offset = AEM_NUM_DESC_TYPES * sizeof(struct aem_desc_hdr); + + aem_single_dynamic_desc_init(aem_dynamic_descs, AEM_DESC_TYPE_ENTITY, (char *)aem_dynamic_descs + offset, + sizeof(struct entity_dynamic_desc), 1); + + offset += sizeof(struct entity_dynamic_desc); + + aem_single_dynamic_desc_init(aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, (char *)aem_dynamic_descs + offset, + sizeof(struct avb_interface_dynamic_desc), num_interfaces); + + offset += num_interfaces * (sizeof(struct avb_interface_dynamic_desc) + path_sequence_size); + + aem_single_dynamic_desc_init(aem_dynamic_descs, AEM_DESC_TYPE_STREAM_OUTPUT, (char *)aem_dynamic_descs + offset, + sizeof(struct stream_output_dynamic_desc), num_stream_outputs); + + offset += num_stream_outputs * sizeof(struct stream_output_dynamic_desc); + + aem_single_dynamic_desc_init(aem_dynamic_descs, AEM_DESC_TYPE_STREAM_INPUT, (char *)aem_dynamic_descs + offset, + sizeof(struct stream_input_dynamic_desc), num_stream_inputs); + + offset += num_stream_inputs * sizeof(struct stream_input_dynamic_desc); + + return aem_dynamic_descs; + +err: + return NULL; +} + +__init void aem_init(struct aem_desc_hdr *aem_desc, struct avdecc_entity_config *cfg, int entity_num) +{ + unsigned char local_mac[6] = {0}; + int num_interfaces, i; + struct entity_descriptor *entity = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_ENTITY, 0, NULL); + u64 entity_id, association_id; + struct avdecc_config *avdecc_cfg = container_of(cfg, struct avdecc_config, entity_cfg[entity_num]); + + /* Fetch fields from (in order of descending priority): + * . the config file (if available) first, + * . or the AEM definition file, + * . or dynamically from the MAC address (for the entity id only), + * . or dynamically from the entity id (for the entity model id only). + */ + + if (entity->entity_capabilities & htonl(ADP_ENTITY_ASSOCIATION_ID_SUPPORTED)) { + if (cfg->association_id) { + association_id = htonll(cfg->association_id); + copy_64(&entity->association_id, &association_id); + } + } else + entity->association_id = 0; + + if (entity->association_id == 0) + entity->entity_capabilities &= ~htonl(ADP_ENTITY_ASSOCIATION_ID_VALID); + else + entity->entity_capabilities |= htonl(ADP_ENTITY_ASSOCIATION_ID_VALID); + + if (cfg->entity_id) { + entity_id = htonll(cfg->entity_id); + copy_64(&entity->entity_id, &entity_id); + } else if (entity->entity_id == 0) { + /* Entity IDs are per device so just use port0 address for all entities generated on all ports */ + if (net_get_local_addr(avdecc_cfg->logical_port_list[0], local_mac) >= 0) + compute_entity_id_from_mac((u8 *)&entity->entity_id, local_mac, entity_num); + } + + if (entity->entity_model_id == 0) { + os_memcpy(&entity->entity_model_id, &entity->entity_id, 8); + } + + + /* dynamic settings */ + num_interfaces = aem_get_descriptor_max(aem_desc, AEM_DESC_TYPE_AVB_INTERFACE); + for (i = 0; i < num_interfaces; i++) { + struct avb_interface_descriptor *interface = aem_get_descriptor(aem_desc, AEM_DESC_TYPE_AVB_INTERFACE, i, NULL); + + if (net_get_local_addr(avdecc_cfg->logical_port_list[i], local_mac) >= 0) + os_memcpy(interface->mac_address, local_mac, 6); + } + +} diff --git a/avdecc/aem.h b/avdecc/aem.h new file mode 100644 index 0000000..71d0338 --- /dev/null +++ b/avdecc/aem.h @@ -0,0 +1,201 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AEM common definitions +*/ + +#ifndef _AEM_H_ +#define _AEM_H_ + +#include "common/types.h" +#include "common/adp.h" +#include "common/srp.h" +#include "common/timer.h" +#include "common/random.h" +#include "genavb/aem_helpers.h" +#include "genavb/init.h" +#include "genavb/control_srp.h" +#include "genavb/ptp.h" +#include "adp_milan.h" +#include "adp_ieee.h" +#include "acmp.h" + +typedef enum { + RELEASED, + ACQUIRED +} entity_acquire_state; + +typedef enum { + UNLOCKED, + LOCKED +} entity_lock_state; + +/* MILAN Discovery, connection and control specification for talkers and listeners rev1.1a (Section 6.4) + * The List of the controllers registred for unsolicited notifications is stored in the aecp_ctx + */ +struct entity_dynamic_desc { + entity_lock_state lock_status; /**< Whether a controller has temporarily locked this entity or not. */ + u64 locking_controller_id; /**< ID of the controller currently owning (having locked) this entity, if any. (in network order) */ + + entity_acquire_state acquire_status; /**< Whether a controller has acquired this entity or not. */ + u64 acquiring_controller_id; /**< ID of the controller currently owning (having acquired) this entity, if any. (in network order) */ + + struct timer lock_timer; /**< Timer used for locking timeout */ +}; + +/* MILAN Discovery, connection and control specification for talkers and listeners rev1.1 a (Section 6.6) */ +struct avb_interface_dynamic_desc { + u64 gptp_grandmaster_id; + u32 propagation_delay; + + struct msrp_pdu_fv_domain msrp_domain[CFG_SR_CLASS_MAX]; + + u32 link_up; + u32 link_down; + u32 gptp_gm_changed; + + bool operational_state; /**< Link state of the interface (up/down) */ + bool as_capable; + + u16 max_ptlv_entries; + u16 num_ptlv_entries; + + struct entity *entity; /**< Pointer to the parent entity struct */ + u16 interface_index; /**< Interface index of this AVB interface */ + + struct timer async_get_as_path_unsolicited_notification_timer; /**< Timer used to regulate AECP GET_AS_PATH async unsolicited notification upon path_sequence changes. */ + u8 async_get_as_path_unsolicited_notification_pending:1; /**< Flag used to state if an unsolicited notification is registered to be sent (on next timer expiration) for this AVB_INTERFACE. */ + u8 async_get_as_path_unsolicited_notification_timer_running:1; /**< Flag used to state if the timer is started. */ + u8 reserved:6; + + union { + struct { + adp_milan_advertise_state_t state; /**< ADP advertise state */ + struct timer adp_delay_timer; /**< TMR_DELAY: random between 0 and 4 sec */ + struct timer adp_advertise_timer; /**< TMR_ADVERTISE: 5 sec */ + + struct timer async_get_counters_unsolicited_notification_timer; /**< Timer used to regulate AECP GET_COUNTERS async unsolicited notification upon descriptor changes. */ + } milan; + + struct { + adp_ieee_advertise_interface_state_t advertise_state; /**< ADP interface advertise state */ + } ieee; + } u; + + struct ptp_clock_identity path_sequence[]; /**< Variable size array ruled by avdecc_entity_cfg->max_ptlv_entries */ +}; + +struct listener_pair; + +struct srp_talker_params { + u64 stream_id; /**< (in network order) */ + u16 vlan_id; /**< (in network order) */ + u8 stream_dest_mac[6]; /**< (in network order) */ +}; + +/* Common structure between AVDECC IEEE 1722.1 TalkerStreamInfos and MILAN Discovery, connection + * and control specification for talkers and listeners rev1.1a (Section 6.7) + */ +struct stream_output_dynamic_desc { + + u64 stream_id; /**< (in network order) */ + u16 stream_vlan_id; /**< (in network order) */ + u8 stream_dest_mac[6]; /**< (in network order) */ + + sr_class_t stream_class; + + u32 presentation_time_offset; + + union { + struct { + genavb_talker_stream_declaration_type_t srp_talker_declaration_type; /**< SRP Talker declared attribute state (6.7.2)*/ + genavb_talker_stream_status_t srp_listener_status; /**< SRP Listener registered attribute state (6.7.2)*/ + + struct timer probe_tx_reception_timer; /**< 15 sec timer started on PROBE_TX reception */ + struct timer srp_talker_withdraw_timer; /**< Timer used to wait for two Leaveall period (on MAAP conflict or domain priority code change) on attribute withdraw before redeclaring it. */ + + u8 talker_declared:1; + u8 avtp_connected:1; + u8 maap_started:1; + u8 probe_tx_valid:1; /**< True if we received a PROBE_TX_COMMAND in the last 15 sec. */ + u8 srp_talker_withdraw_in_progress:1; /**< True if we are still waiting two LeaveALL periods after withdrawing the talker attribute */ + u8 reserved:3; + + struct srp_talker_params srp_talker_withdraw_params; /**< Contains SRP talker params in withdrawal phase. Valid only if srp_talker_withdraw_in_progress is True */ + + struct timer async_unsolicited_notification_timer; /**< Timer used to regulate AECP PDUs sent as async unsolicited notification upon descriptor changes. */ + + struct entity *entity; /**< Pointer to the parent entity struct */ + u16 unique_id; /**< Unique ID of the talker source */ + + struct msrp_failure_information failure; /**< The MSRP failure information */ + } milan; + + struct { + u16 connection_count; + struct listener_pair *listeners; + } ieee; + } u; +}; + +/* Common structure between AVDECC IEEE 1722.1 ListenerStreamInfos and MILAN Discovery, connection + * and control specification for talkers and listeners rev1.1a (Section 6.8) + */ +struct stream_input_dynamic_desc { + + u64 talker_entity_id; /**< (in network order) */ + u64 controller_entity_id; /**< (in network order) */ + u64 stream_id; /**< (in network order) */ + u16 talker_unique_id; /**< (in network order) */ + u16 flags; /**< (in network order) */ + u16 stream_vlan_id; /**< (in network order) */ + u8 stream_dest_mac[6]; /**< (in network order) */ + + union { + struct { + acmp_milan_listener_sink_sm_state_t state; + + struct timer acmp_retry_timer; /**< TMR_RETRY: 4 seconds */ + struct timer acmp_delay_timer; /**< TMR_DELAY: random between 0 and 1 sec */ + struct timer acmp_talker_registration_timer; /**< TMR_NO_TK: 10 seconds */ + + adp_milan_listener_sink_talker_state_t talker_state; /**< Talker's discovered state (6.8.4) */ + struct timer adp_discovery_timer; /**< TMR_NO_ADP: depends on the talker's PDU valid_time */ + + genavb_listener_stream_status_t srp_stream_status; /**< SRP stream state (6.8.8)*/ + acmp_milan_listener_sink_srp_state_t srp_state; /**< SRP sink state: used to track transition between Registering <-> Not registering */ + + u32 msrp_accumulated_latency; /**< The accumulated_latency from the talker advertise. */ + struct msrp_failure_information failure; /**< The MSRP failure information from the talker failed. */ + + struct timer async_unsolicited_notification_timer; /**< Timer used to regulate AECP PDUs sent as async unsolicited notification upon descriptor changes. */ + + struct entity *entity; /**< Pointer to the parent entity struct */ + u16 unique_id; /**< Unique ID of the listener sink */ + + u8 probing_status; /**< Probing status (6.8.6) ::acmp_milan_probing_status_t */ + u8 acmp_status; /**< ACMP status (6.8.6) (in network order) */ + + u16 probe_tx_seq_id; /**< Sequence ID of the sent PROBE_TX command */ + + u16 interface_index; /**< Talker's last received ADP ENTITY_AVAILABLE interface index (in network order)*/ + u32 available_index; /**< Talker's last received ADP ENTITY_AVAILABLE available index (in network order) */ + } milan; + + struct { + u8 connected; + unsigned int flags_priv; + } ieee; + } u; +}; + +struct aem_desc_hdr *aem_entity_static_init(void); +void aem_init(struct aem_desc_hdr *aem_desc, struct avdecc_entity_config *cfg, int entity_num); +void *aem_dynamic_descs_init(struct aem_desc_hdr *aem_descs, struct avdecc_entity_config *cfg); + +#endif /* _AEM_H_ */ diff --git a/avdecc/avdecc.c b/avdecc/avdecc.c new file mode 100644 index 0000000..8350048 --- /dev/null +++ b/avdecc/avdecc.c @@ -0,0 +1,2250 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVDECC common code + @details Handles all AVDECC entities +*/ + +#include "os/stdlib.h" +#include "os/string.h" + +#include "common/log.h" +#include "common/avtp.h" +#include "common/ether.h" +#include "common/srp.h" + +#include "genavb/aem.h" +#include "genavb/qos.h" + +#include "avdecc.h" +#include "avdecc_ieee.h" + +struct __attribute__ ((packed)) genavb_avdecc_managed_objects_default_ds_request { + avb_u16 container_id; + avb_u16 container_len; + avb_u16 clk_id_leaf_id; + avb_u16 clk_id_leaf_len; + avb_u16 priority1_leaf_id; + avb_u16 priority1_leaf_len; + avb_u16 clk_class_leaf_id; + avb_u16 clk_class_leaf_len; + avb_u16 offset_scaled_log_variance_leaf_id; + avb_u16 offset_scaled_log_variance_leaf_len; + avb_u16 clk_accuracy_leaf_id; + avb_u16 clk_accuracy_leaf_len; + avb_u16 priority2_leaf_id; + avb_u16 priority2_leaf_len; +}; + +struct __attribute__ ((packed)) genavb_avdecc_managed_objects_response_success { + avb_u16 container_id; //should be 0 + avb_u16 container_len; // should be 2 + leaves' data length + avb_u16 global_status; // should be 0 +}; + +struct __attribute__ ((packed)) genavb_avdecc_managed_objects_default_ds_response_success { + avb_u16 clk_id_leaf_id; // should be 0 + avb_u16 clk_id_leaf_len; // should be 2 + 8 = 10 + avb_u16 clk_id_leaf_status; // should be 0 + avb_u64 clk_id_leaf_value; // correct clockidentity + avb_u16 priority1_leaf_id; // should be 5 + avb_u16 priority1_leaf_len; // should be 2 + 1 + avb_u16 priority1_leaf_status; // should be 0 + avb_u8 priority1_leaf_value; // correct value + avb_u16 clk_class_leaf_id; // should be 2 + avb_u16 clk_class_leaf_len; // should be 2 + 1 + avb_u16 clk_class_leaf_status; // should be 0 + avb_u8 clk_class_leaf_value; // correct value + avb_u16 offset_scaled_log_variance_leaf_id; // should be 4 + avb_u16 offset_scaled_log_variance_leaf_len; // should be 2 + 2 + avb_u16 offset_scaled_log_variance_leaf_status; // should be 0 + avb_u16 offset_scaled_log_variance_leaf_value; //correct value + avb_u16 clk_accuracy_leaf_id; // should be 3 + avb_u16 clk_accuracy_leaf_len; // should be 2 + 1 + avb_u16 clk_accuracy_leaf_status; //should be 0 + avb_u8 clk_accuracy_leaf_value; // correct value + avb_u16 priority2_leaf_id; // should be 6 + avb_u16 priority2_leaf_len; // should be 2 + 1 + avb_u16 priority2_leaf_status; // should be 0 + avb_u8 priority2_leaf_value; // correct value +}; + +static const char *talker_stream_status2string(genavb_talker_stream_status_t status) +{ + switch (status) { + case2str(NO_LISTENER); + case2str(FAILED_LISTENER); + case2str(ACTIVE_AND_FAILED_LISTENERS); + case2str(ACTIVE_LISTENER); + default: + return (char *) "Unknown talker stream status"; + } +} + +static const char *talker_stream_declaration_type2string(genavb_talker_stream_declaration_type_t talker_declaration_type) +{ + switch (talker_declaration_type) { + case2str(NO_TALKER_DECLARATION); + case2str(TALKER_ADVERTISE); + case2str(TALKER_FAILED); + default: + return (char *) "Unknown talker declaration type"; + } +} + +static const char *listener_stream_status2string(genavb_listener_stream_status_t status) +{ + switch (status) { + case2str(NO_TALKER); + case2str(ACTIVE); + case2str(FAILED); + default: + return (char *) "Unknown listener stream status"; + } +} + +static const char *listener_stream_declaration_type2string(genavb_listener_stream_declaration_type_t listener_declaration_type) +{ + switch (listener_declaration_type) { + case2str(NO_LISTENER_DECLARATION); + case2str(LISTENER_READY); + case2str(LISTENER_READY_FAILED); + case2str(LISTENER_FAILED); + default: + return (char *) "Unknown listener declaration type"; + } +} + +/** gPTP IPC, sends a GENAVB_MSG_MANAGED_GET message to get the default_parameter_data_set container + * \return none + * \param port + */ +static void avdecc_ipc_managed_get_default_ds(struct avdecc_port *port) +{ + struct ipc_desc *desc; + struct genavb_avdecc_managed_objects_default_ds_request *request; + int rc; + + desc = ipc_alloc(&port->ipc_tx_gptp, sizeof(struct genavb_msg_managed_get)); + if (desc) { + desc->dst = IPC_DST_ALL; + desc->type = GENAVB_MSG_MANAGED_GET; + desc->len = sizeof(struct genavb_msg_managed_get); + desc->flags = 0; + + os_memset(&desc->u.managed_get, 0, sizeof(struct genavb_msg_managed_get)); + + request = (struct genavb_avdecc_managed_objects_default_ds_request *)&desc->u.managed_get; + + request->container_id = 0; + request->container_len = 24; + request->clk_id_leaf_id = 0; + request->priority1_leaf_id = 5; + request->clk_class_leaf_id = 2; + request->offset_scaled_log_variance_leaf_id = 4; + request->clk_accuracy_leaf_id = 3; + request->priority2_leaf_id = 6; + + rc = ipc_tx(&port->ipc_tx_gptp, desc); + if (rc < 0) { + os_log(LOG_ERR, "ipc_tx() failed (%d)\n", rc); + ipc_free(&port->ipc_tx_gptp, desc); + } + } else + os_log(LOG_ERR, "ipc_alloc() failed\n"); +} + +/** gPTP IPC response parser, parse a GENAVB_MSG_MANAGED_GET message that contains the default_parameter_data_set data + * \return none + * \param entity + * \param port_id + * \param managed_object_resp, the response of a managed_get request on default_parameter_data_set container + */ +static void avdecc_ipc_managed_parse_default_ds_response(struct entity *entity, unsigned int port_id, struct genavb_avdecc_managed_objects_response_success *managed_object_resp) +{ + struct avb_interface_descriptor *avb_itf; + struct genavb_avdecc_managed_objects_default_ds_response_success *dflt_param_data_set = (struct genavb_avdecc_managed_objects_default_ds_response_success *)(managed_object_resp + 1); + unsigned int container_len_expected, leaf_len_expected; + + if (managed_object_resp->global_status != 0) { + os_log(LOG_ERR, "Managed GET response status %u failed\n", managed_object_resp->global_status); + return; + } + + container_len_expected = sizeof(struct genavb_avdecc_managed_objects_default_ds_response_success); + container_len_expected += (sizeof(struct genavb_avdecc_managed_objects_response_success) - offsetof(struct genavb_avdecc_managed_objects_response_success, global_status)); + if (managed_object_resp->container_len != container_len_expected) { + os_log(LOG_ERR, "entity(%p): Managed object get default_parameter_data_set failed: status (%u), len : expected(%u) got(%u)\n", + entity, managed_object_resp->global_status, container_len_expected, managed_object_resp->container_len); + goto exit; + } + + avb_itf = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + + if (!avb_itf) { + os_log(LOG_ERR, "entity(%p): Unsupported avb interface (%u)\n", entity, port_id); + goto exit; + } + + leaf_len_expected = sizeof(dflt_param_data_set->clk_id_leaf_status) + sizeof(dflt_param_data_set->clk_id_leaf_value); + if (dflt_param_data_set->clk_id_leaf_status == 0 && dflt_param_data_set->clk_id_leaf_len == leaf_len_expected) { + avb_itf->clock_identity = get_64(&dflt_param_data_set->clk_id_leaf_value); + + } else { + os_log(LOG_ERR, "entity(%p): Managed object get clock identity failed: status (%u), len : expected(%u) got(%u)\n", + entity, dflt_param_data_set->clk_id_leaf_status, leaf_len_expected, dflt_param_data_set->clk_id_leaf_len); + goto exit; + } + + leaf_len_expected = sizeof(dflt_param_data_set->priority1_leaf_status) + sizeof(dflt_param_data_set->priority1_leaf_value); + if (dflt_param_data_set->priority1_leaf_status == 0 && dflt_param_data_set->priority1_leaf_len == leaf_len_expected) { + avb_itf->priority1 = dflt_param_data_set->priority1_leaf_value; + + } else { + os_log(LOG_ERR, "entity(%p): Managed object get clock identity failed: status (%u), len : expected(%u) got(%u)\n", + entity, dflt_param_data_set->priority1_leaf_status, leaf_len_expected, dflt_param_data_set->priority1_leaf_len); + goto exit; + } + + leaf_len_expected = sizeof(dflt_param_data_set->clk_class_leaf_status) + sizeof(dflt_param_data_set->clk_class_leaf_value); + if (dflt_param_data_set->clk_class_leaf_status == 0 && dflt_param_data_set->clk_class_leaf_len == leaf_len_expected) { + avb_itf->clock_class = dflt_param_data_set->clk_class_leaf_value; + + } else { + os_log(LOG_ERR, "entity(%p): Managed object get clock identity failed: status (%u), len : expected(%u) got(%u)\n", + entity, dflt_param_data_set->clk_class_leaf_status, leaf_len_expected, dflt_param_data_set->clk_class_leaf_len); + goto exit; + } + + leaf_len_expected = sizeof(dflt_param_data_set->offset_scaled_log_variance_leaf_status) + sizeof(dflt_param_data_set->offset_scaled_log_variance_leaf_value); + if (dflt_param_data_set->offset_scaled_log_variance_leaf_status == 0 && dflt_param_data_set->offset_scaled_log_variance_leaf_len == leaf_len_expected) { + avb_itf->offset_scaled_log_variance = dflt_param_data_set->offset_scaled_log_variance_leaf_value; + + } else { + os_log(LOG_ERR, "entity(%p): Managed object get clock identity failed: status (%u), len : expected(%u) got(%u)\n", + entity, dflt_param_data_set->offset_scaled_log_variance_leaf_status, leaf_len_expected, dflt_param_data_set->offset_scaled_log_variance_leaf_len); + goto exit; + } + + leaf_len_expected = sizeof(dflt_param_data_set->clk_accuracy_leaf_status) + sizeof(dflt_param_data_set->clk_accuracy_leaf_value); + if (dflt_param_data_set->clk_accuracy_leaf_status == 0 && dflt_param_data_set->clk_accuracy_leaf_len == leaf_len_expected) { + avb_itf->clock_accuracy = dflt_param_data_set->clk_accuracy_leaf_value; + + } else { + os_log(LOG_ERR, "entity(%p): Managed object get clock identity failed: status (%u), len : expected(%u) got(%u)\n", + entity, dflt_param_data_set->clk_accuracy_leaf_status, leaf_len_expected, dflt_param_data_set->clk_accuracy_leaf_len); + goto exit; + } + + leaf_len_expected = sizeof(dflt_param_data_set->priority2_leaf_status) + sizeof(dflt_param_data_set->priority2_leaf_value); + if (dflt_param_data_set->priority2_leaf_status == 0 && dflt_param_data_set->priority2_leaf_len == leaf_len_expected) { + avb_itf->priority2 = dflt_param_data_set->priority2_leaf_value; + + } else { + os_log(LOG_ERR, "entity(%p): Managed object get clock identity failed: status (%u), len : expected(%u) got(%u)\n", + entity, dflt_param_data_set->priority2_leaf_status, leaf_len_expected, dflt_param_data_set->priority2_leaf_len); + goto exit; + } + +exit: + return; +} + +/** gPTP IPC response, receive handler for GENAVB_MSG_MANAGED_GET messages + * \return none + * \param entity + * \param port_id + * \param managed_get_response, the response of a managed_get request + */ +static void avdecc_ipc_managed_get_response(struct entity *entity, unsigned int port_id, struct genavb_msg_managed_get_response *managed_get_response) +{ + struct genavb_avdecc_managed_objects_response_success *managed_object_resp = (struct genavb_avdecc_managed_objects_response_success *)managed_get_response; + + switch (managed_object_resp->container_id) { + case 0: // default_parameter_data_set + avdecc_ipc_managed_parse_default_ds_response(entity, port_id, managed_object_resp); + break; + + default: + os_log(LOG_ERR, "Unsupported managed container id %u\n", managed_object_resp->container_id); + break; + } +} + +void avdecc_inflight_restart(struct inflight_ctx *entry) +{ + timer_stop(&entry->timeout); + timer_start(&entry->timeout, entry->timeout_ms); +} + +/** Inflight timeout callback. + * \return none + * \param data timer private data + */ +void avdecc_inflight_timeout(void *data) +{ + struct inflight_ctx *entry = data; + int rc; + + rc = entry->cb(entry); + + os_log(LOG_DEBUG, "Inflight (%p) timeout, cb returned %d\n", entry, rc); + + if (rc == AVDECC_INFLIGHT_TIMER_STOP) { + list_del(&entry->list); + list_add(&entry->entity->free_inflight, &entry->list); + timer_stop(&entry->timeout); + } + else if (rc == AVDECC_INFLIGHT_TIMER_RESTART) + avdecc_inflight_restart(entry); + else + os_log(LOG_CRIT, "callback returned invalid return code...\n"); +} + +/** Gets a free inflight context. + * \return pointer to a free inflight context, NULL if not available + * \param entity pointer to the entity context to get an inflight entry from. + */ +struct inflight_ctx *avdecc_inflight_get(struct entity *entity) +{ + struct inflight_ctx *entry = NULL; + struct list_head *list_entry; + + if (!list_empty(&entity->free_inflight)) { + list_entry = list_first(&entity->free_inflight); + entry = container_of(list_entry, struct inflight_ctx, list); + list_del(list_entry); + } + else + os_log(LOG_ERR, "entity(%p) No more inflight entries available\n", entity); + + return entry; +} + +/** Enqueues the provided inflight context and starts its associated timeout. + * It is used to save a command PDU and to manage the retransmission. + * \return 0 + * \param inflight_head pointer to the list head + * \param entry pointer to the inflight context + * \param timeout timeout in ms + */ +int avdecc_inflight_start(struct list_head *inflight_head, struct inflight_ctx *entry, unsigned int timeout) +{ + + entry->list_head = inflight_head; + list_add(inflight_head, &entry->list); + entry->timeout_ms = timeout; + timer_start(&entry->timeout, timeout); + + os_log(LOG_DEBUG, "Started inflight (%p) with sequence id %d, timeout %d ms\n", entry, entry->data.sequence_id, timeout); + + return 0; +} + + +/** + * Puts an inflight entry back to the free list. + * The entry must not be in use: must be called after calling ::avdecc_inflight_get but before calling ::avdecc_inflight_start. + * \param entity Entity context the entry should be returned to. + * \param entry Inflight entry to return to the free inflight list. + */ +void avdecc_inflight_abort(struct entity *entity, struct inflight_ctx *entry) +{ + list_add(&entity->free_inflight, &entry->list); +} + + + +struct inflight_ctx *avdecc_inflight_find(struct list_head *inflight_head, u16 sequence_id) +{ + struct list_head *list_entry; + struct inflight_ctx *entry; + + list_entry = list_first(inflight_head); + + while (list_entry != inflight_head) { + entry = container_of(list_entry, struct inflight_ctx, list); + + if (sequence_id == entry->data.sequence_id) + return entry; + + list_entry = list_next(list_entry); + } + + return NULL; +} + +/** + * Checks the entity lock to see if the controller is authorized or not. + * \return false if the controller is authorized (either entity is not locked or + * it's the controller owning the lock), or true if entity is locked by another controller + * \param entity pointer to the entity structure to check + * \param controller_id controller entity ID trying to alter the entity state. + */ +bool avdecc_entity_is_locked(struct entity *entity, u64 controller_id) +{ + struct entity_dynamic_desc *entity_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_ENTITY, 0, NULL); + if ((entity_dynamic->lock_status == LOCKED) + && (entity_dynamic->locking_controller_id != controller_id)) { + + return true; + } + + return false; +} + +/** + * Checks the entity acquire status to see if the controller is authorized or not. + * \return false if the controller is authorized (either entity is not acquired or + * it's the controller owning the lock), or true if entity is locked by another controller + * \param entity pointer to the entity structure to check + * \param controller_id controller entity ID trying to alter the entity state. + */ +bool avdecc_entity_is_acquired(struct entity *entity, u64 controller_id) +{ + struct entity_dynamic_desc *entity_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_ENTITY, 0, NULL); + if ((entity_dynamic->acquire_status == ACQUIRED) + && (entity_dynamic->acquiring_controller_id != controller_id)) { + + return true; + } + + return false; +} + +/** + * Find an inflight entry matching the provided sequence ID and controller entity ID. + * \return pointer to found inflight entry or NULL. + * \param inflight_head Inflight list to search. + * \param sequence_id sequence ID to match. + * \param controller_id controller entity ID to match. + */ +struct inflight_ctx *aem_inflight_find_controller(struct list_head *inflight_head, u16 sequence_id, u64 controller_id) +{ + struct list_head *list_entry; + struct inflight_ctx *entry; + + list_entry = list_first(inflight_head); + + while (list_entry != inflight_head) { + entry = container_of(list_entry, struct inflight_ctx, list); + + if ((sequence_id == entry->data.sequence_id) && (controller_id == entry->data.pdu.aem.controller_entity_id)) + return entry; + + list_entry = list_next(list_entry); + } + + return NULL; +} + + +void avdecc_inflight_remove(struct entity *entity, struct inflight_ctx *entry) +{ + if (entry) { + timer_stop(&entry->timeout); + list_del(&entry->list); + list_add(&entity->free_inflight, &entry->list); + os_log(LOG_DEBUG, "Removed inflight (%p)\n", entry); + } +} + +/** + * Removes a PDU from the inflight list, based on the PDU sequence ID. + * \return * -1 if no inflight PDU matched the sequence ID. + * * 0 if a PDU was found. + * \param entity Pointer to entity context. + * \param inflight_head Inflight list to search/update. + * \param sequence_id Sequence ID to be searched and removed from the list. + * \param orig_seq_id On successful return, will contain the sequence ID of the original PDU that triggered the command. + * \param priv0 On successful return, will contain the inflight first entry private data. + * \param priv1 On successful return, will contain the inflight second entry private data. + */ +int avdecc_inflight_cancel(struct entity *entity, struct list_head *inflight_head, u16 sequence_id, u16 *orig_seq_id, void **priv0, void **priv1) +{ + struct inflight_ctx *entry; + + entry = avdecc_inflight_find(inflight_head, sequence_id); + if (entry) { + if (orig_seq_id) + *orig_seq_id = entry->data.orig_seq_id; + if (priv0) + *priv0 = (void *)entry->data.priv[0]; + if (priv1) + *priv1 = (void *)entry->data.priv[1]; + + avdecc_inflight_remove(entity, entry); + os_log(LOG_DEBUG, "Cancelled inflight (%p)\n", entry); + + return 0; + } + + return -1; +} + +__init static unsigned int avdecc_inflight_data_size(struct avdecc_entity_config *cfg) +{ + return cfg->max_inflights * sizeof(struct inflight_ctx); +} + +__init static int avdecc_inflight_init(struct entity *entity, void *data, struct avdecc_entity_config *cfg) +{ + int i; + + entity->inflight_storage = (struct inflight_ctx *)data; + entity->max_inflights = cfg->max_inflights; + + list_head_init(&entity->free_inflight); + + for (i = 0; i < entity->max_inflights; i++) { + list_add(&entity->free_inflight, &entity->inflight_storage[i].list); + entity->inflight_storage[i].timeout.data = &entity->inflight_storage[i]; + entity->inflight_storage[i].timeout.func = avdecc_inflight_timeout; + if (timer_create(entity->avdecc->timer_ctx, &entity->inflight_storage[i].timeout, 0, AVDECC_CFG_INFLIGHT_TIMER_RESOLUTION) < 0) { + os_log(LOG_ERR, "entity(%p) timer_create failed\n", entity); + goto err; + } + entity->inflight_storage[i].entity = entity; + } + + os_log(LOG_INIT, "avdecc(%p) entity(%p) %d inflight commands max\n", entity->avdecc, entity, entity->max_inflights); + + return 0; + +err: + while(i--) + timer_destroy(&entity->inflight_storage[i].timeout); + + return -1; +} + +__exit static int avdecc_inflight_exit(struct entity *entity) +{ + int i; + + for (i = 0; i < entity->max_inflights; i++) + timer_destroy(&entity->inflight_storage[i].timeout); + + return 0; +} + +static void avdecc_entity_lock_timeout(void *data) +{ + struct entity *entity = (struct entity *)data; + struct entity_dynamic_desc *entity_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_ENTITY, 0, NULL); + + entity_dynamic->lock_status = UNLOCKED; + + /* Send unsolicited notification (notify that the entity is now unlocked) + * Per AVNU.IO.CONTROL 7.5.2. + */ + if (entity->milan_mode) + aecp_aem_send_async_unsolicited_notification(&entity->aecp, AECP_AEM_CMD_LOCK_ENTITY, AEM_DESC_TYPE_ENTITY, 0); +} + +__init static bool avdecc_entity_check(struct entity *entity) +{ + struct avdecc_ctx *avdecc = entity->avdecc; + struct stream_descriptor *stream_desc; + struct control_descriptor *control_desc; + unsigned int num_interfaces, num_stream_input, num_stream_output, num_control; + u16 avb_itf_index, num_of_formats, control_value_type; + int i; + + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + if (num_interfaces > avdecc->port_max) { + os_log(LOG_ERR, "entity(%p) unsupported num interfaces %u, max ports %u\n", + entity, num_interfaces, avdecc->port_max); + goto err; + } + + for (i = 0; i < num_interfaces; i++) { + if (!avdecc->port[i].initialized) { + os_log(LOG_ERR, "entity(%p) unsupported avb interface index %u\n", entity, i); + goto err; + } + } + + num_stream_input = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT); + + for (i = 0; i < num_stream_input; i++) { + stream_desc = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_INPUT, i, NULL); + if (!stream_desc) { + os_log(LOG_ERR, "entity(%p) invalid stream input index %u\n", entity, i); + goto err; + } + + avb_itf_index = ntohs(stream_desc->avb_interface_index); + if (avb_itf_index >= avdecc->port_max || !avdecc->port[avb_itf_index].initialized) { + os_log(LOG_ERR, "entity(%p) unsupported avb interface index %u for stream input %u\n", + entity, avb_itf_index, i); + goto err; + } + + num_of_formats = ntohs(stream_desc->number_of_formats); + if (num_of_formats > AEM_NUM_FORMATS_MAX) { + os_log(LOG_ERR, "entity(%p) number of formats %u exceeding max %u for stream input %u\n", + entity, num_of_formats, AEM_NUM_FORMATS_MAX, i); + goto err; + } + } + + num_stream_output = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT); + + for (i = 0; i < num_stream_output; i++) { + stream_desc = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_STREAM_OUTPUT, i, NULL); + if (!stream_desc) { + os_log(LOG_ERR, "entity(%p) invalid stream output index %u\n", entity, i); + goto err; + } + + avb_itf_index = ntohs(stream_desc->avb_interface_index); + if (avb_itf_index >= avdecc->port_max || !avdecc->port[avb_itf_index].initialized) { + os_log(LOG_ERR, "entity(%p) unsupported avb interface index %u for stream output %u\n", + entity, avb_itf_index, i); + goto err; + } + + num_of_formats = ntohs(stream_desc->number_of_formats); + if (num_of_formats > AEM_NUM_FORMATS_MAX) { + os_log(LOG_ERR, "entity(%p) number of formats %u exceeding max %u for stream output %u\n", + entity, num_of_formats, AEM_NUM_FORMATS_MAX, i); + goto err; + } + } + + num_control = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_CONTROL); + + for (i = 0; i < num_control; i++) { + control_desc = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_CONTROL, i, NULL); + if (!control_desc) { + os_log(LOG_ERR, "entity(%p) invalid control index %u\n", entity, i); + goto err; + } + + control_value_type = AEM_CONTROL_GET_VALUE_TYPE(ntohs(control_desc->control_value_type)); + + switch (control_value_type) { + case AEM_CONTROL_LINEAR_UINT8: + if (ntohs(control_desc->number_of_values) > AEM_NUM_VALUE_DETAILS_LINEAR_U8_MAX) { + os_log(LOG_ERR, "entity(%p) control descriptor(%d) control_value_type(%u): number_of_values(%u) exceeding max (%u)\n", + entity, i, control_value_type, ntohs(control_desc->number_of_values), AEM_NUM_VALUE_DETAILS_LINEAR_U8_MAX); + goto err; + } + break; + + case AEM_CONTROL_UTF8: + if (ntohs(control_desc->number_of_values) > AEM_NUM_VALUE_DETAILS_LINEAR_UTF8_MAX) { + os_log(LOG_ERR, "entity(%p) control descriptor(%d) control_value_type(%u): number_of_values(%u) exceeding max (%u)\n", + entity, i, control_value_type, ntohs(control_desc->number_of_values), AEM_NUM_VALUE_DETAILS_LINEAR_UTF8_MAX); + goto err; + } + break; + + default: + os_log(LOG_ERR, "entity(%p) control descriptor(%d) unsupported control_value_type(%u)\n", entity, i, control_value_type); + goto err; + } + } + + return true; + +err: + return false; +} + +__init static int avdecc_dynamic_states_init(struct entity *entity, struct avdecc_entity_config *cfg) +{ + struct entity_dynamic_desc *entity_dynamic; + struct avb_interface_dynamic_desc *avb_itf_dynamic; + unsigned int num_interfaces; + int i; + struct avdecc_ctx *avdecc = entity->avdecc; + + entity_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_ENTITY, 0, NULL); + if (!entity_dynamic) + goto err_entity; + + entity_dynamic->lock_status = UNLOCKED; + entity_dynamic->acquire_status = RELEASED; + + entity_dynamic->lock_timer.func = avdecc_entity_lock_timeout; + entity_dynamic->lock_timer.data = entity; + + if (timer_create(avdecc->timer_ctx, &entity_dynamic->lock_timer, 0, AVDECC_CFG_ENTITY_LOCK_TIMER_GRANULARITY_MS) < 0) { + os_log(LOG_ERR, "entity(%p) timer_create failed\n", entity); + goto err_timer; + } + + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + for (i = 0; i < num_interfaces; i++) { + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, i, NULL); + if (!avb_itf_dynamic) + goto err_interfaces; + + avb_itf_dynamic->link_down = 0; + avb_itf_dynamic->link_up = 0; + avb_itf_dynamic->operational_state = false; + avb_itf_dynamic->as_capable = false; + + avb_itf_dynamic->gptp_gm_changed = 0; + avb_itf_dynamic->gptp_grandmaster_id = 0; + + avb_itf_dynamic->propagation_delay = 0; + + avb_itf_dynamic->max_ptlv_entries = cfg->max_ptlv_entries; + avb_itf_dynamic->num_ptlv_entries = 0; + } + + return 0; + +err_interfaces: + timer_destroy(&entity_dynamic->lock_timer); + +err_timer: +err_entity: + return -1; +} + +__init static int avdecc_entity_mc_init(struct entity *entity) +{ + unsigned char mc_addr[6] = MC_ADDR_AVDECC_ADP_ACMP; + struct avdecc_ctx *avdecc = entity->avdecc; + int num_interfaces, i, j; + + /* dynamic settings */ + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + for (i = 0; i < num_interfaces; i++) { + + if (net_add_multi(&avdecc->port[i].net_rx, avdecc_port_to_logical(entity->avdecc, i), mc_addr) < 0) { + os_log(LOG_ERR, "avdecc(%p) port(%u) entity(%p) cannot add multicast address\n", avdecc, i, entity); + goto err; + } + } + return 0; + +err: + for (j = 0; j < i; j++) + net_del_multi(&avdecc->port[j].net_rx, avdecc_port_to_logical(entity->avdecc, i), mc_addr); + + return -1; +} + +__exit static void avdecc_entity_mc_exit(struct entity *entity) +{ + unsigned char mc_addr[6] = MC_ADDR_AVDECC_ADP_ACMP; + struct avdecc_ctx *avdecc = entity->avdecc; + int num_interfaces, i; + + /* dynamic settings */ + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + for (i = 0; i < num_interfaces; i++) + net_del_multi(&avdecc->port[i].net_rx, avdecc_port_to_logical(entity->avdecc, i), mc_addr); +} + +__exit static void avdecc_dynamic_states_exit(struct entity *entity) +{ + struct entity_dynamic_desc *entity_dynamic; + + entity_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, + AEM_DESC_TYPE_ENTITY, 0, NULL); + + timer_destroy(&entity_dynamic->lock_timer); + +} + +__init static struct entity *avdecc_entity_alloc(struct avdecc_entity_config *cfg) +{ + struct entity *entity; + unsigned int size; + + size = sizeof(struct entity); + size += avdecc_inflight_data_size(cfg); + size += aecp_data_size(cfg); + size += acmp_data_size(cfg); + + entity = os_malloc(size); + if (!entity) + goto err; + + os_memset(entity, 0, size); + + return entity; + +err: + return NULL; +} + +/* Common AVDECC code entry points */ +__init static struct entity *avdecc_entity_init(struct avdecc_ctx *avdecc, int entity_num, struct avdecc_entity_config *cfg) +{ + struct entity *entity; + u8 *avdecc_data, *acmp_data, *aecp_data; + + /* Pass the milan mode to the entity cfg. */ + cfg->milan_mode = avdecc->milan_mode; + + entity = avdecc_entity_alloc(cfg); + if (!entity) + goto err_malloc; + + entity->flags = cfg->flags; + entity->channel_waitmask = cfg->channel_waitmask; + entity->valid_time = cfg->valid_time; + + entity->milan_mode = avdecc->milan_mode; + entity->avdecc = avdecc; + + if (cfg->aem) + entity->aem_descs = cfg->aem; + else + entity->aem_descs = aem_entity_static_init(); + + aem_init(entity->aem_descs, cfg, entity_num); + + entity->aem_dynamic_descs = aem_dynamic_descs_init(entity->aem_descs, cfg); + if (!entity->aem_dynamic_descs) { + os_log(LOG_CRIT, "Cannot init dynamic descriptors for entity (%d)\n", entity_num); + goto err_dynamic_desc_init; + } + + entity->desc = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_ENTITY, 0, NULL); + if (!entity->desc) { + os_log(LOG_CRIT, "Cannot find entity (%d)\n", entity_num); + goto err_entity_desc; + } + + entity->index = entity_num; + entity->channel_openmask = 0; + + if (!avdecc_entity_check(entity)) + goto err_entity_check; + + if (avdecc_dynamic_states_init(entity, cfg) < 0) + goto err_dynamic_states_init; + + avdecc_data = (u8 *)(entity + 1); + if (avdecc_inflight_init(entity, avdecc_data, cfg) < 0) + goto err_inflight; + + if (adp_init(&entity->adp) < 0) + goto err_adp; + + aecp_data = avdecc_data + avdecc_inflight_data_size(cfg); + if (aecp_init(&entity->aecp, aecp_data, cfg) < 0) + goto err_aecp; + + acmp_data = (acmp_data_size(cfg)) ? (aecp_data + aecp_data_size(cfg)) : NULL; + if (acmp_init(&entity->acmp, acmp_data, cfg) < 0) + goto err_acmp; + + if (avdecc_entity_mc_init(entity) < 0) + goto err_multi; + + os_log(LOG_INIT, "entity(%d) (%p) desc(%p), id : %016"PRIx64", name : %s\n", + entity_num, entity, entity->desc, ntohll(entity->desc->entity_id), entity->desc->entity_name); + + return entity; + +err_multi: + acmp_exit(&entity->acmp); +err_acmp: + aecp_exit(&entity->aecp); +err_aecp: + adp_exit(&entity->adp); +err_adp: + avdecc_inflight_exit(entity); +err_inflight: + avdecc_dynamic_states_exit(entity); +err_dynamic_states_init: +err_entity_check: +err_entity_desc: +err_dynamic_desc_init: + os_free(entity); +err_malloc: + return NULL; +} + +__exit static void avdecc_entity_exit(struct entity *entity) +{ + avdecc_dynamic_states_exit(entity); + avdecc_entity_mc_exit(entity); + acmp_exit(&entity->acmp); + aecp_exit(&entity->aecp); + adp_exit(&entity->adp); + avdecc_inflight_exit(entity); + os_free(entity); +} + +/** Main AVDECC network receive function. + * Receives all AVDECC frames and routes them to the different sub-stack + * components. + * \return none + * \param rx pointer to network receive context + * \param desc pointer to network receive descriptor + */ +void avdecc_net_rx(struct net_rx *rx, struct net_rx_desc *desc) +{ + struct eth_hdr *eth = (struct eth_hdr *)((char *)desc + desc->l2_offset); + struct avtp_ctrl_hdr *avtp = (struct avtp_ctrl_hdr *)((char *)desc + desc->l3_offset); + struct avdecc_port *port = container_of(rx, struct avdecc_port, net_rx); + + switch (avtp->subtype) { + case AVTP_SUBTYPE_ADP: + adp_net_rx(port, (struct adp_pdu *)(avtp + 1), avtp->control_data, AVTP_GET_STATUS(avtp), eth->src); + break; + case AVTP_SUBTYPE_AECP: + aecp_net_rx(port, (struct aecp_pdu *)(avtp + 1), avtp->control_data, AVTP_GET_STATUS(avtp), AVTP_GET_CTRL_DATA_LEN(avtp) + 8, eth->src); /* Add 8 to account for the entity id field */ + break; + case AVTP_SUBTYPE_ACMP: + acmp_net_rx(port, (struct acmp_pdu *)(avtp + 1), avtp->control_data, AVTP_GET_STATUS(avtp)); + break; + default : + os_log(LOG_ERR, "unknown subtype %x\n", avtp->subtype); + break; + } + + net_rx_free(desc); +} + + +static void avdecc_local_entity_updated(struct avdecc_ctx *avdecc, struct entity *entity) +{ + struct adp_discovery_ctx *disc; + unsigned int num_interfaces; + int i, port; + + if (entity_ready(entity)) { + adp_update(&entity->adp); + + if (!avdecc->milan_mode) { + + num_interfaces = aem_get_descriptor_max(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE); + + for (port = 0; port < num_interfaces; port++) { + disc = &avdecc->port[port].discovery; + + for (i = 0; i < disc->max_entities_discovery; i++) { + if (disc->entities[i].in_use) + avdecc_ieee_try_fast_connect(entity, &disc->entities[i], port); + } + } + } else if (aem_get_talker_streams(entity->aem_descs)) { + acmp_milan_talkers_maap_start(entity); + } + } +} + +/** AVDECC IPC receive callback. + * IPC received from MAAP + * \return none + * \param rx pointer to IPC receive context + * \param desc pointer to IPC descriptor + */ +void avdecc_ipc_rx_maap(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct entity *entity; + struct avdecc_ctx *avdecc = container_of(rx, struct avdecc_ctx, ipc_rx_maap); + avb_u16 status, port_id; + avb_u8 *base_address; + avb_u16 count; + avb_u32 range_id; + + os_log(LOG_DEBUG, "Received MAAP IPC type: %d\n", desc->type); + + switch (desc->type) { + case GENAVB_MSG_MAAP_STATUS: + if (desc->len != sizeof(struct genavb_maap_status)) { + os_log(LOG_ERR, "MAAP status message failed\n"); + break; + } + + status = desc->u.maap_status.status; + count = desc->u.maap_status.count; + base_address = desc->u.maap_status.base_address; + port_id = desc->u.maap_status.port_id; + range_id = desc->u.maap_status.range_id; + + os_log(LOG_INFO, "avdecc(%p) port(%u) range(%u) status(%u) base_address (%02x:%02x:%02x:%02x:%02x:%02x) count (%u)\n", + avdecc, port_id, range_id, status, base_address[0], base_address[1], base_address[2], base_address[3], base_address[4], + base_address[5], count); + + if (avdecc->milan_mode) { + entity = avdecc_get_local_talker(avdecc, port_id); + if (!entity) { + os_log(LOG_DEBUG, "avdecc(%p) port(%u) Couldn't find any local talker entity supporting this port.\n", + avdecc, port_id); + goto exit; + } + + if (status == MAAP_STATUS_SUCCESS) + acmp_milan_talker_maap_valid(entity, port_id, range_id, base_address, count); + else if (status == MAAP_STATUS_CONFLICT) + acmp_milan_talker_maap_conflict(entity, port_id, range_id, base_address, count); + } + + break; + + case GENAVB_MSG_MAAP_CREATE_RANGE_RESPONSE: + status = desc->u.maap_create_response.status; + range_id = desc->u.maap_create_response.range_id; + port_id = desc->u.maap_create_response.port_id; + + if (status != MAAP_RESPONSE_SUCCESS) { + os_log(LOG_ERR, "avdecc(%p) port(%u) range(%u) error on range creation\n", avdecc, port_id, range_id); + goto exit; + } + + os_log(LOG_DEBUG, "avdecc(%p) port(%u) range(%u) range creation success\n", avdecc, port_id, range_id); + + break; + + case GENAVB_MSG_MAAP_DELETE_RANGE_RESPONSE: + status = desc->u.maap_delete_response.status; + range_id = desc->u.maap_delete_response.range_id; + port_id = desc->u.maap_delete_response.port_id; + + if (status != MAAP_RESPONSE_SUCCESS) { + os_log(LOG_ERR, "avdecc(%p) port(%u) range(%u) error on range deletion\n", avdecc, port_id, range_id); + goto exit; + } + + os_log(LOG_DEBUG, "avdecc(%p) port(%u) range(%u) range deletion success\n", avdecc, port_id, range_id); + + break; + + default: + break; + } + +exit: + ipc_free(rx, desc); +} + +/** AVDECC IPC receive callback. + * IPC received from gPTP application. + * \return none + * \param rx pointer to IPC receive context + * \param desc pointer to IPC descriptor + */ +void avdecc_ipc_rx_gptp(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct avdecc_port *port = container_of(rx, struct avdecc_port, ipc_rx_gptp); + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + struct avb_interface_dynamic_desc *avb_itf_dynamic; + struct avb_interface_descriptor *avb_itf; + int i; + + os_log(LOG_DEBUG, "Received IPC type: %d\n", desc->type); + + switch (desc->type) { + case GENAVB_MSG_GM_STATUS: + if ((desc->len < (sizeof(struct genavb_msg_gm_status))) || (desc->len != (sizeof(struct genavb_msg_gm_status) + desc->u.gm_status.num_ptlv * sizeof(struct ptp_clock_identity)))) { + os_log(LOG_ERR, "avdecc(%p) grandmaster status message failed", avdecc); + break; + } + + /* We can receive indication from multiple domains, filter on the supported domain. + * FIXME: make the supported domain configurable + */ + if (desc->u.gm_status.domain != CFG_DEFAULT_GPTP_DOMAIN) + break; + + for (i = 0; i < avdecc->num_entities; i++) { + struct entity *entity = avdecc->entities[i]; + bool path_sequence_changed = false; + + avb_itf = aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE, port->port_id, NULL); + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port->port_id, NULL); + + if (!avb_itf || !avb_itf_dynamic) /* Interface index not supported in this entity */ + continue; + + /* update the domain number */ + avb_itf->domain_number = desc->u.gm_status.domain; + + if (avb_itf_dynamic->num_ptlv_entries != desc->u.gm_status.num_ptlv) + path_sequence_changed = true; + + /* update the path sequence to the grandmaster as per Milan Dicovery Connection Control 6.6.1 */ + if (desc->u.gm_status.num_ptlv <= avb_itf_dynamic->max_ptlv_entries) { + avb_itf_dynamic->num_ptlv_entries = desc->u.gm_status.num_ptlv; + } else { + /* Truncate the path trace to the max supported size. */ + avb_itf_dynamic->num_ptlv_entries = avb_itf_dynamic->max_ptlv_entries; + } + + if (path_sequence_changed || os_memcmp(&avb_itf_dynamic->path_sequence, &desc->u.gm_status.path_sequence, avb_itf_dynamic->num_ptlv_entries * sizeof(struct ptp_clock_identity))) { + os_memcpy(&avb_itf_dynamic->path_sequence, &desc->u.gm_status.path_sequence, avb_itf_dynamic->num_ptlv_entries * sizeof(struct ptp_clock_identity)); + + path_sequence_changed = true; + } + + if (!cmp_64(&avb_itf_dynamic->gptp_grandmaster_id, &desc->u.gm_status.gm_id)) { + os_log(LOG_INFO, "GTP GM id change for local entity(%p) old 0x%"PRIx64" new 0x%"PRIx64"\n", + entity, ntohll(avb_itf_dynamic->gptp_grandmaster_id), ntohll(desc->u.gm_status.gm_id)); + + copy_64(&avb_itf_dynamic->gptp_grandmaster_id, &desc->u.gm_status.gm_id); + + avb_itf_dynamic->gptp_gm_changed++; + + avdecc_local_entity_updated(avdecc, entity); + + if (entity_ready(entity)) { + if (avdecc->milan_mode) { + adp_milan_advertise_sm(entity, port->port_id, ADP_MILAN_ADV_GM_CHANGE); + + aecp_milan_register_get_counters_async_notification(entity, port->port_id); + } else + adp_ieee_advertise_interface_sm(entity, port->port_id, ADP_INTERFACE_ADV_EVENT_GM_CHANGE); + } + } + + if (path_sequence_changed && entity_ready(entity)) { + /* Register send of an asynchronous GET_AS_PATH unsolicited notification per MILAN Specification v1.2 5.4.5.2 + * to notify changes in the path sequence from gPTP + */ + aecp_trigger_async_get_as_path_notification(entity, avb_itf_dynamic->interface_index); + } + } + + break; + + case GENAVB_MSG_GPTP_PORT_PARAMS: + if (desc->len != sizeof(struct genavb_msg_gptp_port_params)) { + os_log(LOG_ERR, "avdecc(%p) gptp port params message failed", avdecc); + break; + } + + /* Updates only on the concerned port and filter on the supported domain + * FIXME: make the supported domain configurable + */ + if (desc->u.gptp_port_params.port_id != port->logical_port || desc->u.gptp_port_params.domain != CFG_DEFAULT_GPTP_DOMAIN) + break; + + for (i = 0; i < avdecc->num_entities; i++) { + struct entity *entity = avdecc->entities[i]; + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port->port_id, NULL); + + if (!avb_itf_dynamic) /* Interface index not supported in this entity */ + continue; + + avb_itf_dynamic->as_capable = desc->u.gptp_port_params.as_capable; + avb_itf_dynamic->propagation_delay = desc->u.gptp_port_params.pdelay; + } + + break; + + case GENAVB_MSG_MANAGED_GET_RESPONSE: + if (desc->len > GENAVB_MAX_MANAGED_SIZE) { + os_log(LOG_ERR, "avdecc(%p) gptp managed get response message failed", avdecc); + break; + } + + for (i = 0; i < avdecc->num_entities; i++) { + struct entity *entity = avdecc->entities[i]; + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port->port_id, NULL); + + if (!avb_itf_dynamic) /* Interface index not supported in this entity */ + continue; + + avdecc_ipc_managed_get_response(entity, port->port_id, &desc->u.managed_get_response); + } + + break; + + default: + break; + } + + ipc_free(rx, desc); +} + +static void avdecc_ipc_gm_get_status(struct avdecc_port *port) +{ + struct ipc_desc *desc; + int rc; + + desc = ipc_alloc(&port->ipc_tx_gptp, sizeof(struct genavb_msg_gm_status)); + if (desc) { + desc->type = GENAVB_MSG_GM_GET_STATUS; + desc->len = sizeof(struct genavb_msg_gm_status); + desc->flags = 0; + + desc->u.gm_get_status.domain = CFG_DEFAULT_GPTP_DOMAIN; + + rc = ipc_tx(&port->ipc_tx_gptp, desc); + if (rc < 0) { + os_log(LOG_ERR, "ipc_tx() failed (%d)\n", rc); + ipc_free(&port->ipc_tx_gptp, desc); + } + } else + os_log(LOG_ERR, "ipc_alloc() failed\n"); +} + +static void avdecc_ipc_srp_deregister_all(struct avdecc_port *port) +{ + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + struct ipc_desc *desc; + int rc; + + os_log(LOG_INFO, "avdecc(%p) port(%u) Deregister all SRP stream declarations on port.\n", avdecc, port->port_id); + + desc = ipc_alloc(&port->ipc_tx_srp, 0); + if (desc) { + desc->type = GENAVB_MSG_DEREGISTER_ALL; + desc->len = 0; + desc->flags = 0; + + rc = ipc_tx(&port->ipc_tx_srp, desc); + if (rc < 0) { + os_log(LOG_ERR, "ipc_tx() failed (%d)\n", rc); + ipc_free(&port->ipc_tx_srp, desc); + } + } else + os_log(LOG_ERR, "ipc_alloc() failed\n"); +} + +/** Find a local entity (ready or not) with Controller capability. + * \return pointer to first found local controller entity (if any), NULL otherwise. + */ +struct entity *avdecc_get_local_controller_any(struct avdecc_ctx *avdecc) +{ + unsigned int i = 0; + + while (i < avdecc->num_entities) { + if (avdecc->entities[i]->desc->controller_capabilities & htonl(ADP_CONTROLLER_IMPLEMENTED)) + return avdecc->entities[i]; + i++; + } + + return NULL; +} + +/** Find a local entity that is ready, with Controller capability and has the port valid. + * \param avdecc pointer to avdecc context + * \param port_id avdecc port id/interface index + * \return pointer to first found local controller entity (if any), NULL otherwise. + */ +struct entity *avdecc_get_local_controller(struct avdecc_ctx *avdecc, unsigned int port_id) +{ + unsigned int i = 0; + + while (i < avdecc->num_entities) { + if ((avdecc->entities[i]->desc->controller_capabilities & htonl(ADP_CONTROLLER_IMPLEMENTED)) + && entity_ready(avdecc->entities[i]) && avdecc_entity_port_valid(avdecc->entities[i], port_id)) + return avdecc->entities[i]; + i++; + } + + return NULL; +} + +/** Find a local entity (ready or not) with Talker or Listener capability. + * \return pointer to first matching local entity (if any), NULL if none was foudn. + */ +struct entity *avdecc_get_local_controlled_any(struct avdecc_ctx *avdecc) +{ + unsigned int i = 0; + + while (i < avdecc->num_entities) { + if ((avdecc->entities[i]->desc->listener_capabilities & htons(ADP_LISTENER_IMPLEMENTED)) || + (avdecc->entities[i]->desc->talker_capabilities & htons(ADP_TALKER_IMPLEMENTED))) + return avdecc->entities[i]; + i++; + } + + return NULL; +} + +/** Find a local entity that is ready, with Listener capability and has the port valid. + * \param avdecc pointer to avdecc context + * \param port_id avdecc port id/interface index + * \return pointer to first found local listener entity (if any), NULL otherwise. + */ +struct entity *avdecc_get_local_listener(struct avdecc_ctx *avdecc, unsigned int port_id) +{ + unsigned int i = 0; + + while (i < avdecc->num_entities) { + if ((avdecc->entities[i]->desc->listener_capabilities & htons(ADP_LISTENER_IMPLEMENTED)) + && entity_ready(avdecc->entities[i]) && avdecc_entity_port_valid(avdecc->entities[i], port_id)) + return avdecc->entities[i]; + i++; + } + + return NULL; +} + +/** Find a local entity (ready or not) and with Listener capability and has the port valid. + * \param avdecc pointer to avdecc context + * \param port_id avdecc port id/interface index + * \return pointer to first found local listener entity (if any), NULL otherwise. + */ +struct entity *avdecc_get_local_listener_any(struct avdecc_ctx *avdecc, unsigned int port_id) +{ + unsigned int i = 0; + + while (i < avdecc->num_entities) { + if ((avdecc->entities[i]->desc->listener_capabilities & htons(ADP_LISTENER_IMPLEMENTED)) + && avdecc_entity_port_valid(avdecc->entities[i], port_id)) + return avdecc->entities[i]; + i++; + } + + return NULL; +} + +/** Find a local entity that is ready, with Talker capability and has the port valid. + * \param avdecc pointer to avdecc context + * \param port_id avdecc port id/interface index + * \return pointer to first found local talker entity (if any), NULL otherwise. + */ +struct entity *avdecc_get_local_talker(struct avdecc_ctx *avdecc, unsigned int port_id) +{ + unsigned int i = 0; + + while (i < avdecc->num_entities) { + if ((avdecc->entities[i]->desc->talker_capabilities & htons(ADP_TALKER_IMPLEMENTED)) + && entity_ready(avdecc->entities[i]) && avdecc_entity_port_valid(avdecc->entities[i], port_id)) + return avdecc->entities[i]; + i++; + } + + return NULL; +} + +/** Send an IPC HEARTBEAT message on the provided tx IPC. + * \ return 0 (or positive) on success, -1 otherwise. + */ +static int avdecc_ipc_send_heartbeat(struct ipc_tx *tx, unsigned int ipc_dst) +{ + struct ipc_desc *tx_desc; + int rc = 0; + + os_log(LOG_DEBUG, "ipc_tx(%p) Sending IPC heartbeat\n", tx); + tx_desc = ipc_alloc(tx, sizeof(struct ipc_heartbeat)); + if (tx_desc) { + tx_desc->dst = ipc_dst; + tx_desc->type = IPC_HEARTBEAT; + tx_desc->len = sizeof(struct ipc_heartbeat); + tx_desc->u.hearbeat.status = 0; + + + rc = ipc_tx(tx, tx_desc); + if (rc < 0) { + if (rc == -IPC_TX_ERR_QUEUE_FULL) + os_log(LOG_ERR, "ict_tx(%p) ipc_tx() failed (%d)\n", tx, rc); + ipc_free(tx, tx_desc); + rc = -1; + } + } else { + os_log(LOG_ERR, "ipc_tx(%p) ipc_alloc() failed\n", tx); + rc = -1; + } + + return rc; +} + +/** AVDECC IPC receive callback. + * IPC received from the controller API. + * \return none + * \param rx pointer to IPC receive context + * \param desc pointer to IPC descriptor + */ +void avdecc_ipc_rx_controller(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct avdecc_ctx *avdecc = container_of(rx, struct avdecc_ctx, ipc_rx_controller); + struct entity *entity; + struct ipc_tx *ipc = &avdecc->ipc_tx_controller; + int rc = -1; + + os_log(LOG_DEBUG, "Received IPC type: %d\n", desc->type); + + /* Assume at most one controller entity per endpoint. + * FIXME to be replaced by explicit entity selection by app + * if/when multiple controller entities per endpoint is implemented. + */ + entity = avdecc_get_local_controller_any(avdecc); + if (!entity) { + os_log(LOG_ERR, "avdecc(%p) Couldn't find any local controller entities, ignoring message from application.\n", avdecc); + goto exit; + } + + if (desc->flags & IPC_FLAGS_AVB_MSG_SYNC) + ipc = &avdecc->ipc_tx_controller_sync; + + switch (desc->type) { + case GENAVB_MSG_ACMP_COMMAND: + if (entity_ready(entity)) + rc = acmp_ipc_rx(entity, &desc->u.acmp_command, desc->len, ipc, desc->src); + break; + case GENAVB_MSG_AECP: + if (entity_ready(entity)) + rc = aecp_ipc_rx_controller(entity, &desc->u.aecp_msg, desc->len, ipc, desc->src); + break; + case GENAVB_MSG_ADP: + if (entity_ready(entity)) + rc = adp_ipc_rx(entity, &desc->u.adp_msg, desc->len, ipc, desc->src); + break; + + case IPC_HEARTBEAT: + os_log(LOG_DEBUG, "Sending HEARTBEAT on ipc_tx(%p)\n", ipc); + avdecc_ipc_send_heartbeat(ipc, desc->src); + + if (!(entity->channel_openmask & AVDECC_WAITMASK_CONTROLLER)) { + entity->channel_openmask |= AVDECC_WAITMASK_CONTROLLER; + + avdecc_local_entity_updated(avdecc, entity); + } + break; + + default: + os_log(LOG_ERR, "Received unknown message type %d on a CONTROLLER channel\n", desc->type); + break; + } + +exit: + if (rc < 0) { + // FIXME: If we were unable to send a command that expected a response, send an error response back to the application. + } + + ipc_free(rx, desc); +} + + +void avdecc_ipc_rx_controlled(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct avdecc_ctx *avdecc = container_of(rx, struct avdecc_ctx, ipc_rx_controlled); + struct entity *entity; + + os_log(LOG_DEBUG, "avdecc(%p) Received IPC type: %d\n", avdecc, desc->type); + + /* Assume at most one non-controller entity per endpoint. + * FIXME to be replaced by explicit entity selection by app + * if/when multiple (non-controller) entities per endpoint is implemented. + */ + entity = avdecc_get_local_controlled_any(avdecc); + if (!entity) { + os_log(LOG_ERR, "avdecc(%p) Couldn't find any local non-controller entities, ignoring message from application.\n", avdecc); + goto exit; + } + + switch (desc->type) { + case GENAVB_MSG_AECP: + if (entity_ready(entity)) + aecp_ipc_rx_controlled(entity, &desc->u.aecp_msg, desc->len); + // TODO else ??? + break; + + case IPC_HEARTBEAT: + avdecc_ipc_send_heartbeat(&avdecc->ipc_tx_controlled, IPC_DST_ALL); + + if (!(entity->channel_openmask & AVDECC_WAITMASK_CONTROLLED)) { + entity->channel_openmask |= AVDECC_WAITMASK_CONTROLLED; + + avdecc_local_entity_updated(avdecc, entity); + } + break; + + default: + os_log(LOG_ERR, "Received unknown message type %d on a CONTROLLED channel\n", desc->type); + break; + } + +exit: + ipc_free(rx, desc); +} + + +void avdecc_ipc_rx_media_stack(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct avdecc_ctx *avdecc = container_of(rx, struct avdecc_ctx, ipc_rx_media_stack); + struct entity *entity; + + os_log(LOG_DEBUG, "avdecc(%p) Received IPC type: %d\n", avdecc, desc->type); + + switch (desc->type) { + case IPC_HEARTBEAT: + /* Assume at most one non-controller entity per endpoint. + * FIXME to be replaced by explicit entity selection by app + * if/when multiple (non-controller) entities per endpoint is implemented. + */ + entity = avdecc_get_local_controlled_any(avdecc); + if (!entity) { + os_log(LOG_ERR, "avdecc(%p) Couldn't find any local non-controller entities, ignoring message from application.\n", avdecc); + goto exit; + } + + avdecc_ipc_send_heartbeat(&avdecc->ipc_tx_media_stack, IPC_DST_ALL); + + if (!(entity->channel_openmask & AVDECC_WAITMASK_MEDIA_STACK)) { + entity->channel_openmask |= AVDECC_WAITMASK_MEDIA_STACK; + + avdecc_local_entity_updated(avdecc, entity); + } + break; + + case GENAVB_MSG_MEDIA_STACK_BIND: + if (desc->len != sizeof(struct ipc_media_stack_bind)) { + os_log(LOG_ERR, "avdecc(%p) wrong length received %u expected %zu\n", avdecc, desc->len, sizeof(struct ipc_media_stack_bind)); + goto exit; + } + + if (!avdecc->milan_mode) { + os_log(LOG_ERR, "avdecc(%p) GENAVB_MSG_MEDIA_STACK_BIND supported only in Milan mode\n", avdecc); + goto exit; + } + + os_log(LOG_INFO, "avdecc(%p): received GENAVB_MSG_MEDIA_STACK_BIND: Controller (%016"PRIx64") bound listener stream (%016"PRIx64", %u, %s) to talker stream (%016"PRIx64", %u) \n", + avdecc, desc->u.media_stack_bind.controller_entity_id, + desc->u.media_stack_bind.entity_id, desc->u.media_stack_bind.listener_stream_index, (desc->u.media_stack_bind.started == ACMP_LISTENER_STREAM_STARTED) ? "STARTED" : "STOPPED", + desc->u.media_stack_bind.talker_entity_id, desc->u.media_stack_bind.talker_stream_index); + + if ((entity = avdecc_get_entity(avdecc, htonll(desc->u.media_stack_bind.entity_id))) != NULL) + acmp_milan_listener_sink_rcv_binding_params(entity, &desc->u.media_stack_bind); + + break; + + default: + os_log(LOG_ERR, "Received unknown message type %d on a MEDIA_STACK channel\n", desc->type); + break; + } + +exit: + ipc_free(rx, desc); +} + +void avdecc_ipc_rx_srp(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct avdecc_port *port = container_of(rx, struct avdecc_port, ipc_rx_srp); + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + struct ipc_msrp_listener_status *listener_status; + struct ipc_msrp_talker_status *talker_status; + struct ipc_msrp_listener_declaration_status *listener_declaration_status; + struct ipc_msrp_talker_declaration_status *talker_declaration_status; + u64 stream_id; + struct entity *entity; + + os_log(LOG_DEBUG, "avdecc(%p) port(%u) received IPC type: %d\n", avdecc, port->port_id, desc->type); + + switch (desc->type) { + case GENAVB_MSG_LISTENER_STATUS: + + listener_status = &desc->u.msrp_listener_status; + stream_id = get_64(listener_status->stream_id); + + os_log(LOG_INFO, "avdecc(%p) port(%u) Received listener status %s for stream_id(%016"PRIx64")\n", + avdecc, port->port_id, listener_stream_status2string(listener_status->status), ntohll(stream_id)); + + if (avdecc->milan_mode) { + u16 listener_unique_id; + + entity = avdecc_get_local_listener(avdecc, port->port_id); + if (!entity) { + os_log(LOG_DEBUG, "avdecc(%p) port(%u) Couldn't find any local listener entity supporting this port.\n", + avdecc, port->port_id); + goto exit; + } + + if (!acmp_milan_get_listener_unique_id(entity, stream_id, &listener_unique_id)) + acmp_milan_listener_srp_state_sm(entity, listener_unique_id, listener_status); + } + + break; + + case GENAVB_MSG_TALKER_STATUS: + talker_status = &desc->u.msrp_talker_status; + stream_id = get_64(talker_status->stream_id); + + os_log(LOG_INFO, "avdecc(%p) port(%u) Received talker status %s for stream_id(%016"PRIx64")\n", + avdecc, port->port_id, talker_stream_status2string(talker_status->status), ntohll(stream_id)); + + if (avdecc->milan_mode) { + u16 talker_unique_id; + + entity = avdecc_get_local_talker(avdecc, port->port_id); + if (!entity) { + os_log(LOG_DEBUG, "avdecc(%p) port(%u) Couldn't find any local talker entity supporting this port.\n", + avdecc, port->port_id); + goto exit; + } + + if (!acmp_milan_get_talker_unique_id(entity, stream_id, &talker_unique_id)) + acmp_milan_talker_update_status(entity, talker_unique_id, talker_status); + } + break; + + case GENAVB_MSG_TALKER_DECLARATION_STATUS: + talker_declaration_status = &desc->u.msrp_talker_declaration_status; + stream_id = get_64(talker_declaration_status->stream_id); + + os_log(LOG_INFO, "avdecc(%p) port(%u) talker declaration %s for stream_id(%016"PRIx64")\n", + avdecc, port->port_id, talker_stream_declaration_type2string(talker_declaration_status->declaration_type), ntohll(stream_id)); + + if (avdecc->milan_mode) { + u16 talker_unique_id; + + entity = avdecc_get_local_talker(avdecc, port->port_id); + if (!entity) { + os_log(LOG_DEBUG, "avdecc(%p) port(%u) Couldn't find any local talker entity supporting this port.\n", + avdecc, port->port_id); + goto exit; + } + + if (!acmp_milan_get_talker_unique_id(entity, stream_id, &talker_unique_id)) + acmp_milan_talker_update_declaration(entity, talker_unique_id, talker_declaration_status); + } + break; + + case GENAVB_MSG_LISTENER_DECLARATION_STATUS: + listener_declaration_status = &desc->u.msrp_listener_declaration_status; + stream_id = get_64(listener_declaration_status->stream_id); + + os_log(LOG_INFO, "avdecc(%p) port(%u) listener declaration %s for stream_id(%016"PRIx64")\n", + avdecc, port->port_id, listener_stream_declaration_type2string(listener_declaration_status->declaration_type), ntohll(stream_id)); + + break; + + case GENAVB_MSG_TALKER_RESPONSE: + case GENAVB_MSG_LISTENER_RESPONSE: + break; + + default: + os_log(LOG_ERR, "avdecc(%p) port(%u) received unknown message type %d on MSRP channel\n", avdecc, port->port_id, desc->type); + break; + } + +exit: + ipc_free(rx, desc); +} + +static void avdecc_ipc_get_mac_status(struct avdecc_ctx *avdecc, struct ipc_tx *ipc, unsigned int port_id) +{ + struct ipc_desc *desc; + struct ipc_mac_service_get_status *get_status; + int rc; + + desc = ipc_alloc(ipc, sizeof(struct ipc_mac_service_get_status)); + if (desc) { + desc->type = IPC_MAC_SERVICE_GET_STATUS; + desc->len = sizeof(struct ipc_mac_service_get_status); + desc->flags = 0; + + get_status = &desc->u.mac_service_get_status; + + get_status->port_id = port_id; + + rc = ipc_tx(ipc, desc); + if (rc < 0) { + os_log(LOG_ERR, "ipc_tx() failed (%d)\n", rc); + ipc_free(ipc, desc); + } + } else + os_log(LOG_ERR, "ipc_alloc() failed\n"); +} + +static void avdecc_ipc_rx_mac_service(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct avdecc_port *port = container_of(rx, struct avdecc_port, ipc_rx_mac_service); + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + struct ipc_mac_service_status *status; + struct avb_interface_dynamic_desc *avb_itf_dynamic; + int i; + + switch (desc->type) { + case IPC_MAC_SERVICE_STATUS: + status = &desc->u.mac_service_status; + + os_log(LOG_DEBUG, "IPC_MAC_SERVICE_STATUS: logical_port(%u) operational(%u): received on avdecc port (%u)\n", + status->port_id, status->operational, port->port_id); + + for (i = 0; i < avdecc->num_entities; i++) { + struct entity *entity = avdecc->entities[i]; + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port->port_id, NULL); + if (!avb_itf_dynamic) /* Interface index not supported in this entity */ + continue; + + if (!avb_itf_dynamic->operational_state && status->operational) { + avb_itf_dynamic->link_up++; + + if (entity_ready(entity)) { + if (avdecc->milan_mode) { + adp_milan_advertise_sm(entity, port->port_id, ADP_MILAN_ADV_LINK_UP); + + aecp_milan_register_get_counters_async_notification(entity, port->port_id); + } else + adp_ieee_advertise_interface_sm(entity, port->port_id, ADP_INTERFACE_ADV_EVENT_LINK_UP); + } + + } else if (avb_itf_dynamic->operational_state && !status->operational) { + avb_itf_dynamic->link_down++; + + if (entity_ready(entity)) { + if (avdecc->milan_mode) { + adp_milan_advertise_sm(entity, port->port_id, ADP_MILAN_ADV_LINK_DOWN); + + aecp_milan_register_get_counters_async_notification(entity, port->port_id); + } else + adp_ieee_advertise_interface_sm(entity, port->port_id, ADP_INTERFACE_ADV_EVENT_LINK_DOWN); + } + } + + /* Save the current state */ + avb_itf_dynamic->operational_state = (status->operational) ? true : false; + } + + break; + + default: + break; + } + + ipc_free(rx, desc); +} + +/** Sends an AVDECC packet back to the stack. + * Takes ownership of the TX descriptor in all cases. + * \param avdecc pointer to the AVDECC context + * \param rx_desc RX descriptor containing the packet to send + * \param local_mac Pointer to local MAC address to set as source + */ +static void avdecc_net_tx_loopback(struct avdecc_port *port, struct net_rx_desc *rx_desc, u8 *local_mac) +{ + struct eth_hdr *eth = (struct eth_hdr *)NET_DATA_START(rx_desc); + + /* Make sure any response will come back locally as well. */ + os_memcpy(eth->src, local_mac, 6); + + rx_desc->l3_offset = rx_desc->l2_offset + sizeof(struct eth_hdr); + + avdecc_net_rx(&port->net_rx, rx_desc); +} + +/** Sends an AVDECC packet on the network. + * Takes ownership of the TX descriptor in all cases. + * \return 0 on success, -1 in case of failure + * \param port pointer to the AVDECC port on which the packet will be sent + * \param desc TX descriptor containing the packet to send + */ +int avdecc_net_tx(struct avdecc_port *port, struct net_tx_desc *desc) +{ + struct net_tx_desc *tx_desc; + struct eth_hdr *eth = (struct eth_hdr *)NET_DATA_START(desc); + int rc = 0; + + os_log(LOG_DEBUG, "port (%u), l2_offset %x, len %d, flags %d\n", + port->port_id, desc->l2_offset, desc->len, desc->flags); + + if (MAC_IS_MCAST(eth->dst)) { + tx_desc = net_tx_clone(&port->net_tx, desc); + if (!tx_desc) + os_log(LOG_ERR, "port(%u) cannot alloc tx descriptor\n", port->port_id); + else { + if (net_tx(&port->net_tx, tx_desc) < 0) { + os_log(LOG_ERR, "port(%u) cannot transmit packet\n", port->port_id); + net_tx_free(tx_desc); + } + } + + avdecc_net_tx_loopback(port, (struct net_rx_desc *)desc, port->local_physical_mac); + + } else { + if (os_memcmp(eth->dst, port->local_physical_mac, 6) == 0) { + avdecc_net_tx_loopback(port, (struct net_rx_desc *)desc, port->local_physical_mac); + } else { + if (net_tx(&port->net_tx, desc) < 0) { + os_log(LOG_ERR, "port(%u) cannot transmit packet\n", port->port_id); + net_tx_free(desc); + rc = -1; + } + } + } + + if (!rc) + os_log(LOG_DEBUG, "Success \n"); + + return rc; +} + +size_t avdecc_add_common_header(void *buf, u8 subtype, u8 msg_type, u16 length, u8 status) +{ + struct avtp_ctrl_hdr *avtp_ctrl = (struct avtp_ctrl_hdr *)buf; + + avtp_ctrl->subtype = subtype; + avtp_ctrl->version = 0; + avtp_ctrl->sv = 0; + avtp_ctrl->control_data = msg_type; + AVTP_SET_CTRL_DATA_STATUS(avtp_ctrl, status, length); + + return sizeof(struct avtp_ctrl_hdr); +} + +/** + * Get the avdecc port for a specific logical port number + * \return 0 on success and the interface index set in the interface_index pointer, -1 otherwise. + * \return The avdecc port with the corresponding logical port id on success, NULL otherwise + * \param avdecc pointer to avdecc context + * \param logical_port logical port number + */ +struct avdecc_port *logical_to_avdecc_port(struct avdecc_ctx *avdecc, unsigned int logical_port) +{ + int i; + + for (i = 0; i < avdecc->port_max; i++) { + if (logical_port == avdecc->port[i].logical_port) { + if (avdecc->port[i].initialized) + return &avdecc->port[i]; + else + return NULL; + } + } + + return NULL; +} + +/** + * Get the logical port for a specific avdecc port (avb interface index) + * \return logical port id + * \param avdecc pointer to avdecc context + * \param port_id avdecc port id/interface index + */ +unsigned int avdecc_port_to_logical(struct avdecc_ctx *avdecc, unsigned int port_id) +{ + return avdecc->port[port_id].logical_port; +} + +/** Find the local entity matching the provided entity ID and return it if it is ready. + * \return pointer to the entity (if found and ready), NULL otherwise. + */ +struct entity *avdecc_get_entity(struct avdecc_ctx *avdecc, u64 entity_id) +{ + int i; + + for (i = 0; i < avdecc->num_entities; i++) + if ((avdecc->entities[i]->desc->entity_id == entity_id) && entity_ready(avdecc->entities[i])) + return avdecc->entities[i]; + + return NULL; +} + +/** Checks if the provided interface index is valid for the AVDECC entity. + * \return True if the interface index is enabled, False otherwise + * \param entity Pointer to entity + * \param port_id avdecc port id/interface index + */ +bool avdecc_entity_port_valid(struct entity *entity, unsigned int port_id) +{ + if (aem_get_descriptor(entity->aem_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL)) + return true; + else + return false; +} + +__init static int avdecc_port_ipc_init(struct avdecc_port *port, unsigned long priv) +{ + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + + if (avdecc->use_gptp_bridge_stack) { + /* No gptp endpoint stack is available: use the bridge stack for GM status events. */ + if (ipc_rx_init(&port->ipc_rx_gptp, IPC_GPTP_BRIDGE_MEDIA_STACK, + avdecc_ipc_rx_gptp, priv) < 0) + goto err_ipc_rx_gptp; + + if (ipc_tx_init(&port->ipc_tx_gptp, IPC_MEDIA_STACK_GPTP_BRIDGE) < 0) + goto err_ipc_tx_gptp; + } else { + if (ipc_rx_init(&port->ipc_rx_gptp, (port->port_id == CFG_ENDPOINT_0_LOGICAL_PORT ) ? IPC_GPTP_MEDIA_STACK : IPC_GPTP_1_MEDIA_STACK, + avdecc_ipc_rx_gptp, priv) < 0) + goto err_ipc_rx_gptp; + + if (ipc_tx_init(&port->ipc_tx_gptp, (port->port_id == CFG_ENDPOINT_0_LOGICAL_PORT ) ? IPC_MEDIA_STACK_GPTP : IPC_MEDIA_STACK_GPTP_1) < 0) + goto err_ipc_tx_gptp; + } + + if (ipc_tx_connect(&port->ipc_tx_gptp, &port->ipc_rx_gptp) < 0) + goto err_ipc_tx_gptp_connect; + + if (avdecc->srp_enabled) { + if (ipc_tx_init(&port->ipc_tx_srp, (port->port_id == CFG_ENDPOINT_0_LOGICAL_PORT ) ? IPC_MEDIA_STACK_MSRP : IPC_MEDIA_STACK_MSRP_1) < 0) + goto err_ipc_tx_srp; + + if (ipc_rx_init(&port->ipc_rx_srp, (port->port_id == CFG_ENDPOINT_0_LOGICAL_PORT ) ? IPC_MSRP_MEDIA_STACK : IPC_MSRP_1_MEDIA_STACK, avdecc_ipc_rx_srp, priv) < 0) + goto err_ipc_rx_srp; + + if (ipc_tx_connect(&port->ipc_tx_srp, &port->ipc_rx_srp) < 0) + goto err_ipc_tx_srp_connect; + } + + if (avdecc->management_enabled) { + if (ipc_rx_init(&port->ipc_rx_mac_service, (port->port_id == CFG_ENDPOINT_0_LOGICAL_PORT ) ? IPC_MAC_SERVICE_MEDIA_STACK : IPC_MAC_SERVICE_1_MEDIA_STACK, + avdecc_ipc_rx_mac_service, priv) < 0) + goto err_ipc_rx_mac_service; + + if (ipc_tx_init(&port->ipc_tx_mac_service, (port->port_id == CFG_ENDPOINT_0_LOGICAL_PORT ) ? IPC_MEDIA_STACK_MAC_SERVICE : IPC_MEDIA_STACK_MAC_SERVICE_1) < 0) + goto err_ipc_tx_mac_service; + + if (ipc_tx_connect(&port->ipc_tx_mac_service, &port->ipc_rx_mac_service) < 0) + goto err_ipc_tx_mac_connect; + + avdecc_ipc_get_mac_status(avdecc, &port->ipc_tx_mac_service, port->port_id); + } + + return 0; + +err_ipc_tx_mac_connect: + if (avdecc->management_enabled) + ipc_tx_exit(&port->ipc_tx_mac_service); +err_ipc_tx_mac_service: + if (avdecc->management_enabled) + ipc_rx_exit(&port->ipc_rx_mac_service); +err_ipc_rx_mac_service: +err_ipc_tx_srp_connect: + if (avdecc->srp_enabled) + ipc_rx_exit(&port->ipc_rx_srp); + +err_ipc_rx_srp: + if (avdecc->srp_enabled) + ipc_tx_exit(&port->ipc_tx_srp); + +err_ipc_tx_srp: +err_ipc_tx_gptp_connect: + ipc_tx_exit(&port->ipc_tx_gptp); + +err_ipc_tx_gptp: + ipc_rx_exit(&port->ipc_rx_gptp); + +err_ipc_rx_gptp: + return -1; +} + + +__init static int avdecc_ports_init(struct avdecc_ctx *avdecc, struct avdecc_config *cfg, unsigned long priv) +{ + unsigned int i; + struct net_address addr; + unsigned int logical_port; + + for (i = 0; i < avdecc->port_max; i++) { + logical_port = cfg->logical_port_list[i]; + + avdecc->port[i].port_id = i; + avdecc->port[i].logical_port = logical_port; + avdecc->port[i].initialized = false; + + if (net_get_local_addr(logical_port, avdecc->port[i].local_physical_mac) < 0) { + os_log(LOG_ERR, "avdecc(%p) could not get local physical mac address on port(%u)\n", avdecc, i); + goto err_local_addr; + } + + /** + * Network Rx/Tx + */ + addr.ptype = PTYPE_AVTP; + addr.port = logical_port; + addr.priority = AVDECC_DEFAULT_PRIORITY; + addr.u.avtp.subtype = AVTP_SUBTYPE_AVDECC; + + if (net_rx_init(&avdecc->port[i].net_rx, &addr, avdecc_net_rx, priv) < 0) + goto err_net_rx_init; + + if (net_tx_init(&avdecc->port[i].net_tx, &addr) < 0) + goto err_net_tx_init; + + if (avdecc_port_ipc_init(&avdecc->port[i], priv) < 0) + goto err_ipc_init; + + avdecc->port[i].initialized = true; + + os_log(LOG_INIT, "avdecc(%p) - port(%u)(%p) / max %d done\n", + avdecc, avdecc->port[i].port_id, &avdecc->port[i], avdecc->port_max); + + continue; + + err_ipc_init: + net_tx_exit(&avdecc->port[i].net_tx); + err_net_tx_init: + net_rx_exit(&avdecc->port[i].net_rx); + + err_net_rx_init: + err_local_addr: + continue; + } + + return 0; +} + +__init static struct avdecc_ctx *avdecc_alloc(unsigned int timer_n, unsigned int n_ports, unsigned int max_entities_discovery) +{ + struct avdecc_ctx *avdecc; + unsigned int size; + + size = sizeof(struct avdecc_ctx) + n_ports * sizeof(struct avdecc_port); + size += timer_pool_size(timer_n); + size += adp_discovery_data_size(max_entities_discovery) * n_ports; + + avdecc = os_malloc(size); + if (!avdecc) + goto err; + + os_memset(avdecc, 0, size); + + avdecc->timer_ctx = (struct timer_ctx *)((u8 *)(avdecc + 1) + n_ports * sizeof(struct avdecc_port)); + avdecc->adp_discovery_data = (void *)((u8 *)avdecc->timer_ctx + timer_pool_size(timer_n)); + avdecc->port_max = n_ports; + + return avdecc; + +err: + return NULL; +} + +__exit static void avdecc_port_ipc_exit(struct avdecc_port *port) +{ + struct avdecc_ctx *avdecc = avdecc_port_to_context(port); + + if (avdecc->management_enabled) { + ipc_tx_exit(&port->ipc_tx_mac_service); + ipc_rx_exit(&port->ipc_rx_mac_service); + } + + if (avdecc->srp_enabled) { + ipc_rx_exit(&port->ipc_rx_srp); + ipc_tx_exit(&port->ipc_tx_srp); + } + + ipc_tx_exit(&port->ipc_tx_gptp); + + ipc_rx_exit(&port->ipc_rx_gptp); +} + +__exit static void avdecc_ports_exit(struct avdecc_ctx *avdecc) +{ + unsigned int i; + + for (i = 0; i < avdecc->port_max; i++) { + if (!avdecc->port[i].initialized) + continue; + + net_tx_exit(&avdecc->port[i].net_tx); + net_rx_exit(&avdecc->port[i].net_rx); + + avdecc_port_ipc_exit(&avdecc->port[i]); + + avdecc->port[i].initialized = false; + } + + os_log(LOG_INIT, "done\n"); +} + +__init void *avdecc_init(struct avdecc_config *cfg, unsigned long priv) +{ + struct avdecc_ctx *avdecc; + unsigned int timer_n; + u8 *adp_discovery_data; + int i = 0, j, k = 0; + + if (cfg->num_entities == 0) + cfg->num_entities = 1; + + timer_n = cfg->port_max * cfg->num_entities * CFG_AVDECC_MAX_TIMERS_PER_ENTITY; + + avdecc = avdecc_alloc(timer_n, cfg->port_max, cfg->max_entities_discovery); + if (!avdecc) + goto err_malloc; + + log_level_set(avdecc_COMPONENT_ID, cfg->log_level); + + avdecc->srp_enabled = cfg->srp_enabled; + avdecc->management_enabled = cfg->management_enabled; + avdecc->milan_mode = cfg->milan_mode; + avdecc->use_gptp_bridge_stack = cfg->use_gptp_bridge_stack; + + if (avdecc_ports_init(avdecc, cfg, priv) < 0) + goto err_ports; + + /** + * IPC + */ + if (ipc_rx_init(&avdecc->ipc_rx_controller, IPC_CONTROLLER_AVDECC, avdecc_ipc_rx_controller, priv) < 0) + goto err_ipc_rx_controller; + + if (ipc_rx_init(&avdecc->ipc_rx_controlled, IPC_CONTROLLED_AVDECC, avdecc_ipc_rx_controlled, priv) < 0) + goto err_ipc_rx_controlled; + + if (ipc_rx_init(&avdecc->ipc_rx_media_stack, IPC_MEDIA_STACK_AVDECC, avdecc_ipc_rx_media_stack, priv) < 0) + goto err_ipc_rx_media_stack; + + if (ipc_tx_init(&avdecc->ipc_tx_media_stack, IPC_AVDECC_MEDIA_STACK) < 0) + goto err_ipc_tx_avtp; + + if (ipc_tx_init(&avdecc->ipc_tx_maap, IPC_MEDIA_STACK_MAAP) < 0) + goto err_ipc_tx_maap; + + if (ipc_rx_init(&avdecc->ipc_rx_maap, IPC_MAAP_MEDIA_STACK, avdecc_ipc_rx_maap, priv) < 0) + goto err_ipc_rx_maap; + + if (ipc_tx_connect(&avdecc->ipc_tx_maap, &avdecc->ipc_rx_maap) < 0) + goto err_ipc_tx_maap_connect; + + if (ipc_tx_init(&avdecc->ipc_tx_controlled, IPC_AVDECC_CONTROLLED) < 0) + goto err_ipc_tx_controlled; + + if (ipc_tx_init(&avdecc->ipc_tx_controller, IPC_AVDECC_CONTROLLER) < 0) + goto err_ipc_tx_controller; + + if (ipc_tx_init(&avdecc->ipc_tx_controller_sync, IPC_AVDECC_CONTROLLER_SYNC) < 0) + goto err_ipc_tx_controller_sync; + + + /** + * Timers + */ + if (timer_pool_init(avdecc->timer_ctx, timer_n, priv) < 0) + goto err_timer_pool_init; + + avdecc->num_entities = cfg->num_entities; + + for (i = 0; i < avdecc->num_entities; i++) { + avdecc->entities[i] = avdecc_entity_init(avdecc, i, &cfg->entity_cfg[i]); + if (!avdecc->entities[i]) + goto err_entity_init; + } + + for (k = 0; k < avdecc->port_max; k++) { + adp_discovery_data = (u8 *)avdecc->adp_discovery_data + k * adp_discovery_data_size(cfg->max_entities_discovery); + + if (adp_discovery_init(&avdecc->port[k].discovery, adp_discovery_data, cfg) < 0) + goto err_discovery_init; + } + + for (j = 0; j < avdecc->port_max; j++) { + /* No gptp endpoint stack is available: + * Requests for the DefaultDS are not meaningful to gptp bridge stack. + */ + if (!avdecc->use_gptp_bridge_stack) + avdecc_ipc_managed_get_default_ds(&avdecc->port[j]); + + avdecc_ipc_gm_get_status(&avdecc->port[j]); + } + + if (avdecc->srp_enabled) { + for (j = 0; j < avdecc->port_max; j++) + avdecc_ipc_srp_deregister_all(&avdecc->port[j]); + } + + os_log(LOG_INIT, "avdecc(%p) done, loaded %d entities.\n", avdecc, avdecc->num_entities); + + return avdecc; + +err_discovery_init: + for (j = 0; j < k; j++) + adp_discovery_exit(&avdecc->port[j].discovery); + +err_entity_init: + for (j = 0; j < i; j++) + avdecc_entity_exit(avdecc->entities[j]); + + timer_pool_exit(avdecc->timer_ctx); + +err_timer_pool_init: + ipc_tx_exit(&avdecc->ipc_tx_controller_sync); + +err_ipc_tx_controller_sync: + ipc_tx_exit(&avdecc->ipc_tx_controller); + +err_ipc_tx_controller: + ipc_tx_exit(&avdecc->ipc_tx_controlled); + +err_ipc_tx_controlled: +err_ipc_tx_maap_connect: + ipc_rx_exit(&avdecc->ipc_rx_maap); +err_ipc_rx_maap: + ipc_tx_exit(&avdecc->ipc_tx_maap); + +err_ipc_tx_maap: + ipc_tx_exit(&avdecc->ipc_tx_media_stack); + +err_ipc_tx_avtp: + ipc_rx_exit(&avdecc->ipc_rx_media_stack); + +err_ipc_rx_media_stack: + ipc_rx_exit(&avdecc->ipc_rx_controlled); + +err_ipc_rx_controlled: + ipc_rx_exit(&avdecc->ipc_rx_controller); + +err_ipc_rx_controller: + avdecc_ports_exit(avdecc); + +err_ports: + os_free(avdecc); + +err_malloc: + return NULL; +} + +__exit int avdecc_exit(void *avdecc_h) +{ + struct avdecc_ctx *avdecc = (struct avdecc_ctx *)avdecc_h; + int i; + + /* De-init entities in reverse order while decrementing the num_entities + * counter to protect against access to the freed entity (on avdecc_net_tx_loopback()) + */ + while (avdecc->num_entities-- > 0) { + avdecc_entity_exit(avdecc->entities[avdecc->num_entities]); + } + + /* avdecc_entity_exit() can generate PDUs (in loopback), so de-init the + * discovery context after that. + */ + for (i = 0; i < avdecc->port_max; i++) { + if (!avdecc->port[i].initialized) + continue; + + adp_discovery_exit(&avdecc->port[i].discovery); + } + + timer_pool_exit(avdecc->timer_ctx); + + ipc_tx_exit(&avdecc->ipc_tx_controller_sync); + + ipc_tx_exit(&avdecc->ipc_tx_controller); + + ipc_tx_exit(&avdecc->ipc_tx_controlled); + + ipc_rx_exit(&avdecc->ipc_rx_maap); + + ipc_tx_exit(&avdecc->ipc_tx_maap); + + ipc_tx_exit(&avdecc->ipc_tx_media_stack); + + ipc_rx_exit(&avdecc->ipc_rx_media_stack); + + ipc_rx_exit(&avdecc->ipc_rx_controlled); + + ipc_rx_exit(&avdecc->ipc_rx_controller); + + avdecc_ports_exit(avdecc); + + os_log(LOG_INIT, "avdecc(%p)\n", avdecc); + + os_free(avdecc_h); + + return 0; +} diff --git a/avdecc/avdecc.cmake b/avdecc/avdecc.cmake new file mode 100644 index 0000000..ce6b27a --- /dev/null +++ b/avdecc/avdecc.cmake @@ -0,0 +1,22 @@ +if(CONFIG_AVDECC) + + genavb_include_os(${TARGET_OS}/avdecc.cmake) + + genavb_add_library(NAME avdecc + SRCS + avdecc.c + avdecc_ieee.c + adp_milan.c + adp.c + adp_ieee.c + aecp.c + acmp.c + acmp_ieee.c + acmp_milan.c + aem.c + entity.c + ) + + genavb_link_libraries(TARGET ${avb} LIB avdecc) + +endif() diff --git a/avdecc/avdecc.h b/avdecc/avdecc.h new file mode 100644 index 0000000..ec89db7 --- /dev/null +++ b/avdecc/avdecc.h @@ -0,0 +1,175 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVDECC main header file + @details Definition of AVDECC stack component entry point functions and global context structure. +*/ + +#ifndef _AVDECC_H_ +#define _AVDECC_H_ + +#include "common/net.h" +#include "common/ipc.h" +#include "common/timer.h" +#include "common/avtp.h" +#include "common/avdecc.h" +#include "common/srp.h" +#include "common/log.h" + +#include "genavb/aem.h" + +#include "adp.h" +#include "aecp.h" +#include "acmp.h" +#include "aem.h" + +#define AVDECC_INFLIGHT_TIMER_RESTART 0 +#define AVDECC_INFLIGHT_TIMER_STOP 1 + +/* The Presentation time offset can be changed to any value in the range between 0x0 and 0x7FFFFFFF ns + * as per MILAN Specification v1.2 5.3.7.6 + */ +#define STREAM_PRESENTATION_TIME_OFFSET_MAX 0x7FFFFFFF +#define STREAM_PRESENTATION_TIME_OFFSET_INVALID (STREAM_PRESENTATION_TIME_OFFSET_MAX + 1U) + +struct inflight_data { + u8 retried; + u8 msg_type; + u16 sequence_id; /**< Sequence ID to match the entry against AECP responses, in host byte order. */ + u16 orig_seq_id; + uintptr_t priv[2]; + u8 mac_dst[6]; + u16 len; + u16 port_id; /* avdecc port information */ + union { + struct acmp_pdu acmp; + struct aecp_aem_pdu aem; + u8 buf[AVDECC_AECP_MAX_SIZE]; + //other commands + } pdu; +}; + +struct inflight_ctx { + struct timer timeout; + unsigned int timeout_ms; + struct inflight_data data; + int(*cb)(struct inflight_ctx *); + struct list_head list; + struct list_head *list_head; + struct entity *entity; +}; + +struct entity { + unsigned int index; + unsigned int flags; + struct aem_desc_hdr *aem_descs; + struct aem_desc_hdr *aem_dynamic_descs; + struct aecp_ctx aecp; + struct acmp_ctx acmp; + struct adp_ctx adp; + struct entity_descriptor *desc; + struct avdecc_ctx *avdecc; + struct inflight_ctx *inflight_storage; + struct list_head free_inflight; + unsigned int channel_openmask; + unsigned int channel_waitmask; + unsigned int valid_time; /**< Valid time is in units of seconds. */ + unsigned int max_inflights; + bool milan_mode; +}; + +struct avdecc_port { + unsigned int port_id; //maps directly to AVB interface index + unsigned int logical_port; + + u8 local_physical_mac[6]; + bool initialized; + + struct adp_discovery_ctx discovery; + + struct net_rx net_rx; + struct net_tx net_tx; + + struct ipc_tx ipc_tx_srp; + struct ipc_tx ipc_tx_mac_service; + struct ipc_tx ipc_tx_gptp; + + struct ipc_rx ipc_rx_gptp; + struct ipc_rx ipc_rx_srp; + struct ipc_rx ipc_rx_mac_service; +}; + +/* AVDECC global context structure */ +struct avdecc_ctx { + struct ipc_tx ipc_tx_media_stack; + struct ipc_tx ipc_tx_maap; + struct ipc_tx ipc_tx_controlled; + struct ipc_tx ipc_tx_controller; + struct ipc_tx ipc_tx_controller_sync; + + struct ipc_rx ipc_rx_controller; + struct ipc_rx ipc_rx_controlled; + struct ipc_rx ipc_rx_media_stack; + struct ipc_rx ipc_rx_maap; + + void *adp_discovery_data; + struct timer_ctx *timer_ctx; + struct entity *entities[CFG_AVDECC_NUM_ENTITIES]; //make this an array to pointer to avoid saving the dynamic allocations pointers (for later free) and keep the allocated space starting with the parent. + unsigned int num_entities; + unsigned int port_max; + bool srp_enabled; + bool management_enabled; + bool milan_mode; + bool use_gptp_bridge_stack; /* If true, use gptp bridge stack instance, instead of endpoint stack, to retrieve gptp information */ + + /* variable size array */ + struct avdecc_port port[]; +}; + +static inline unsigned int entity_ready(struct entity *entity) +{ + return (entity->channel_openmask & entity->channel_waitmask) == entity->channel_waitmask; +} + +/* An invalid mac address is all zeroed. */ +static inline bool is_invalid_mac_addr(const u8 *mac_addr) +{ + return !(mac_addr[0] | mac_addr[1] | mac_addr[2] | mac_addr[3] | mac_addr[4] | mac_addr[5]); +} + +#define avdecc_port_to_context(port_) container_of(port_, struct avdecc_ctx, port[port_->port_id]) + +void avdecc_net_rx(struct net_rx *, struct net_rx_desc *); +void avdecc_ipc_rx_gptp(struct ipc_rx const *, struct ipc_desc *); +void avdecc_ipc_rx_controller(struct ipc_rx const *rx, struct ipc_desc *desc); +void avdecc_ipc_rx_controlled(struct ipc_rx const *rx, struct ipc_desc *desc); +void avdecc_ipc_rx_media_stack(struct ipc_rx const *rx, struct ipc_desc *desc); +int avdecc_net_tx(struct avdecc_port *port, struct net_tx_desc *desc); +size_t avdecc_add_common_header(void *buf, u8 subtype, u8 msg_type, u16 length, u8 status); +struct entity * avdecc_get_entity(struct avdecc_ctx *avdecc, u64 entity_id); +bool avdecc_entity_port_valid(struct entity *entity, unsigned int port_id); +struct inflight_ctx *avdecc_inflight_get(struct entity *entity); +int avdecc_inflight_start(struct list_head *inflight, struct inflight_ctx *entry, unsigned int timeout); +void avdecc_inflight_restart(struct inflight_ctx *entry); +struct inflight_ctx *avdecc_inflight_find(struct list_head *inflight_head, u16 sequence_id); +struct inflight_ctx *aem_inflight_find_controller(struct list_head *inflight_head, u16 sequence_id, u64 controller_id); +void avdecc_inflight_remove(struct entity *entity, struct inflight_ctx *entry); +int avdecc_inflight_cancel(struct entity *entity, struct list_head *inflight_head, u16 sequence_id, u16 *orig_seq_id, void **priv0, void **priv1); +struct entity *avdecc_get_local_controller_any(struct avdecc_ctx *avdecc); +struct entity *avdecc_get_local_controller(struct avdecc_ctx *avdecc, unsigned int port_id); +struct entity *avdecc_get_local_controlled_any(struct avdecc_ctx *avdecc); +struct entity *avdecc_get_local_listener(struct avdecc_ctx *avdecc, unsigned int port_id); +struct entity *avdecc_get_local_listener_any(struct avdecc_ctx *avdecc, unsigned int port_id); +struct entity *avdecc_get_local_talker(struct avdecc_ctx *avdecc, unsigned int port_id); +bool avdecc_entity_is_locked(struct entity *entity, u64 controller_id); +bool avdecc_entity_is_acquired(struct entity *entity, u64 controller_id); +struct avdecc_port *logical_to_avdecc_port(struct avdecc_ctx *avdecc, unsigned int logical_port); +unsigned int avdecc_port_to_logical(struct avdecc_ctx *avdecc, unsigned int port_id); + +#endif /* _AVDECC_H_ */ diff --git a/avdecc/avdecc_entry.h b/avdecc/avdecc_entry.h new file mode 100644 index 0000000..4d6ce65 --- /dev/null +++ b/avdecc/avdecc_entry.h @@ -0,0 +1,21 @@ +/* + * Copyright 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVDECC stack component entry points + @details + */ + +#ifndef _AVDECC_ENTRY_H_ +#define _AVDECC_ENTRY_H_ + +#include "genavb/init.h" + +void *avdecc_init(struct avdecc_config *cfg, unsigned long priv); +int avdecc_exit(void *avdecc_h); + +#endif /* _AVDECC_ENTRY_H_ */ diff --git a/avdecc/avdecc_ieee.c b/avdecc/avdecc_ieee.c new file mode 100644 index 0000000..22e24b8 --- /dev/null +++ b/avdecc/avdecc_ieee.c @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVDECC IEEE 1722.1 specific code + @details Handles all AVDECC IEEE 1722.1 specific functions +*/ + +#include "avdecc.h" +#include "avdecc_ieee.h" + +/** Try to establish fast connect with the discovered entity on the specified interface. + * \return none + * \param entity pointer to local entity struct + * \param entity_desc pointer to discovered entity_discovery struct + * \param port_id port index on which to try the connection. + */ +void avdecc_ieee_try_fast_connect(struct entity *entity, struct entity_discovery *entity_disc, unsigned int port_id) +{ + struct avb_interface_dynamic_desc *avb_itf_dynamic; + + avb_itf_dynamic = aem_get_descriptor(entity->aem_dynamic_descs, AEM_DESC_TYPE_AVB_INTERFACE, port_id, NULL); + if (!avb_itf_dynamic) { + os_log(LOG_ERR, "entity(%p) invalid interface index (%u)\n", entity, port_id); + return; + } + + if ((entity->flags & AVDECC_FAST_CONNECT_MODE) + && cmp_64(&entity_disc->info.gptp_grandmaster_id, &avb_itf_dynamic->gptp_grandmaster_id)) + acmp_ieee_listener_fast_connect(&entity->acmp, entity_disc->info.entity_id, port_id); +} + +/** Handles changes/updates for the discovered entity + * \return none + * \param avdecc pointer to avdecc context + * \param disc pointer to discovery context + * \param entity_desc pointer to discovered entity_discovery struct + * \param gptp_gmid_changed true if the GM id changed for the discovered entity, false otherwise + */ +void avdecc_ieee_discovery_update(struct avdecc_ctx *avdecc, struct adp_discovery_ctx *disc, struct entity_discovery *entity_disc, bool gptp_gmid_changed) +{ + struct avdecc_port *port = container_of(disc, struct avdecc_port, discovery); + struct entity *entity; + int i; + + /* DEMO back-to-back */ + /* Find first local entity with Listener capability + * (assume at most one listener entity per endpoint). + */ + entity = avdecc_get_local_listener_any(avdecc, port->port_id); + if (!entity) { + os_log(LOG_DEBUG, "avdecc(%p) port(%u) Couldn't find any local listener entity supporting this port to do fast-connect\n", + avdecc, port->port_id); + return; + } + + /* Prepare fast connect for the first discovered entity which has talker capabilities (and is not ourself) */ + if ((entity_disc->info.talker_capabilities & htons(ADP_TALKER_IMPLEMENTED)) + && (entity_disc->info.talker_capabilities & htons(ADP_TALKER_VIDEO_SOURCE | ADP_TALKER_AUDIO_SOURCE)) + && !cmp_64(&entity_disc->info.entity_id, &entity->desc->entity_id) + && (entity->flags & AVDECC_FAST_CONNECT_BTB)) + acmp_ieee_listener_fast_connect_btb(&entity->acmp, entity_disc->info.entity_id, port->port_id); + /* DEMO back-to-back */ + + if (gptp_gmid_changed) { + for (i = 0; i < avdecc->num_entities; i++) + if (entity_ready(avdecc->entities[i]) && avdecc_entity_port_valid(avdecc->entities[i], port->port_id)) + avdecc_ieee_try_fast_connect(avdecc->entities[i], entity_disc, port->port_id); + } + +} diff --git a/avdecc/avdecc_ieee.h b/avdecc/avdecc_ieee.h new file mode 100644 index 0000000..67f28ac --- /dev/null +++ b/avdecc/avdecc_ieee.h @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVDECC IEEE 1722.1 header file + @details Definition of AVDECC IEEE 1722.1 specific stack components +*/ + +#ifndef _AVDECC_IEEE_H_ +#define _AVDECC_IEEE_H_ + +#include "avdecc.h" + +void avdecc_ieee_try_fast_connect(struct entity *entity, struct entity_discovery *entity_disc, unsigned int port_id); +void avdecc_ieee_discovery_update(struct avdecc_ctx *avdecc, struct adp_discovery_ctx *disc, struct entity_discovery *entity_disc, bool gptp_gmid_changed); + +#endif /* _AVDECC_IEEE_H_ */ diff --git a/avdecc/config.h b/avdecc/config.h new file mode 100644 index 0000000..abfd575 --- /dev/null +++ b/avdecc/config.h @@ -0,0 +1,69 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2019-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVDECC static configuration + @details Contains all compile time configuration options for avdecc +*/ + +#ifndef _AVDECC_CFG_H_ +#define _AVDECC_CFG_H_ + +#include "common/config.h" +#include "common/adp.h" + +#define avdecc_CFG_LOG CFG_LOG + +#define CFG_AVDECC_MAX_TIMERS_PER_ENTITY 6 + +#define AVDECC_CFG_INFLIGHT_TIMER_RESOLUTION 10 + +#define AVDECC_CFG_ENTITY_LOCK_TIMER_GRANULARITY_MS 100 +#define AVDECC_CFG_ENTITY_LOCK_TIMER_MS (1000 * 60) /* 1 min entity lock timer. */ + +#define AECP_CFG_MAX_AEM_IN_PROGRESS (10000 / AECP_IN_PROGRESS_TIMEOUT) /* 10000 ms : Maximum IN_PROGRESS responses for AECP CMD before declaring the application unresponsive*/ + +#define CFG_AECP_DEFAULT_NUM_UNSOLICITED 8 +#define CFG_AECP_MAX_NUM_UNSOLICITED 64 +#define CFG_AECP_MIN_NUM_UNSOLICITED 1 + +#define CFG_ADP_DEFAULT_NUM_ENTITIES_DISCOVERY 16 +#define CFG_ADP_MIN_NUM_ENTITIES_DISCOVERY 8 +#define CFG_ADP_MAX_NUM_ENTITIES_DISCOVERY 128 + +#define CFG_AEM_DEFAULT_NUM_PTLV_ENTRIES 16 +#define CFG_AEM_MIN_NUM_PTLV_ENTRIES 1 +#define CFG_AEM_MAX_NUM_PTLV_ENTRIES 179 + +#define CFG_DEFAULT_AVB_INTERFACE_INDEX 0 + +#define CFG_AVDECC_DEFAULT_NUM_INFLIGHTS 5 +#define CFG_AVDECC_MAX_NUM_INFLIGHTS (CFG_ADP_MAX_NUM_ENTITIES_DISCOVERY) +#define CFG_AVDECC_MIN_NUM_INFLIGHTS 5 + +#define CFG_ADP_DEFAULT_VALID_TIME 62 //seconds +#define CFG_ADP_MIN_VALID_TIME 2 //seconds +#define CFG_ADP_MAX_VALID_TIME 62 //seconds +#define CFG_ADP_MILAN_VALID_TIME 20 //seconds /* AVNU.IO.CONTROL (9.2) */ + +#define CFG_ACMP_DEFAULT_NUM_TALKER_STREAMS 8 +#define CFG_ACMP_MIN_NUM_TALKER_STREAMS 1 +#define CFG_ACMP_MAX_NUM_TALKER_STREAMS 32 + +#define CFG_ACMP_DEFAULT_NUM_LISTENER_STREAMS 8 +#define CFG_ACMP_MIN_NUM_LISTENER_STREAMS 1 +#define CFG_ACMP_MAX_NUM_LISTENER_STREAMS 32 + +#define CFG_ACMP_DEFAULT_NUM_LISTENER_PAIRS 10 +#define CFG_ACMP_MIN_NUM_LISTENER_PAIRS 1 +#define CFG_ACMP_MAX_NUM_LISTENER_PAIRS 512 + +#define CFG_ACMP_DEFAULT_MAAP_BASE_RANGE_ID 1000 /* MAAP base range id for ACMP talkers */ +#define CFG_ACMP_DEFAULT_MAAP_COUNT_PER_RANGE 1 /* One MAAP range per ACMP talker */ + +#endif /* _AVDECC_CFG_H_ */ diff --git a/avdecc/entity.c b/avdecc/entity.c new file mode 100644 index 0000000..7e82cbb --- /dev/null +++ b/avdecc/entity.c @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AECP common code + @details Handles AECP stack +*/ + +#include "common/types.h" +#include "common/net.h" + +#include "genavb/aem.h" + +#include "aem.h" + +#include "entity.h" + +AEM_ENTITY_STORAGE(); + +static struct aem_desc_hdr aem_desc[AEM_NUM_DESC_TYPES] = {{0, }, }; + +__init struct aem_desc_hdr *aem_entity_static_init(void) +{ + AEM_ENTITY_INIT(aem_desc); + + aem_entity_desc_fixup(aem_desc); + aem_configuration_desc_fixup(aem_desc); + aem_video_cluster_desc_fixup(aem_desc); + + return aem_desc; +} diff --git a/avdecc/entity.h b/avdecc/entity.h new file mode 100644 index 0000000..52bf508 --- /dev/null +++ b/avdecc/entity.h @@ -0,0 +1,405 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @brief AECP AEM entity common definitions +*/ + +#ifndef _ENTITY_H_ +#define _ENTITY_H_ + +/* One day this will be generated from a niiice XML file :) */ + +#define AEM_ENTITY_MODEL_ID 0x00049fff00000001 /* to be incremented by one upon change in the structure of entity model - 17722_1-6.2.1.9 */ + + +/* Entity config */ +#define AEM_CFG_ENTITY_NAME "NXP AVB device" +#define AEM_CFG_ENTITY_GROUP_NAME "NXP demo" +#define AEM_CFG_ENTITY_SERIAL "0000000000000001" +#define AEM_CFG_ENTITY_VENDOR_NAME 0 +#define AEM_CFG_ENTITY_MODEL_NAME 1 +#define AEM_CFG_ENTITY_FW_VERSION "0.0.1" +#define AEM_CFG_ENTITY_CAPABILITIES (ADP_ENTITY_CLASS_A_SUPPORTED | ADP_ENTITY_CLASS_B_SUPPORTED | ADP_ENTITY_GPTP_SUPPORTED | ADP_ENTITY_AEM_SUPPORTED) +#define AEM_CFG_ENTITY_TALKER_STREAM_SOURCES 3 +#define AEM_CFG_ENTITY_TALKER_CAPABILITIES (ADP_TALKER_AUDIO_SOURCE | ADP_TALKER_MEDIA_CLOCK_SOURCE | ADP_TALKER_IMPLEMENTED) +#define AEM_CFG_ENTITY_LISTENER_STREAM_SINKS 3 +#define AEM_CFG_ENTITY_LISTENER_CAPABILITIES (ADP_LISTENER_AUDIO_SINK | ADP_LISTENER_MEDIA_CLOCK_SINK | ADP_LISTENER_IMPLEMENTED) +#define AEM_CFG_ENTITY_CONTROLLER_CAPABILITIES 0 +#define AEM_CFG_ENTITY_CURRENT_CONF 0 + +#define AEM_CFG_ENTITY_DESCRIPTORS { AEM_CFG_ENTITY_DESCRIPTOR } + +/* Configuration config */ +#define AEM_CFG_CONFIG_NAME_0 "Unique configuration" +#define AEM_CFG_CONFIG_LOC_DESC_0 7 +#define AEM_CFG_CONFIG_CONTROL_COUNT_0 0 + +#define AEM_CFG_CONFIG_DESCRIPTORS {AEM_CFG_CONFIG_DESCRIPTOR(0)} + +/* Audio unit config */ +#define AEM_CFG_AUDIO_UNIT_NAME_0 "Audio unit" +#define AEM_CFG_AUDIO_UNIT_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_UNIT_CLK_DOMAIN_IDX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_STREAM_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_STREAM_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_IN_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_EXT_OUT_PORT_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_EXT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_IN_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_INT_OUT_PORT_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROLS_0 1 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROLS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SIGNAL_SEL_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MIXERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MIXER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MATRICES_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MATRIX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_SPLITTERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_SPLITTER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_COMBINERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_COMBINER_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_MUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_DEMUX_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_TRANSCODERS_0 0 +#define AEM_CFG_AUDIO_UNIT_NB_CONTROL_BLOCKS_0 0 +#define AEM_CFG_AUDIO_UNIT_BASE_CONTROL_BLOCK_0 0 +#define AEM_CFG_AUDIO_UNIT_CUR_SAMPLING_RATE_0 48000 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_COUNT_0 1 +#define AEM_CFG_AUDIO_UNIT_SAMP_RATES_0 { htonl(48000) } + + +#define AEM_CFG_AUDIO_UNIT_DESCRIPTORS {AEM_CFG_AUDIO_UNIT_DESCRIPTOR(0)} + +/* Stream input config */ +#define AEM_CFG_STREAM_INPUT_NAME_0 "Stream input 0" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 0x0205021800806000 // AAF 2chans 24/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_INPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_INPUT_NAME_1 "Stream input 1" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_1 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_1 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_1) } + +#define AEM_CFG_STREAM_INPUT_NAME_2 "Stream input 2" +#define AEM_CFG_STREAM_INPUT_LOC_DESC_2 AEM_CFG_STREAM_INPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_INPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_INPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_INPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_INPUT_NB_FORMATS_2 AEM_CFG_STREAM_INPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_INPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_INPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_INPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_INPUT_FORMATS_2 { htonll(AEM_CFG_STREAM_INPUT_CURRENT_FORMAT_2) } + +#define AEM_CFG_STREAM_INPUT_DESCRIPTORS {AEM_CFG_STREAM_INPUT_DESCRIPTOR(0), AEM_CFG_STREAM_INPUT_DESCRIPTOR(1), AEM_CFG_STREAM_INPUT_DESCRIPTOR(2)} + +/* Stream output config */ +#define AEM_CFG_STREAM_OUTPUT_NAME_0 "Stream output 0" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 (AEM_STREAM_FLAG_CLASS_A | AEM_STREAM_FLAG_CLASS_B) +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 0x0205021800806000 // AAF 2chans 24/32bits 48kHz 6samples/packet +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 1 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_0 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_1 "Stream output 1" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_1 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_1 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_1 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_1 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_1 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_1 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_1 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_1 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_1 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_1) } + +#define AEM_CFG_STREAM_OUTPUT_NAME_2 "Stream output 2" +#define AEM_CFG_STREAM_OUTPUT_LOC_DESC_2 AEM_CFG_STREAM_OUTPUT_LOC_DESC_0 +#define AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_2 AEM_CFG_STREAM_OUTPUT_CLOCK_DOMAIN_IDX_0 +#define AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_2 AEM_CFG_STREAM_OUTPUT_STREAM_FLAGS_0 +#define AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2 AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_0 +#define AEM_CFG_STREAM_OUTPUT_NB_FORMATS_2 AEM_CFG_STREAM_OUTPUT_NB_FORMATS_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_0_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_1_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_2 AEM_CFG_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_2_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_0 +#define AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_2 AEM_CFG_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_0 +#define AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_2 AEM_CFG_STREAM_OUTPUT_AVB_ITF_INDEX_0 +#define AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_2 AEM_CFG_STREAM_OUTPUT_BUFFER_LENGTH_0 +#define AEM_CFG_STREAM_OUTPUT_FORMATS_2 { htonll(AEM_CFG_STREAM_OUTPUT_CURRENT_FORMAT_2) } + +#define AEM_CFG_STREAM_OUTPUT_DESCRIPTORS {AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(0), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(1), AEM_CFG_STREAM_OUTPUT_DESCRIPTOR(2)} + + +/* Jack output config */ +#define AEM_CFG_JACK_OUTPUT_NAME_0 "Jack output" +#define AEM_CFG_JACK_OUTPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_OUTPUT_FLAGS_0 0 +#define AEM_CFG_JACK_OUTPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_OUTPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_OUTPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_OUTPUT_DESCRIPTORS {AEM_CFG_JACK_OUTPUT_DESCRIPTOR(0)} + + +/* Jack input config */ +#define AEM_CFG_JACK_INPUT_NAME_0 "Jack input" +#define AEM_CFG_JACK_INPUT_LOC_DESC_0 7 +#define AEM_CFG_JACK_INPUT_FLAGS_0 0 +#define AEM_CFG_JACK_INPUT_TYPE_0 AEM_JACK_TYPE_UNBALANCED_ANALOG +#define AEM_CFG_JACK_INPUT_NUM_CTRL_0 0 +#define AEM_CFG_JACK_INPUT_BASE_CTRL_0 0 + + +#define AEM_CFG_JACK_INPUT_DESCRIPTORS {AEM_CFG_JACK_INPUT_DESCRIPTOR(0)} + + +/* AVB interface config */ +#define AEM_CFG_AVB_ITF_NAME_0 "AVB interface" +#define AEM_CFG_AVB_ITF_LOC_DESC_0 7 +#define AEM_CFG_AVB_ITF_ITF_FLAGS_0 AEM_AVB_FLAGS_GPTP_SUPPORTED +#define AEM_CFG_AVB_ITF_CLOCK_ID_0 0 +#define AEM_CFG_AVB_ITF_PRIO1_0 0xFF +#define AEM_CFG_AVB_ITF_CLOCK_CLASS_0 0xFF +#define AEM_CFG_AVB_ITF_OFF_SCALED_VAR_0 0 +#define AEM_CFG_AVB_ITF_CLOCK_ACCURACY_0 0xFF +#define AEM_CFG_AVB_ITF_PRIO2_0 0xFF +#define AEM_CFG_AVB_ITF_DOMAIN_NB_0 0 +#define AEM_CFG_AVB_ITF_LOG_SYN_INTER_0 0 +#define AEM_CFG_AVB_ITF_LOG_ANN_INTER_0 0 +#define AEM_CFG_AVB_ITF_POG_PDEL_INTER_0 0 +#define AEM_CFG_AVB_ITF_PORT_NB_0 0 + + +#define AEM_CFG_AVB_ITF_DESCRIPTORS {AEM_CFG_AVB_ITF_DESCRIPTOR(0)} + +/* Clock source config */ +#define AEM_CFG_CLK_SOURCE_NAME_0 "Clock source" +#define AEM_CFG_CLK_SOURCE_LOC_DESC_0 7 +#define AEM_CFG_CLK_SOURCE_FLAGS_0 AEM_CLOCK_SOURCE_FLAGS_LOCAL_ID +#define AEM_CFG_CLK_SOURCE_TYPE_0 AEM_CLOCK_SOURCE_TYPE_INPUT_STREAM +#define AEM_CFG_CLK_SOURCE_ID_0 0 +#define AEM_CFG_CLK_SOURCE_LOC_TYPE_0 AEM_DESC_TYPE_STREAM_INPUT +#define AEM_CFG_CLK_SOURCE_LOC_INDEX_0 0 + + +#define AEM_CFG_CLK_SOURCE_DESCRIPTORS {AEM_CFG_CLK_SOURCE_DESCRIPTOR(0)} + +/* Locale config */ +#define AEM_CFG_LOCALE_IDENTIFIER_0 "en" +#define AEM_CFG_LOCALE_NB_STRINGS_0 1 +#define AEM_CFG_LOCALE_BASE_STRINGS_0 0 + + +#define AEM_CFG_LOCALE_DESCRIPTORS {AEM_CFG_LOCALE_DESCRIPTOR(0)} + +/* Strings config */ +#define AEM_CFG_STRINGS_0_0 "NXP AVB" +#define AEM_CFG_STRINGS_1_0 {} +#define AEM_CFG_STRINGS_2_0 {} +#define AEM_CFG_STRINGS_3_0 {} +#define AEM_CFG_STRINGS_4_0 {} +#define AEM_CFG_STRINGS_5_0 {} +#define AEM_CFG_STRINGS_6_0 {} + +#define AEM_CFG_STRINGS_DESCRIPTORS {AEM_CFG_STRINGS_DESCRIPTOR(0)} + +/* Clock domain config */ +#define AEM_CFG_CLK_DOMAIN_NAME_0 "Clock domain" +#define AEM_CFG_CLK_DOMAIN_LOC_DESC_0 7 +#define AEM_CFG_CLK_DOMAIN_SOURCE_IDX_0 0 +#define AEM_CFG_CLK_DOMAIN_SOURCES_COUNT_0 1 +#define AEM_CFG_CLK_DOMAIN_SOURCES_0 {htons(0)} + + +#define AEM_CFG_CLK_DOMAIN_DESCRIPTORS {AEM_CFG_CLK_DOMAIN_DESCRIPTOR(0)} + +/* Stream port input config */ +#define AEM_CFG_STREAM_PORT_IN_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_IN_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_IN_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_CLUSTER_0 0 +#define AEM_CFG_STREAM_PORT_IN_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_IN_BASE_MAP_0 0 + + +#define AEM_CFG_STREAM_PORT_IN_DESCRIPTORS {AEM_CFG_STREAM_PORT_IN_DESCRIPTOR(0)} + + +/* Stream port output config */ +#define AEM_CFG_STREAM_PORT_OUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_STREAM_PORT_OUT_PORT_FLAGS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CONTROLS_0 0 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CONTROL_0 0 +#define AEM_CFG_STREAM_PORT_OUT_NB_CLUSTERS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_CLUSTER_0 1 +#define AEM_CFG_STREAM_PORT_OUT_NB_MAPS_0 1 +#define AEM_CFG_STREAM_PORT_OUT_BASE_MAP_0 1 + + +#define AEM_CFG_STREAM_PORT_OUT_DESCRIPTORS {AEM_CFG_STREAM_PORT_OUT_DESCRIPTOR(0)} + + +/* Audio cluster config */ +#define AEM_CFG_AUDIO_CLUSTER_NAME_0 "Audio cluster 0" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_0 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_0 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_0 1000000 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_0 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_0 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_0 AEM_AUDIO_CLUSTER_FORMAT_MBLA + +#define AEM_CFG_AUDIO_CLUSTER_NAME_1 "Audio cluster 1" +#define AEM_CFG_AUDIO_CLUSTER_LOC_DESC_1 7 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_TYPE_1 AEM_DESC_TYPE_EXTERNAL_PORT_INPUT +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_IDX_1 0 +#define AEM_CFG_AUDIO_CLUSTER_SIGNAL_OUTPUT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_PATH_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_BLOCK_LAT_1 0 +#define AEM_CFG_AUDIO_CLUSTER_CHAN_COUNT_1 2 +#define AEM_CFG_AUDIO_CLUSTER_FORMAT_1 AEM_AUDIO_CLUSTER_FORMAT_MBLA + + +#define AEM_CFG_AUDIO_CLUSTER_DESCRIPTORS {AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(0), AEM_CFG_AUDIO_CLUSTER_DESCRIPTOR(1)} + +/* Audio map config */ + +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_0 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_0 {\ + {0x0000, 0x0000, 0x0000, 0x0000},\ + {0x0000, 0x0100, 0x0000, 0x0100}} + +#define AEM_CFG_AUDIO_MAP_NB_MAPPINGS_1 2 +#define AEM_CFG_AUDIO_MAP_MAP_UNIT_1 {\ + {0x0000, 0x0000, 0x0000, 0x0000},\ + {0x0000, 0x0100, 0x0000, 0x0100}} + + +#define AEM_CFG_AUDIO_MAP_DESCRIPTORS {AEM_CFG_AUDIO_MAP_DESCRIPTOR(0), AEM_CFG_AUDIO_MAP_DESCRIPTOR(1)} + +/* External port input config */ +#define AEM_CFG_EXT_PORT_INPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_INPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_INVALID +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_INPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_INPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_INPUT_JACK_IDX_0 0 + + + +#define AEM_CFG_EXT_PORT_INPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_INPUT_DESCRIPTOR(0)} + + +/* External port output config */ +#define AEM_CFG_EXT_PORT_OUTPUT_CLK_DOM_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_PORT_FLAGS_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_NB_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BASE_CONTROL_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_TYPE_0 AEM_DESC_TYPE_CONTROL +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_IDX_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_EXT_PORT_OUTPUT_BLOCK_LAT_0 100 +#define AEM_CFG_EXT_PORT_OUTPUT_JACK_IDX_0 0 + + + +#define AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTORS {AEM_CFG_EXT_PORT_OUTPUT_DESCRIPTOR(0)} + + +#define AEM_CFG_CONTROL_NAME_0 "Volume Control 0" +#define AEM_CFG_CONTROL_LOC_DESC_0 7 +#define AEM_CFG_CONTROL_BLOCK_LAT_0 0 +#define AEM_CFG_CONTROL_CTRL_LAT_0 200 +#define AEM_CFG_CONTROL_DOMAIN_0 0 +#define AEM_CFG_CONTROL_VALUE_TYPE_0 AEM_CONTROL_SET_VALUE_TYPE(0, 0, AEM_CONTROL_LINEAR_UINT8) +#define AEM_CFG_CONTROL_TYPE_0 AEM_CONTROL_TYPE_GAIN +#define AEM_CFG_CONTROL_RESET_TIME_0 0 +#define AEM_CFG_CONTROL_NB_VALUES_0 1 +#define AEM_CFG_CONTROL_SIGNAL_TYPE_0 AEM_DESC_TYPE_AUDIO_CLUSTER +#define AEM_CFG_CONTROL_SIGNAL_INDEX_0 0 +#define AEM_CFG_CONTROL_SIGNAL_OUTPUT_0 0 +#define AEM_CFG_CONTROL_VALUE_DETAILS_0 {\ + .linear_int8 = {{0, 100, 1, 50, 100, htons(AEM_CONTROL_SET_UNIT_FORMAT(0, AEM_CONTROL_CODE_PERCENT)), 0}}} + + +#define AEM_CFG_CONTROL_DESCRIPTORS {AEM_CFG_CONTROL_DESCRIPTOR(0)} + +#include "genavb/aem_entity.h" + +#endif /* _ENTITY_H_ */ diff --git a/avdecc/linux/avdecc.cmake b/avdecc/linux/avdecc.cmake new file mode 100644 index 0000000..c980256 --- /dev/null +++ b/avdecc/linux/avdecc.cmake @@ -0,0 +1 @@ +genavb_target_add_srcs(TARGET ${avb} SRCS main.c) diff --git a/avdecc/linux/main.c b/avdecc/linux/main.c new file mode 100644 index 0000000..3b45697 --- /dev/null +++ b/avdecc/linux/main.c @@ -0,0 +1,157 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVDECC linux specific code + @details Setups linux thread for AVDECC stack component. Implements AVDECC main loop and event handling. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common/types.h" +#include "common/log.h" +#include "common/ipc.h" + +#include "os/string.h" +#include "os/net.h" +#include "os/ipc.h" +#include "os/timer.h" + +#include "linux/avb.h" + +#include "avdecc/avdecc_entry.h" + + +#define EPOLL_MAX_EVENTS 8 + +/* Linux specific AVDECC code entry points */ + +static void avdecc_thread_cleanup(void *arg) +{ + struct avb_ctx *avb = arg; + struct avdecc_ctx *avdecc = avb->avdecc; + + avdecc_exit(avdecc); + + avb->avdecc = NULL; + + os_log(LOG_INIT, "done\n"); +} + +static void avdecc_status(struct avb_ctx *avb, int status) +{ + pthread_mutex_lock(&avb->status_mutex); + + avb->avdecc_status = status; + + pthread_cond_signal(&avb->avdecc_cond); + + pthread_mutex_unlock(&avb->status_mutex); +} + +void *avdecc_thread_main(void *arg) +{ + struct avb_ctx *avb = arg; + struct avdecc_ctx *avdecc; + int epoll_fd; + struct epoll_event event[EPOLL_MAX_EVENTS]; + struct sched_param param = { + .sched_priority = AVDECC_CFG_PRIORITY, + }; + int rc; + + rc = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); + if (rc) { + os_log(LOG_ERR, "pthread_setschedparam(), %s\n", strerror(rc)); + goto err_setschedparam; + } + + + epoll_fd = epoll_create(1); + if (epoll_fd < 0) { + os_log(LOG_CRIT, "epoll_create(), %s\n", strerror(errno)); + goto err_epoll_create; + } + + avdecc = avdecc_init(&avb->avdecc_cfg, epoll_fd); + if (!avdecc) + goto err_avdecc_init; + + avb->avdecc = avdecc; + + pthread_cleanup_push(avdecc_thread_cleanup, avb); + + os_log(LOG_INIT, "started\n"); + + avdecc_status(avb, 1); + + while (1) { + int ready, i; + struct linux_epoll_data *epoll_data; + + /* thread main loop */ + /* use epoll to wait for events from all open file descriptors */ + + pthread_testcancel(); + + ready = epoll_wait(epoll_fd, event, EPOLL_MAX_EVENTS, -1); + if (ready < 0) { + if (errno == EINTR) + continue; + + os_log(LOG_CRIT, "epoll_wait(), %s\n", strerror(errno)); + break; + } + + for (i = 0; i < ready; i++) { + if (event[i].events & (EPOLLHUP | EPOLLRDHUP)) + os_log(LOG_ERR, "event error, %x\n", event[i].events); + + if (event[i].events & (EPOLLIN | EPOLLERR)) { + epoll_data = (struct linux_epoll_data *)event[i].data.ptr; + + switch (epoll_data->type) { + case EPOLL_TYPE_NET_RX: + net_rx((struct net_rx *)epoll_data->ptr); + break; + case EPOLL_TYPE_TIMER: + os_timer_process((struct os_timer *)epoll_data->ptr); + break; + case EPOLL_TYPE_IPC: + ipc_rx((struct ipc_rx *)epoll_data->ptr); + break; + default: + break; + } + } + } + } + + pthread_cleanup_pop(1); + + close(epoll_fd); + + return (void *)0; + +err_avdecc_init: + close(epoll_fd); + +err_epoll_create: + avb->avdecc = NULL; + +err_setschedparam: + avdecc_status(avb, -1); + + return (void *)-1; +} diff --git a/avdecc/rtos/avdecc.cmake b/avdecc/rtos/avdecc.cmake new file mode 100644 index 0000000..c980256 --- /dev/null +++ b/avdecc/rtos/avdecc.cmake @@ -0,0 +1 @@ +genavb_target_add_srcs(TARGET ${avb} SRCS main.c) diff --git a/avdecc/rtos/main.c b/avdecc/rtos/main.c new file mode 100644 index 0000000..abf0b47 --- /dev/null +++ b/avdecc/rtos/main.c @@ -0,0 +1,230 @@ +/* + * Copyright 2018-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief NXP AVDECC RTOS specific code + @details Setups RTOS task for NXP AVDECC stack component. Implements main loop and event handling. + */ + +#include +#include + +#include "rtos_abstraction_layer.h" + +#include "common/ipc.h" +#include "common/log.h" + +#include "os/config.h" +#include "os/sys_types.h" +#include "os/log.h" +#include "os/sys_types.h" +#include "os/net.h" +#include "os/ipc.h" +#include "os/timer.h" + +#include "avdecc/avdecc_entry.h" +#include "avdecc/config.h" + +#define AVDECC_TASK_NAME "AVDECC Stack" +#define AVDECC_TASK_SUCCESS (1 << 0) +#define AVDECC_TASK_ERROR (1 << 1) + +struct avdecc_ctx { + rtos_event_group_t event_group; + struct avdecc_config *avdecc_cfg; + rtos_mqueue_t event_queue; + uint8_t queue_buffer[AVDECC_CFG_EVENT_QUEUE_LENGTH * sizeof(struct event)]; + rtos_thread_t task; + void *avdecc; +}; + +const struct avdecc_config avdecc_default_config = { + .log_level = avdecc_CFG_LOG, + .port_max = CFG_EP_DEFAULT_NUM_PORTS, + .logical_port_list = CFG_EP_LOGICAL_PORT_LIST, + .enabled = true, + .srp_enabled = true, +#ifdef CONFIG_MANAGEMENT + .management_enabled = true, +#else + .management_enabled = false, +#endif + .milan_mode = false, + .use_gptp_bridge_stack = false, + .num_entities = 1, + .max_entities_discovery = CFG_ADP_DEFAULT_NUM_ENTITIES_DISCOVERY, + .entity_cfg[0 ... CFG_AVDECC_NUM_ENTITIES - 1].valid_time = CFG_ADP_DEFAULT_VALID_TIME, + .entity_cfg[0 ... CFG_AVDECC_NUM_ENTITIES - 1].max_listener_pairs = CFG_ACMP_DEFAULT_NUM_LISTENER_PAIRS, + .entity_cfg[0 ... CFG_AVDECC_NUM_ENTITIES - 1].max_listener_streams = 3, + .entity_cfg[0 ... CFG_AVDECC_NUM_ENTITIES - 1].max_talker_streams = 3, + .entity_cfg[0 ... CFG_AVDECC_NUM_ENTITIES - 1].max_inflights = CFG_AVDECC_DEFAULT_NUM_INFLIGHTS, + .entity_cfg[0 ... CFG_AVDECC_NUM_ENTITIES - 1].max_unsolicited_registrations = CFG_AECP_DEFAULT_NUM_UNSOLICITED, + .entity_cfg[0 ... CFG_AVDECC_NUM_ENTITIES - 1].max_ptlv_entries = CFG_AEM_DEFAULT_NUM_PTLV_ENTRIES, +}; + + +/** + * + */ +__init static void clip_config_values(unsigned int *val, unsigned int min, unsigned int max) +{ + if (*val < min) + *val = min; + + if (*val > max) + *val = max; +} + +__init static void process_avdecc_entity(struct avdecc_entity_config *entity_cfg) +{ + entity_cfg->flags &= (AVDECC_FAST_CONNECT_MODE | AVDECC_FAST_CONNECT_BTB); + + clip_config_values(&entity_cfg->talker_entity_id_n, 0, ACMP_CFG_MAX_UNIQUE_ID); + clip_config_values(&entity_cfg->talker_unique_id_n, 0, ACMP_CFG_MAX_UNIQUE_ID); + clip_config_values(&entity_cfg->listener_unique_id_n, 0, ACMP_CFG_MAX_UNIQUE_ID); + clip_config_values(&entity_cfg->valid_time, CFG_ADP_MIN_VALID_TIME, CFG_ADP_MAX_VALID_TIME); + clip_config_values(&entity_cfg->max_listener_streams, CFG_ACMP_MIN_NUM_LISTENER_STREAMS, CFG_ACMP_MAX_NUM_LISTENER_STREAMS); + clip_config_values(&entity_cfg->max_talker_streams, CFG_ACMP_MIN_NUM_TALKER_STREAMS, CFG_ACMP_MAX_NUM_TALKER_STREAMS); + clip_config_values(&entity_cfg->max_listener_pairs, CFG_ACMP_MIN_NUM_LISTENER_PAIRS, CFG_ACMP_MAX_NUM_LISTENER_PAIRS); + clip_config_values(&entity_cfg->max_inflights, CFG_AVDECC_MIN_NUM_INFLIGHTS, CFG_AVDECC_MAX_NUM_INFLIGHTS); + clip_config_values(&entity_cfg->max_unsolicited_registrations, CFG_AECP_MIN_NUM_UNSOLICITED, CFG_AECP_MAX_NUM_UNSOLICITED); +} + +/** + * + */ +__init static void process_config(struct avdecc_config *avdecc_cfg) +{ + int entity_index = 0; + + if (avdecc_cfg->num_entities > CFG_AVDECC_NUM_ENTITIES) + avdecc_cfg->num_entities = CFG_AVDECC_NUM_ENTITIES; + + for (entity_index = 0; entity_index < avdecc_cfg->num_entities; entity_index++) { + process_avdecc_entity(&avdecc_cfg->entity_cfg[entity_index]); + } +} + +/** + * + */ +static void avdecc_task(void *pvParameters) +{ + struct avdecc_ctx *ctx = pvParameters; + struct avdecc_config *avdecc_cfg = ctx->avdecc_cfg; + void *avdecc = NULL; + + os_log(LOG_INIT, "avdecc task init\n"); + + if (rtos_mqueue_init(&ctx->event_queue, AVDECC_CFG_EVENT_QUEUE_LENGTH, sizeof(struct event), ctx->queue_buffer) < 0) { + os_log(LOG_ERR, "rtos_mqueue_init() failed\n"); + goto err_queue_create; + } + + /** + * AVDECC Config + */ + process_config(avdecc_cfg); + + avdecc = avdecc_init(avdecc_cfg, (unsigned long)&ctx->event_queue); + if (!avdecc) + goto err_avdecc_init; + + ctx->avdecc = avdecc; + + rtos_event_group_set(&ctx->event_group, AVDECC_TASK_SUCCESS); + + os_log(LOG_INIT, "started\n"); + + /** + * Main loop + */ + while (1) { + struct event e; + + if (rtos_mqueue_receive(&ctx->event_queue, &e, RTOS_MS_TO_TICKS(10000)) < 0) + continue; + + switch (e.type) { + case EVENT_TYPE_NET_RX: + net_rx((struct net_rx *) e.data); + break; + case EVENT_TYPE_TIMER: + os_timer_process((struct os_timer *)e.data); + break; + case EVENT_TYPE_IPC: + ipc_rx((struct ipc_rx *)e.data); + break; + default: + os_log(LOG_ERR, "rtos_mqueue_receive(): invalid event type(%u)\n", e.type); + break; + } + } + + /* Not reached */ + +err_avdecc_init: + rtos_mqueue_destroy(&ctx->event_queue); + +err_queue_create: + os_log(LOG_INIT, "avdecc task exited\n"); + + rtos_event_group_set(&ctx->event_group, AVDECC_TASK_ERROR); + + rtos_thread_abort(NULL); +} + +/** + * + */ +__init void *avdecc_task_init(struct avdecc_config *avdecc_cfg) +{ + struct avdecc_ctx *ctx; + uint32_t bits; + + ctx = rtos_malloc(sizeof(struct avdecc_ctx)); + if (!ctx) + goto err_ctx_alloc; + + if (rtos_event_group_init(&ctx->event_group) < 0) + goto err_event_group; + + ctx->avdecc_cfg = avdecc_cfg; + + if (rtos_thread_create(&ctx->task, AVDECC_CFG_PRIORITY, 0, AVDECC_CFG_STACK_DEPTH, AVDECC_TASK_NAME, avdecc_task, ctx) < 0) { + os_log(LOG_ERR, "rtos_thread_create(%s) failed\n", AVDECC_TASK_NAME); + goto err_task_create; + } + + bits = rtos_event_group_wait(&ctx->event_group, AVDECC_TASK_SUCCESS | AVDECC_TASK_ERROR, false, RTOS_WAIT_FOREVER); + if (bits & AVDECC_TASK_ERROR) + goto err_event_group; + + os_log(LOG_INIT, "avdecc main completed\n"); + + return ctx; + +err_event_group: +err_task_create: + rtos_free(ctx); + +err_ctx_alloc: + return NULL; +} + +__exit void avdecc_task_exit(void *handle) +{ + struct avdecc_ctx *ctx = handle; + + rtos_thread_abort(&ctx->task); + + avdecc_exit(ctx->avdecc); + + rtos_mqueue_destroy(&ctx->event_queue); + + rtos_free(ctx); +} diff --git a/avtp/61883_iidc.c b/avtp/61883_iidc.c new file mode 100644 index 0000000..500970e --- /dev/null +++ b/avtp/61883_iidc.c @@ -0,0 +1,903 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief IEC 61883/IIDC protocol handling functions + @details +*/ + + +#include "os/stdlib.h" +#include "os/clock.h" + +#include "common/61883_iidc.h" +#include "common/log.h" +#include "common/net.h" +#include "common/ipc.h" +#include "common/avdecc.h" + +#include "61883_iidc.h" +#include "stream.h" +#include "avtp.h" +#include "media_clock.h" + +static unsigned int avtp_61883_4_prepare_header(struct avtp_data_hdr *hdr, const struct avdecc_format *format); +static unsigned int avtp_61883_6_prepare_header(struct avtp_data_hdr *hdr, const struct avdecc_format *format); + +void avtp_61883_2_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + +} + +void avtp_61883_3_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + +} + + +/* P1722_D14 - chapter I.2.1.1.2.2 */ +static int stream_61883_6_check_format(u64 const *stream_id, struct avdecc_format_iec61883_6_t const *iec61883_6) +{ + int rc = GENAVB_SUCCESS; + unsigned int dbs_check = 0; + + switch (iec61883_6->fdf_u.fdf.evt) { + case IEC_61883_6_FDF_EVT_AM824: + /* for now, support only mbla. All others cnt should be 0 */ + if (iec61883_6->label_iec_60958_cnt || iec61883_6->label_midi_cnt || iec61883_6->label_smptecnt) + goto err_format; + + /* data block size check - P1722_D14 - chapter I.2.1.1.2.2.3 */ + dbs_check = iec61883_6->label_iec_60958_cnt + iec61883_6->label_mbla_cnt + + iec61883_6->label_midi_cnt + iec61883_6->label_smptecnt; + + if (iec61883_6->dbs != dbs_check) { + os_log(LOG_ERR, "stream_id(%016"PRIx64") IEC61883_6 invalid DBS value, %u. Should be %u.\n", + ntohll(*stream_id), iec61883_6->dbs, dbs_check); + goto err_format; + } + /* intentional fall through */ + + case IEC_61883_6_FDF_EVT_FLOATING: + case IEC_61883_6_FDF_EVT_INT32: + /* Data block size in quadlets (max 256). Should be > 0, and not exceed the allowed maximum */ + if ((!iec61883_6->dbs) || (iec61883_6->dbs > CFG_AVTP_61883_6_MAX_CHANNELS)) { + os_log(LOG_ERR, "stream_id(%016"PRIx64") IEC61883_6 invalid DBS value (%u).\n", + ntohll(*stream_id), iec61883_6->dbs); + + goto err_format; + } + break; + + case IEC_61883_6_FDF_EVT_PACKED: + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") IEC61883_6 invalid evt value (%u)\n", + ntohll(*stream_id), iec61883_6->fdf_u.fdf.evt); + goto err_format; + } + + switch (iec61883_6->fdf_u.fdf.sfc) { + case IEC_61883_6_FDF_SFC_32000: + case IEC_61883_6_FDF_SFC_44100: + case IEC_61883_6_FDF_SFC_48000: + case IEC_61883_6_FDF_SFC_88200: + case IEC_61883_6_FDF_SFC_96000: + case IEC_61883_6_FDF_SFC_176400: + case IEC_61883_6_FDF_SFC_192000: + break; + + case IEC_61883_6_FDF_SFC_RSVD: + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") IEC61883_6 invalid sfc value (%u)\n", + ntohll(*stream_id), iec61883_6->fdf_u.fdf.sfc); + goto err_format; + } + + + /* b = blocking mode support, nb = non-blocking mode support - support only b=0 nb=1 */ + if ((iec61883_6->b == 1) || (iec61883_6->nb == 0)) { + os_log(LOG_ERR, "stream_id(%016"PRIx64") IEC61883_6 incompatible b/nb (%u/%u) values\n", + ntohll(*stream_id), iec61883_6->b, iec61883_6->nb); + goto err_format; + } + +//TODO check new ut and sc fields are equal to 0? +// #ifdef CFG_AVTP_1722A +// if ((iec61883_6->ut != 0) || (iec61883_6->sc != 0)) { +// os_log(LOG_ERR, "stream_id(%016"PRIx64") invalid ut/sc (%u/%u) values. Should be 0/0\n", +// ntohll(stream->id), iec61883_6->ut, iec61883_6->sc); +// goto err_format; +// } +// #endif + + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + +/** Calculate a SYT interval for 61883-6 streams + * + * Calculates a SYT interval such that: + * syt_interval = 2^(n + SYT_INTERVAL_LN2) >= frames per packet + * + * \return Log2 of the calculated syt_interval + * \param format stream format + * \param class stream SR class + */ +static unsigned int avtp_61883_6_syt_interval_ln2(const struct avdecc_format *format, sr_class_t class) +{ + unsigned int frames_per_packet = avdecc_fmt_samples_per_packet(format, class); + unsigned int syt_interval_ln2 = SYT_INTERVAL_LN2; + + while ((1 << syt_interval_ln2) < frames_per_packet) + syt_interval_ln2++; + + return syt_interval_ln2; +} + +static int listener_stream_61883_iidc_init(struct stream_listener *stream) +{ + struct avdecc_format const *format = &stream->format; + + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_4: + stream->net_rx = avtp_61883_4_net_rx; + break; + + case IEC_61883_CIP_FMT_6: + stream->net_rx = avtp_61883_6_net_rx; + stream->subtype_data.iec61883_6.syt_interval_ln2 = avtp_61883_6_syt_interval_ln2(format, stream->class); + break; + + default: + break; + } + + return 0; +} + + +/* P1722_D14 chapter I.2.1.1 */ +int listener_stream_61883_iidc_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags) +{ + int rc = GENAVB_SUCCESS; + + /* checking stream format IIDC or IEC 61883-X */ + switch (format->u.s.subtype_u.iec61883.sf) { + case IEC_61883_SF_61883: + break; + + case IEC_61883_SF_IIDC: + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") Stream format (%u) not supported\n", + ntohll(stream->id), format->u.s.subtype_u.iec61883.sf); + rc = -GENAVB_ERR_STREAM_PARAMS; + goto exit; + } + + + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_4: + if (flags & IPC_AVTP_FLAGS_MCR) { + os_log(LOG_ERR, "stream_id(%016"PRIx64") Media clock recovery not supported\n", ntohll(stream->id)); + rc = -GENAVB_ERR_STREAM_PARAMS; + goto exit; + } + + /* no specific checks on 61883-4 - P1722_D14 chap I.2.1.1.2.1 */ + break; + + case IEC_61883_CIP_FMT_6: + rc = stream_61883_6_check_format(&stream->id, &format->u.s.subtype_u.iec61883.format_u.iec61883_6); + if (rc < 0) + goto exit; + + break; + + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") 61883 format (%u) not supported\n", + ntohll(stream->id), format->u.s.subtype_u.iec61883.fmt); + + rc = -GENAVB_ERR_STREAM_PARAMS; + break; + } + + stream->init = listener_stream_61883_iidc_init; + +exit: + return rc; +} + +static void talker_stream_61883_iidc_init(struct stream_talker *stream, unsigned int *hdr_len) +{ + struct avdecc_format const *format = &stream->format; + + stream->subtype_data.iec61883.iec_hdr = (struct iec_61883_hdr *)(stream->avtp_hdr + 1); + + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_4: + /* There is no real syt_interval in this case, but the ratio timestamp/sample is still a valid concept */ + stream->subtype_data.iec61883.syt_interval_ln2 = 0; + stream->net_tx = avtp_61883_4_net_tx; + *hdr_len = avtp_61883_4_prepare_header(stream->avtp_hdr, format); + break; + + case IEC_61883_CIP_FMT_6: + stream->subtype_data.iec61883.syt_interval_ln2 = avtp_61883_6_syt_interval_ln2(format, stream->class); + stream->net_tx = avtp_61883_6_net_tx; + *hdr_len = avtp_61883_6_prepare_header(stream->avtp_hdr, format); + break; + + default: + break; + } + + avtp_data_header_set_len(stream->avtp_hdr, stream->payload_size + *hdr_len); + + *hdr_len += avtp_data_header_init(stream->avtp_hdr, AVTP_SUBTYPE_61883_IIDC, &stream->id); + + stream->common.flags |= STREAM_FLAG_CLOCK_GENERATION; +} + + +/* Check stream format and initialize parameters - P1722_D14 chapter I.2.1.1 */ +int talker_stream_61883_iidc_check(struct stream_talker *stream, struct avdecc_format const *format, + struct ipc_avtp_connect *ipc) +{ + int rc = GENAVB_SUCCESS; + + /* checking stream format IIDC or IEC 61883-X */ + switch (format->u.s.subtype_u.iec61883.sf) { + case IEC_61883_SF_61883: + break; + + case IEC_61883_SF_IIDC: + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") Stream format (%u) not supported\n", + ntohll(stream->id), format->u.s.subtype_u.iec61883.sf); + + goto err_format; + } + + + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_4: + /* no specific checks on 61883-4 - P1722_D14 chap I.2.1.1.2.1 */ + break; + + case IEC_61883_CIP_FMT_6: + rc = stream_61883_6_check_format(&stream->id, &format->u.s.subtype_u.iec61883.format_u.iec61883_6); + break; + + default: + goto err_format; + } + + stream->init = talker_stream_61883_iidc_init; + + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + + +/** Handles reception of 61883-4 avtp packets + * + * Does protocol validation and decapsulation, converts avtp descriptors to media descriptors and transmits + * data to media stack. + * The function takes ownership of the received descriptors and is responsible for freeing them. + * + * \return none + * \param stream pointer to listener stream context + * \param desc array of avtp receive descriptors + * \param n array length + */ +void avtp_61883_4_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + struct iec_61883_hdr *iec_hdr; + struct iec_61883_iidc_specific_hdr spec_hdr; + struct media_desc *media_desc[NET_RX_BATCH]; + unsigned int payload_size, media_n = 0, stats = 0; + int i, j, offset, data_offset; + u32 *current_ts; + + os_log(LOG_DEBUG, "enter stream(%p)\n", stream); + + for (i = 0; i < n; i++) { + iec_hdr = (struct iec_61883_hdr *)((char *)desc[i] + desc[i]->l4_offset); + + spec_hdr.u.raw = desc[i]->protocol_specific_header; + if (unlikely((spec_hdr.u.s.tag != IEC_61883_PSH_TAG_CIP_INCLUDED) + || (iec_hdr->sph != IEC_61883_CIP_SPH_SOURCE_PACKETS) + || (iec_hdr->qpc != 0) + || (iec_hdr->fmt != IEC_61883_CIP_FMT_4))) { + stream->stats.format_err++; + + net_rx_free(&desc[i]->desc); + + /* FIXME we should indicate packets were lost */ + continue; /* Should never happen if stream _is_ 61883-4 */ + } + + if (unlikely(IEC_61883_4_CIP_DBS != iec_hdr->dbs) || unlikely(IEC_61883_4_CIP_FN != iec_hdr->fn)) { + stream->stats.format_err++; + os_log(LOG_ERR,"61883-4 frame stride mismatch: expected DBS = %d, FN = %d but in-stream DBS = %d, FN = %d\n", IEC_61883_4_CIP_DBS, IEC_61883_4_CIP_FN, iec_hdr->dbs, iec_hdr->fn); + } + + if (!(desc[i]->flags & AVTP_TIMESTAMP_INVALID)) { + stream->stats.format_err++; + os_log(LOG_ERR,"61883-4 packet format mismatch: AVTP timestamp valid bit set but shouldn't\n"); + } + + payload_size = desc[i]->l4_len - sizeof(struct iec_61883_hdr); + if ((payload_size % IEC_61883_4_SP_SIZE) != 0) { + stream->stats.format_err++; + os_log(LOG_ERR,"61883-4 payload size invalid: %d instead of %d\n", payload_size, IEC_61883_4_SP_SIZE); + } + + /* FIXME The code below works but is very sensitive to operation order, because the source and destination structs + * actually point to the same memory area. Need to make it safer to avoid overwriting a valid field by mistake. + */ + media_desc[media_n] = (struct media_desc *)desc[i]; + media_desc[media_n]->l2_offset = desc[i]->l4_offset + sizeof(struct iec_61883_hdr); + current_ts = (u32 *)((char *)media_desc[i] + media_desc[i]->l2_offset); + media_desc[media_n]->len = payload_size; + media_desc[media_n]->bytes_lost = 0; // FIXME compute value based on dbc + media_desc[media_n]->flags = desc[i]->flags & (AVTP_MEDIA_CLOCK_RESTART | AVTP_PACKET_LOST | AVTP_TIMESTAMP_UNCERTAIN); + media_desc[media_n]->ts = desc[i]->desc.ts; // This is actually a copy onto itself. Kept for clarity... + + desc[i]->avtp_timestamp = ntohl(*current_ts); + if (!stats) { + stats = 1; + avtp_latency_stats(stream, desc[i]); + } + + j = 0; + data_offset = 0; + for (offset = 0; offset < payload_size; offset += IEC_61883_4_SP_SIZE) { + media_desc[media_n]->avtp_ts[j].offset = data_offset; + media_desc[media_n]->avtp_ts[j].flags = media_desc[media_n]->flags; + media_desc[media_n]->avtp_ts[j].val = ntohl(*current_ts); + current_ts += IEC_61883_4_SP_SIZE / sizeof(u32); + j++; + data_offset += IEC_61883_4_SP_PAYLOAD_SIZE; + } + media_desc[media_n]->n_ts = j; + + media_n++; + } + + stream_media_tx(stream, media_desc, media_n); +} + +void avtp_61883_5_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + +} + +/** Handles reception of 61883-6 avtp packets + * + * Does protocol validation and decapsulation, converts avtp descriptors to media descriptors and transmits + * data to media stack. + * The function takes ownership of the received descriptors and is responsible for freeing them. + * + * \return none + * \param stream pointer to listener stream context + * \param desc array of avtp receive descriptors + * \param n array length + */ +void avtp_61883_6_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + struct iec_61883_hdr *iec_hdr; + struct iec_61883_iidc_specific_hdr spec_hdr; + struct avdecc_format_iec61883_6_t *iec61883_6; + struct media_desc *media_desc[NET_RX_BATCH]; + unsigned int media_n = 0; + unsigned int stats = 0; + int i; + unsigned int syt_interval = (1 << stream->subtype_data.iec61883_6.syt_interval_ln2); + unsigned int syt_interval_mod = syt_interval - 1; + + os_log(LOG_DEBUG, "enter stream(%p)\n", stream); + + for (i = 0; i < n; i++) { + + iec_hdr = (struct iec_61883_hdr *)((char *)desc[i] + desc[i]->l4_offset); + + spec_hdr.u.raw = desc[i]->protocol_specific_header; + if (unlikely((spec_hdr.u.s.tag != IEC_61883_PSH_TAG_CIP_INCLUDED) + || (iec_hdr->sph == IEC_61883_CIP_SPH_SOURCE_PACKETS) + || (iec_hdr->qpc != 0) + || (iec_hdr->fmt != IEC_61883_CIP_FMT_6))) { + stream->stats.format_err++; + + net_rx_free(&desc[i]->desc); + + /* FIXME we should indicate packets were lost */ + continue; // Should never happen if stream _is_ 61883-6 + } + + if (unlikely(iec_hdr->fdf_u.raw == IEC_61883_6_FDF_NODATA)) { + stream->stats.format_err++; + + net_rx_free(&desc[i]->desc); + + /* FIXME we should indicate packets were lost */ + continue; + } + + iec61883_6 = &stream->format.u.s.subtype_u.iec61883.format_u.iec61883_6; + if (unlikely(iec61883_6->dbs != iec_hdr->dbs)) { + stream->stats.format_err++; + os_log(LOG_ERR,"Frame stride mismatch: configured = %d but in-stream = %d\n", avdecc_fmt_sample_stride(&stream->format), (iec_hdr->dbs == 0?256:iec_hdr->dbs) << 2); + } + + /* This should only happen if there is a discontinuity in the dbc */ + if (unlikely((u8)(stream->subtype_data.iec61883_6.syt_count - iec_hdr->dbc) >= syt_interval)) { + stream->stats.format_err++; + + /* offset = mod(SYT_INTERVAL - mod(dbc, SYT_INTERVAL), SYT_INTERVAL) */ + stream->subtype_data.iec61883_6.syt_count = iec_hdr->dbc + ((syt_interval - (iec_hdr->dbc & syt_interval_mod)) & syt_interval_mod); + } + + if (unlikely(iec61883_6->fdf_u.fdf.sfc != iec_hdr->fdf_u.fdf.sfc)) { + stream->stats.format_err++; + os_log(LOG_ERR,"Sampling rate mismatch: configured = %d but in-stream = %d\n", avdecc_fmt_sample_rate(&stream->format), avtp_61883_6_sampling_freq[iec_hdr->fdf_u.fdf.sfc]); + } + + if (unlikely(iec61883_6->fdf_u.fdf.evt != iec_hdr->fdf_u.fdf.evt)) { + stream->stats.format_err++; + os_log(LOG_ERR,"Sample format mismatch: configured fdf_evt = %d but in-stream = %d\n", iec61883_6->fdf_u.fdf.evt, iec_hdr->fdf_u.fdf.evt); + } + + switch (iec_hdr->fdf_u.fdf.evt) { + case IEC_61883_6_FDF_EVT_AM824: + case IEC_61883_6_FDF_EVT_FLOATING: + case IEC_61883_6_FDF_EVT_INT32: + //TODO There may be more than one sample format per frame. For now we only accept AM824 frames with mbla samples + + /* FIXME The code below works but is very sensitive to operation order, because the source and destination structs + * actually point to the same memory area. Need to make it safer to avoid overwriting a valid field by mistake. + */ + media_desc[media_n] = (struct media_desc *)desc[i]; + media_desc[media_n]->l2_offset = desc[i]->l4_offset + sizeof(struct iec_61883_hdr); + media_desc[media_n]->len = desc[i]->l4_len - sizeof(struct iec_61883_hdr); + media_desc[media_n]->bytes_lost = 0; // FIXME compute value based on dbc + media_desc[media_n]->flags = desc[i]->flags & (AVTP_MEDIA_CLOCK_RESTART | AVTP_PACKET_LOST | AVTP_TIMESTAMP_UNCERTAIN); + media_desc[media_n]->ts = desc[i]->desc.ts; // This is actually a copy onto itself. Kept for clarity... + + if (desc[i]->flags & AVTP_TIMESTAMP_INVALID) { + if (media_desc[media_n]->flags) { + media_desc[media_n]->avtp_ts[0].offset = 0; + media_desc[media_n]->avtp_ts[0].flags = AVTP_FLAGS_TO_MEDIA_DESC(AVTP_TIMESTAMP_INVALID); + media_desc[media_n]->n_ts = 1; + } else + media_desc[media_n]->n_ts = 0; + } else { + media_desc[media_n]->avtp_ts[0].offset = (u8)(stream->subtype_data.iec61883_6.syt_count - iec_hdr->dbc) * iec_hdr->dbs << 2; + media_desc[media_n]->avtp_ts[0].flags = 0; + media_desc[media_n]->avtp_ts[0].val = desc[i]->avtp_timestamp; + media_desc[media_n]->n_ts = 1; + + stream->subtype_data.iec61883_6.syt_count += syt_interval; + + + if (!stats) { + stats = 1; + avtp_latency_stats(stream, desc[i]); + } + } + + media_n++; + + break; + + case IEC_61883_6_FDF_EVT_PACKED: + //Not supported according to P1722a/D9 section 6.3.5 + default: + stream->stats.format_err++; + net_rx_free(&desc[i]->desc); + break; + } + } + + stream_media_tx(stream, media_desc, media_n); +} + +void avtp_61883_7_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + +} + +void avtp_iidc_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + +} + + +void avtp_61883_2_net_tx(struct stream_talker *stream) +{ + +} + +void avtp_61883_3_net_tx(struct stream_talker *stream) +{ + +} + +static unsigned int avtp_61883_4_prepare_header(struct avtp_data_hdr *hdr, const struct avdecc_format *format) +{ + struct iec_61883_hdr *iec_hdr; + struct iec_61883_iidc_specific_hdr spec_hdr; + + spec_hdr.u.s.tag = IEC_61883_PSH_TAG_CIP_INCLUDED; + spec_hdr.u.s.channel = IEC_61883_PSH_CHANNEL_AVB_NETWORK; + spec_hdr.u.s.tcode = IEC_61883_PSH_TCODE_AVTP; + spec_hdr.u.s.sy = 0; + + avtp_data_header_set_protocol_specific(hdr, spec_hdr.u.raw); + avtp_data_header_set_timestamp_invalid(hdr); + + iec_hdr = (struct iec_61883_hdr *)(hdr + 1); + iec_hdr->rsvd1 = 0; + iec_hdr->sid = IEC_61883_CIP_SID_AVB_NETWORK; + iec_hdr->dbs = IEC_61883_4_CIP_DBS; + iec_hdr->fn = IEC_61883_4_CIP_FN; + iec_hdr->qpc = 0; + iec_hdr->sph = IEC_61883_4_CIP_SPH; + iec_hdr->rsvd2 = 0; + iec_hdr->dbc = 0; + iec_hdr->rsvd3 = IEC_61883_CIP_2ND_QUAD_ID; + iec_hdr->fmt = format->u.s.subtype_u.iec61883.fmt; + iec_hdr->fdf_u.raw = 0; + iec_hdr->syt = 0; + + return sizeof(struct iec_61883_hdr); +} + +/** Handles transmission of 61883-4 avtp packets + * + * Reads data from media stack, converts media descriptors to network descriptors, including protocol encapsulation, + * and transmit packets. AVTP timestamps are read from media clock generation layer. + * + * \return none + * \param stream pointer to talker stream context + */ +void avtp_61883_4_net_tx(struct stream_talker *stream) +{ + u32 i, j; + int n_now; + void *buf; + struct media_rx_desc *media_desc_array[NET_TX_BATCH], *media_desc; + struct net_tx_desc *net_desc; + u32 ts[TS_TX_BATCH]; + struct iec_61883_hdr *iec_hdr = stream->subtype_data.iec61883.iec_hdr; + struct avtp_data_hdr *data_hdr; + int rc; + int ts_n; + int desc_ts_n; + unsigned int ts_batch, frames_in_packet; + unsigned int offset, ts_now; + unsigned int flags = 0; + unsigned int partial; + unsigned int alignment_ts = 0; + + /* Get MPEG2-TS data from media stack */ + if (unlikely((rc = stream_media_rx(stream, media_desc_array, ts, &flags, &alignment_ts)) < 0)) { + goto media_rx_fail; + } + + /* The media clock outputs timestamps at a constant (max) rate, but less may be required depending on + * instantaneous media bandwidth, so make sure to always fetch max timestamps. */ + ts_batch = stream->tx_batch * stream->frames_per_packet; + + if (ts_batch > TS_TX_BATCH) { + os_log(LOG_ERR, "stream(%p) Unexpected number of timestamps needed(%u), clamping down to %u\n", stream, ts_batch, TS_TX_BATCH); + ts_batch = TS_TX_BATCH; + } + + stream->consumer.gptp_current = stream->gptp_current; + ts_n = media_clock_gen_get_ts(&stream->consumer, ts, ts_batch, &flags, alignment_ts); + + if (ts_n != ts_batch) { + stream->stats.clock_err++; + goto media_clock_fail; + } + + if (flags & MCG_FLAGS_RESET) { + avtp_data_header_toggle_mcr(stream->avtp_hdr); + stream->ts_media_prev = ts[0]; + } + + stream->stats.clock_rx += ts_n; + + n_now = rc; + + ts_n = 0; + i = 0; + while (i < n_now) { + media_desc = media_desc_array[i]; + net_desc = &media_desc->net; + + partial = net_desc->flags & NET_TX_FLAGS_PARTIAL; + net_desc->l2_offset -= stream->header_len; + net_desc->len += stream->header_len; + net_desc->flags = 0; + + buf = NET_DATA_START(net_desc); + + os_memcpy(buf, stream->header_template, stream->header_len); + if (unlikely(partial)) + frames_in_packet = (net_desc->len - stream->header_len) / avdecc_fmt_sample_stride(&stream->format); + else + frames_in_packet = stream->frames_per_packet; + + + desc_ts_n = 0; + for (j = 0; j < frames_in_packet; j++) { + offset = j * IEC_61883_4_SP_SIZE; + + if ((desc_ts_n < media_desc->ts_n) && (media_desc->avtp_ts[desc_ts_n].offset == offset)) { + ts_now = media_desc->avtp_ts[desc_ts_n].val; + stream->ts_media_prev = ts_now; + desc_ts_n++; + } else + ts_now = stream->ts_media_prev; + + *(u32 *)((char *)buf + stream->header_len + offset) = htonl(ts_now); + + ts_n++; + } + + stream->avtp_hdr->sequence_num++; + i++; + + if (unlikely(partial)) { + data_hdr = (struct avtp_data_hdr *)((char *)buf + ((char *)stream->avtp_hdr - (char *)stream->header_template)); + data_hdr->stream_data_length = htons(ntohs(data_hdr->stream_data_length) - (stream->frames_per_packet - frames_in_packet)*avdecc_fmt_sample_stride(&stream->format)); + + stream->stats.partial++; + stream->media_count = 0; + iec_hdr->dbc = 0; + + net_free_multi((void **)&media_desc_array[i], n_now - i); + stream->stats.tx_err += n_now - i; + break; + } else { + stream->media_count += frames_in_packet; + + /* Always send an integer number of source packets */ + iec_hdr->dbc += frames_in_packet << IEC_61883_4_CIP_FN; + } + } + + if (stream_net_tx(stream, media_desc_array, i)) + goto transmit_fail; + + return; + +media_clock_fail: + net_free_multi((void **)media_desc_array, rc); + +media_rx_fail: + /* Reset the avtp stream since it was reset by the media stack */ + stream->media_count = 0; + iec_hdr->dbc = 0; + + return; + +transmit_fail: + return; + +} + +void avtp_61883_5_net_tx(struct stream_talker *stream) +{ + +} + +static unsigned int avtp_61883_6_prepare_header(struct avtp_data_hdr *hdr, const struct avdecc_format *format) +{ + struct iec_61883_hdr *iec_hdr; + struct iec_61883_iidc_specific_hdr spec_hdr; + const struct avdecc_format_iec61883_6_t *iec61883_6 = &format->u.s.subtype_u.iec61883.format_u.iec61883_6; + + spec_hdr.u.s.tag = IEC_61883_PSH_TAG_CIP_INCLUDED; + spec_hdr.u.s.channel = IEC_61883_PSH_CHANNEL_AVB_NETWORK; + spec_hdr.u.s.tcode = IEC_61883_PSH_TCODE_AVTP; + spec_hdr.u.s.sy = 0; + + avtp_data_header_set_protocol_specific(hdr, spec_hdr.u.raw); + + iec_hdr = (struct iec_61883_hdr *)(hdr + 1); + iec_hdr->rsvd1 = 0; + iec_hdr->sid = IEC_61883_CIP_SID_AVB_NETWORK; + iec_hdr->dbs = iec61883_6->dbs; + iec_hdr->fn = 0; + iec_hdr->qpc = 0; + iec_hdr->sph = 0; + iec_hdr->rsvd2 = 0; + iec_hdr->dbc = 0; + iec_hdr->rsvd3 = IEC_61883_CIP_2ND_QUAD_ID; + iec_hdr->fmt = format->u.s.subtype_u.iec61883.fmt; + iec_hdr->fdf_u.raw = iec61883_6->fdf_u.raw; + iec_hdr->syt = IEC_61883_CIP_SYT_NO_TSTAMP; + + return sizeof(struct iec_61883_hdr); +} + + +/** Resets AVTP stream timestamp counters. + * + * Resets the AVTP 61883-6 timestamp counters so the placement of timestamps inside the packets can start again from a clean state. + * \return none + * \param stream pointer to talker stream context + * \param iec_hdr pointer to IEC header context + */ +static inline void avtp_61883_6_stream_ts_reset(struct stream_talker *stream, struct iec_61883_hdr *iec_hdr) +{ + stream->media_count = 0; + stream->frame_with_ts = 0; + stream->ts_n = 0; + iec_hdr->dbc = 0; +} + +/** Handles transmission of 61883-6 avtp packets + * + * Reads data from media stack, converts media descriptors to network descriptors, including protocol encapsulation, + * and transmit packets. AVTP timestamps are read from media clock generation layer. + * + * \return none + * \param stream pointer to talker stream context + */ +void avtp_61883_6_net_tx(struct stream_talker *stream) +{ + u32 get_ts, i; + int n_now; + void *buf; + struct media_rx_desc *media_desc_array[NET_TX_BATCH], *media_desc; + struct net_tx_desc *net_desc; + u32 ts[NET_TX_BATCH]; + struct iec_61883_hdr *iec_hdr = stream->subtype_data.iec61883.iec_hdr; + struct avtp_data_hdr *data_hdr; + unsigned int syt_interval_ln2; + int rc; + int ts_n; + unsigned int ts_batch, frames_in_packet; + unsigned int flags = 0; + unsigned int partial; + unsigned int alignment_ts = 0; + + // Get Audio media from media stack + if (unlikely((rc = stream_media_rx(stream, media_desc_array, ts, &flags, &alignment_ts)) < 0)) { + goto media_rx_fail; + } + + syt_interval_ln2 = stream->subtype_data.iec61883.syt_interval_ln2; + /* The last frame with a timestamp was (stream->media_count - stream->frame_with_ts) frames ago. + * We just received rc * stream->frames_per_packet new frames. + * So the number of necessary timestamps is the sum of these 2 values, divided by the number of frames per timestamps, rounded up to the nearest integer. + * IOW: ts_batch = ceil(total_frames_since_ts / syt_interval), total_frames_since_ts includes this batch. + */ + ts_batch = ((stream->media_count - stream->frame_with_ts) + rc * stream->frames_per_packet + (1 << syt_interval_ln2) - 1) >> syt_interval_ln2; + + if (ts_batch > NET_TX_BATCH) { + os_log(LOG_ERR, "stream(%p) Unexpected number of timestamps needed(%u), clamping down to %u\n", stream, ts_batch, NET_TX_BATCH); + ts_batch = NET_TX_BATCH; + } + + stream->consumer.gptp_current = stream->gptp_current; + ts_n = media_clock_gen_get_ts(&stream->consumer, ts, ts_batch, &flags, alignment_ts); + if (unlikely(flags & MCG_FLAGS_DO_ALIGN)) { + /* skip time per packet update, if there is a timestamp discontinuity */ + stream->ts_n = 0; +// os_log(LOG_ERR, "talker(%p) offset %u write %u read %u ts_batch %d ts_n %d\n", stream, stream->consumer.offset, stream->consumer.grid->write_index, stream->consumer.read_index, ts_batch, ts_n); + } + + if (ts_n != ts_batch) { + stream->stats.clock_err++; + goto media_clock_fail; + } + + if (flags & MCG_FLAGS_RESET) + avtp_data_header_toggle_mcr(stream->avtp_hdr); + + stream->stats.clock_rx += ts_n; + + n_now = rc; + + ts_n = 0; + i = 0; + while (i < n_now) { + media_desc = media_desc_array[i]; + net_desc = &media_desc->net; + + partial = net_desc->flags & NET_TX_FLAGS_PARTIAL; + net_desc->l2_offset -= stream->header_len; + net_desc->len += stream->header_len; + net_desc->flags = NET_TX_FLAGS_TS; + + buf = NET_DATA_START(net_desc); + if (unlikely(partial)) + frames_in_packet = (net_desc->len - stream->header_len) / avdecc_fmt_sample_stride(&stream->format); + else + frames_in_packet = stream->frames_per_packet; + + if (stream->media_count == stream->frame_with_ts) { + stream->ts_launch = ts[ts_n] - stream->max_transit_time; + + /* Update time_per_packet based on actual data rate */ + if (stream->ts_n) + stream->time_per_packet = ((ts[ts_n] - stream->ts_last) * stream->frames_per_packet) >> syt_interval_ln2; + } else + stream->ts_launch += stream->time_per_packet; + + net_desc->ts = stream->ts_launch; + + stream->media_count += frames_in_packet; + + get_ts = ((int)stream->media_count - (int)stream->frame_with_ts) > 0 ? 1 : 0; + if (get_ts) { + + avtp_data_header_set_timestamp(stream->avtp_hdr, ts[ts_n]); + + stream->ts_last = ts[ts_n]; + stream->ts_n++; + ts_n++; + stream->frame_with_ts += (1 << syt_interval_ln2); + } else + avtp_data_header_set_timestamp_invalid(stream->avtp_hdr); + + os_memcpy(buf, stream->header_template, stream->header_len); + + stream->avtp_hdr->sequence_num++; + i++; + + if (unlikely(partial)) { + data_hdr = (struct avtp_data_hdr *)((char *)buf + ((char *)stream->avtp_hdr - (char *)stream->header_template)); + data_hdr->stream_data_length = htons(ntohs(data_hdr->stream_data_length) - (stream->frames_per_packet - frames_in_packet)*avdecc_fmt_sample_stride(&stream->format)); + + stream->stats.partial++; + avtp_61883_6_stream_ts_reset(stream, iec_hdr); + + net_free_multi((void **)&media_desc_array[i], n_now - i); + stream->stats.tx_err += n_now - i; + break; + } else + iec_hdr->dbc += frames_in_packet; + } + + if (stream_net_tx(stream, media_desc_array, i)) + goto transmit_fail; + + return; + +media_clock_fail: + net_free_multi((void **)media_desc_array, rc); + +media_rx_fail: + /* Reset the avtp stream since it was reset by the media stack */ + avtp_61883_6_stream_ts_reset(stream, iec_hdr); + + return; + +transmit_fail: + return; +} + +void avtp_61883_7_net_tx(struct stream_talker *stream) +{ + +} + +void avtp_iidc_net_tx(struct stream_talker *stream) +{ + +} diff --git a/avtp/61883_iidc.h b/avtp/61883_iidc.h new file mode 100644 index 0000000..7094e66 --- /dev/null +++ b/avtp/61883_iidc.h @@ -0,0 +1,41 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief IEC 61883/IIDC protocol handling functions + @details +*/ + +#ifndef _61883_IIDC_H_ +#define _61883_IIDC_H_ + +#include "common/net.h" + +#include "stream.h" + +int listener_stream_61883_iidc_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags); +int talker_stream_61883_iidc_check(struct stream_talker *stream, struct avdecc_format const *format, + struct ipc_avtp_connect *ipc); + +void avtp_61883_2_net_rx(struct stream_listener *, struct avtp_rx_desc **, unsigned int n); +void avtp_61883_3_net_rx(struct stream_listener *, struct avtp_rx_desc **, unsigned int n); +void avtp_61883_4_net_rx(struct stream_listener *, struct avtp_rx_desc **, unsigned int n); +void avtp_61883_5_net_rx(struct stream_listener *, struct avtp_rx_desc **, unsigned int n); +void avtp_61883_6_net_rx(struct stream_listener *, struct avtp_rx_desc **, unsigned int n); +void avtp_61883_7_net_rx(struct stream_listener *, struct avtp_rx_desc **, unsigned int n); +void avtp_iidc_net_rx(struct stream_listener *, struct avtp_rx_desc **, unsigned int n); + +void avtp_61883_2_net_tx(struct stream_talker *); +void avtp_61883_3_net_tx(struct stream_talker *); +void avtp_61883_4_net_tx(struct stream_talker *); +void avtp_61883_5_net_tx(struct stream_talker *); +void avtp_61883_6_net_tx(struct stream_talker *); +void avtp_61883_7_net_tx(struct stream_talker *); +void avtp_iidc_net_tx(struct stream_talker *); + +#endif /* _61883_IIDC_H_ */ diff --git a/avtp/aaf.c b/avtp/aaf.c new file mode 100644 index 0000000..9c486bd --- /dev/null +++ b/avtp/aaf.c @@ -0,0 +1,551 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP Audio Format (AAF) handling functions + @details +*/ + +#ifdef CFG_AVTP_1722A + +#include "common/log.h" + +#include "avtp.h" +#include "aaf.h" + +static void aaf_net_tx(struct stream_talker *stream) +{ + u32 get_ts, i; + int n_now; + void *buf; + struct media_rx_desc *media_desc_array[NET_TX_BATCH], *media_desc; + struct net_tx_desc *net_desc; + u32 ts[NET_TX_BATCH]; + int rc; + int ts_n; + unsigned int ts_batch; + unsigned int flags = 0; + unsigned int partial; + unsigned int sparse = stream->subtype_data.aaf.sparse; + unsigned int alignment_ts = 0; + + // Get Audio media from media stack + if (unlikely((rc = stream_media_rx(stream, media_desc_array, ts, &flags, &alignment_ts)) < 0)) { + goto media_rx_fail; + } + + if (sparse) { + /* The last packet with a timestamp was (stream->subtype_data.aaf.tx_count - stream->frame_with_ts) packets ago. + * We just received rc new packets. + * So the number of necessary timestamps is the sum of these 2 values, divided by the number of packets per timestamps, rounded up to the nearest integer. + * IOW: ts_batch = ceil(total_packets_since_ts / pkts_per_ts), total_packets_since_ts includes this batch. + */ + ts_batch = ((stream->subtype_data.aaf.tx_count - stream->frame_with_ts) + rc + AAF_PACKETS_PER_TIMESTAMP_SPARSE - 1) / AAF_PACKETS_PER_TIMESTAMP_SPARSE; + if (ts_batch > NET_TX_BATCH) { + os_log(LOG_ERR, "stream(%p) Unexpected number of timestamps needed(%u), clamping down to %u\n", stream, ts_batch, NET_TX_BATCH); + ts_batch = NET_TX_BATCH; + } + } else + ts_batch = rc; + + stream->consumer.gptp_current = stream->gptp_current; + ts_n = media_clock_gen_get_ts(&stream->consumer, ts, ts_batch, &flags, alignment_ts); + if (unlikely(flags & MCG_FLAGS_DO_ALIGN)) { + /* skip time per packet update, if there is a timestamp discontinuity */ + stream->ts_n = 0; +// os_log(LOG_ERR, "talker(%p) offset %u write %u read %u ts_batch %d ts_n %d\n", stream, stream->consumer.offset, stream->consumer.grid->write_index, stream->consumer.read_index, ts_batch, ts_n); + } + + if (ts_n != ts_batch) { + stream->stats.clock_err++; + goto media_clock_fail; + } + + if (ts_n) { + u32 delay = ts[0] - stream->gptp_current; + + if ( (delay < stream->max_transit_time) || + (delay > (stream_presentation_offset(stream->max_transit_time, stream->latency) + stream->latency))) { + stream->stats.clock_invalid++; +// goto media_clock_fail; + } + } + + if (flags & MCG_FLAGS_RESET) + avtp_data_header_toggle_mcr(stream->avtp_hdr); + + stream->stats.clock_rx += ts_n; + + n_now = rc; + + ts_n = 0; + i = 0; + while (i < n_now) { + media_desc = media_desc_array[i]; + net_desc = &media_desc->net; + + partial = net_desc->flags & NET_TX_FLAGS_PARTIAL; + net_desc->l2_offset -= stream->header_len; + net_desc->len += stream->header_len; + net_desc->flags = NET_TX_FLAGS_TS; + + buf = NET_DATA_START(net_desc); + + if (unlikely(sparse)) { + if (stream->subtype_data.aaf.tx_count == stream->frame_with_ts) { + stream->ts_launch = ts[ts_n] - stream->max_transit_time; + + /* Update time_per_packet based on actual data rate */ + if (stream->ts_n) + stream->time_per_packet = (ts[ts_n] - stream->ts_last) / AAF_PACKETS_PER_TIMESTAMP_SPARSE; + } else + stream->ts_launch += stream->time_per_packet; + + stream->media_count += stream->frames_per_packet; + stream->subtype_data.aaf.tx_count++; + + get_ts = ((int)stream->subtype_data.aaf.tx_count - (int)stream->frame_with_ts) > 0 ? 1 : 0; + + if (get_ts) { + + avtp_data_header_set_timestamp(stream->avtp_hdr, ts[ts_n]); + + stream->ts_last = ts[ts_n]; + stream->ts_n++; + ts_n++; + stream->frame_with_ts += AAF_PACKETS_PER_TIMESTAMP_SPARSE; + } else + avtp_data_header_set_timestamp_invalid(stream->avtp_hdr); + } else { + stream->ts_launch = ts[ts_n] - stream->max_transit_time; + + avtp_data_header_set_timestamp(stream->avtp_hdr, ts[ts_n]); + + stream->ts_n++; + ts_n++; + + stream->media_count += stream->frames_per_packet; + } + + net_desc->ts = stream->ts_launch; + + os_memcpy(buf, stream->header_template, stream->header_len); + + stream->avtp_hdr->sequence_num++; + + if (unlikely(partial)) { + net_free_multi((void **)&media_desc_array[i], n_now - i); + stream->stats.tx_err += n_now - i; + stream->stats.partial++; + break; + } + + i++; + } + + if (stream_net_tx(stream, media_desc_array, i)) + goto transmit_fail; + + return; + +media_clock_fail: + net_free_multi((void **)media_desc_array, rc); + +media_rx_fail: + /* Reset the avtp stream since it was reset by the media stack */ + stream->media_count = 0; + stream->ts_n = 0; + stream->subtype_data.aaf.tx_count = 0; + stream->frame_with_ts = 0; + + return; + +transmit_fail: + return; + +} + +static void aaf_pcm_prepare_header(struct avtp_aaf_pcm_hdr *hdr, const struct avdecc_format *format) +{ + const struct avdecc_format_aaf_t *aaf_fmt = &format->u.s.subtype_u.aaf; + const struct avdecc_format_aaf_pcm_t *pcm_fmt = &aaf_fmt->format_u.pcm; + + hdr->nsr = aaf_fmt->nsr; + AAF_PCM_CHANNELS_PER_FRAME_SET(hdr, AVDECC_FMT_AAF_PCM_CHANNELS_PER_FRAME(format)); + hdr->bit_depth = pcm_fmt->bit_depth; +} + +static void aaf_aes3_prepare_header(struct avtp_aaf_aes3_hdr *hdr, const struct avdecc_format *format) +{ + const struct avdecc_format_aaf_t *aaf_fmt = &format->u.s.subtype_u.aaf; + const struct avdecc_format_aaf_aes3_t *aes3_fmt = &aaf_fmt->format_u.aes3; + + hdr->nfr = aaf_fmt->nsr; + AAF_AES3_STREAMS_PER_FRAME_SET(hdr, AVDECC_FMT_AAF_AES3_STREAMS_PER_FRAME(format)); + hdr->aes3_dt_ref = aes3_fmt->aes3_dt_ref; + AAF_AES3_DATA_TYPE_SET(hdr, aes3_fmt->aes3_data_type); +} + +static unsigned int aaf_prepare_header(struct avtp_aaf_hdr *hdr, const struct avdecc_format *format, void *stream_id, unsigned int sparse) +{ + const struct avdecc_format_aaf_t *aaf_fmt = &format->u.s.subtype_u.aaf; + + /* AVTP stream common fields */ + hdr->subtype = AVTP_SUBTYPE_AAF; + hdr->version = AVTP_VERSION_0; + hdr->sv = 1; + + copy_64(&hdr->stream_id, stream_id); + + /* AAF fields */ + hdr->format = aaf_fmt->format; + + if (sparse) + hdr->sp = AAF_SP_SPARSE; + else + hdr->sp = AAF_SP_NORMAL; + + hdr->evt = 0; + + if (avdecc_format_is_aaf_pcm(format)) + aaf_pcm_prepare_header((struct avtp_aaf_pcm_hdr *)hdr, format); + else + aaf_aes3_prepare_header((struct avtp_aaf_aes3_hdr *)hdr, format); + + return sizeof(struct avtp_aaf_hdr); +} + +static void aaf_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + struct avtp_aaf_hdr *aaf_hdr; + struct media_desc *media_desc[NET_RX_BATCH]; + unsigned int media_n = 0; + unsigned int stats = 0; + u32 *hdr; + int i; + + os_log(LOG_DEBUG, "enter stream(%p)\n", stream); + + for (i = 0; i < n; i++) { + + aaf_hdr = (struct avtp_aaf_hdr *)((char *)desc[i] + desc[i]->desc.l3_offset); + + hdr = (u32 *)&aaf_hdr->format; + + if (unlikely(((hdr[0] & stream->subtype_data.aaf.hdr_mask[0]) != stream->subtype_data.aaf.hdr[0]) || + ((hdr[1] & stream->subtype_data.aaf.hdr_mask[1]) != stream->subtype_data.aaf.hdr[1]))) { + stream->stats.format_err++; + + net_rx_free(&desc[i]->desc); + + /* FIXME we should indicate packets were lost */ + continue; + } + + /* FIXME The code below works but is very sensitive to operation order, because the source and destination structs + * actually point to the same memory area. Need to make it safer to avoid overwriting a valid field by mistake. + */ + media_desc[media_n] = (struct media_desc *)desc[i]; + media_desc[media_n]->l2_offset = desc[i]->l4_offset; + media_desc[media_n]->len = desc[i]->l4_len; + media_desc[media_n]->bytes_lost = 0; // FIXME compute value based on dbc + media_desc[media_n]->flags = desc[i]->flags & (AVTP_MEDIA_CLOCK_RESTART | AVTP_PACKET_LOST | AVTP_TIMESTAMP_UNCERTAIN); + media_desc[media_n]->ts = desc[i]->desc.ts; // This is actually a copy onto itself. Kept for clarity... + + if (desc[i]->flags & AVTP_TIMESTAMP_INVALID) { + if (media_desc[media_n]->flags) { + media_desc[media_n]->avtp_ts[0].offset = 0; + media_desc[media_n]->avtp_ts[0].flags = AVTP_FLAGS_TO_MEDIA_DESC(AVTP_TIMESTAMP_INVALID); + media_desc[media_n]->n_ts = 1; + } else + media_desc[media_n]->n_ts = 0; + } else { + media_desc[media_n]->avtp_ts[0].offset = 0; + media_desc[media_n]->avtp_ts[0].flags = 0; + media_desc[media_n]->avtp_ts[0].val = desc[i]->avtp_timestamp; + media_desc[media_n]->n_ts = 1; + + if (!stats) { + stats = 1; + avtp_latency_stats(stream, desc[i]); + } + } + + media_n++; + } + + stream_media_tx(stream, media_desc, media_n); +} + +static int aaf_pcm_check_format(const struct avdecc_format *format) +{ + const struct avdecc_format_aaf_pcm_t *pcm_fmt = &format->u.s.subtype_u.aaf.format_u.pcm; + int rc = GENAVB_SUCCESS; + + /* IEEE1722-2016 7.3.4 */ + switch (format->u.s.subtype_u.aaf.format) { + case AAF_FORMAT_USER: + break; + + case AAF_FORMAT_FLOAT_32BIT: + if (pcm_fmt->bit_depth != 32) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + break; + + case AAF_FORMAT_INT_32BIT: + case AAF_FORMAT_INT_24BIT: + case AAF_FORMAT_INT_16BIT: + if ((pcm_fmt->bit_depth <= 0) || (pcm_fmt->bit_depth > avdecc_fmt_bits_per_sample(format))) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + break; + + default: + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + break; + } + + + /* 1722rev1-2016 8.3.2 */ + if ((AVDECC_FMT_AAF_PCM_CHANNELS_PER_FRAME(format) <= 0) || (AVDECC_FMT_AAF_PCM_CHANNELS_PER_FRAME(format) > CFG_AVTP_AAF_PCM_MAX_CHANNELS)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + + if ((AVDECC_FMT_AAF_PCM_SAMPLES_PER_FRAME(format) <= 0) || (AVDECC_FMT_AAF_PCM_SAMPLES_PER_FRAME(format) > CFG_AVTP_AAF_PCM_MAX_SAMPLES)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + + if (AVDECC_FMT_AAF_PCM_SAMPLES_PER_FRAME(format) * avdecc_fmt_sample_size(format) > AVTP_DATA_MTU - avdecc_fmt_hdr_size(format)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + +err_format: + return rc; +} + +static int aaf_aes3_check_format(const struct avdecc_format *format, unsigned int is_talker) +{ + const struct avdecc_format_aaf_aes3_t *aes3_fmt = &format->u.s.subtype_u.aaf.format_u.aes3; + int rc = GENAVB_SUCCESS; + + /* IEEE1722-2016 7.4.4 */ + switch (aes3_fmt->aes3_dt_ref) { + case AAF_AES3_DT_UNSPECIFIED: + case AAF_AES3_DT_PCM: + if (is_talker && aes3_fmt->aes3_data_type) { /* IEEE1722-2016 7.4.5 Table 16 */ + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + break; + + case AAF_AES3_DT_SMPTE338: + if (aes3_fmt->aes3_data_type > 0x1f) { /* IEEE1722-2016 7.4.4.2 */ + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + break; + + case AAF_AES3_DT_IEC61937: + if (aes3_fmt->aes3_data_type > 0x7f) { /* IEEE1722-2016 7.4.4.2 */ + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + break; + + case AAF_AES3_DT_VENDOR: + break; + + default: + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + break; + } + + /* 1722rev1-2016 8.4.2 */ + if ((AVDECC_FMT_AAF_AES3_STREAMS_PER_FRAME(format) <= 0) || (AVDECC_FMT_AAF_AES3_STREAMS_PER_FRAME(format) > CFG_AVTP_AAF_AES3_MAX_STREAMS)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + + /* 1722rev1-2016 I.2.1.3.2 */ + /* frames_per_frame is an uint8_t < CFG_AVTP_AAF_AES3_MAX_FRAMES (256) */ + if ((aes3_fmt->frames_per_frame <= 0)/* || (aes3_fmt->frames_per_frame > CFG_AVTP_AAF_AES3_MAX_FRAMES)*/) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + + if (aes3_fmt->frames_per_frame * avdecc_fmt_sample_size(format) > AVTP_DATA_MTU - avdecc_fmt_hdr_size(format)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + +err_format: + return rc; +} + +static int aaf_check_format(const struct avdecc_format *format, unsigned int is_talker) +{ + const struct avdecc_format_aaf_t *aaf_fmt = &format->u.s.subtype_u.aaf; + int rc; + + /* 1722rev1-2016 Table 11 and 15 */ + switch (aaf_fmt->nsr) { + default: + case AAF_NSR_USER_SPECIFIED: + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + break; + + case AAF_NSR_8000: + case AAF_NSR_16000: + case AAF_NSR_32000: + case AAF_NSR_44100: + case AAF_NSR_48000: + case AAF_NSR_88200: + case AAF_NSR_96000: + case AAF_NSR_176400: + case AAF_NSR_192000: + case AAF_NSR_24000: + break; + } + + if (avdecc_format_is_aaf_pcm(format)) + rc = aaf_pcm_check_format(format); + else if (avdecc_format_is_aaf_aes3(format)) + rc = aaf_aes3_check_format(format, is_talker); + else + rc = -GENAVB_ERR_STREAM_PARAMS; + +err_format: + return rc; +} + +static int listener_stream_aaf_init(struct stream_listener *stream) +{ + struct avdecc_format const *format = &stream->format; + const struct avdecc_format_aaf_t *aaf_fmt = &format->u.s.subtype_u.aaf; + struct avtp_aaf_hdr aaf_hdr, aaf_hdr_mask; + u32 *hdr, *hdr_mask; + + os_memset(&aaf_hdr, 0, sizeof(aaf_hdr)); + os_memset(&aaf_hdr_mask, 0, sizeof(aaf_hdr_mask)); + + /* Setup receive header pattern match */ + aaf_hdr.format = aaf_fmt->format; + aaf_hdr_mask.format = 0xff; + + if (avdecc_format_is_aaf_pcm(format)) { + const struct avdecc_format_aaf_pcm_t *pcm_fmt = &aaf_fmt->format_u.pcm; + struct avtp_aaf_pcm_hdr *pcm_hdr = (struct avtp_aaf_pcm_hdr *)&aaf_hdr; + struct avtp_aaf_pcm_hdr *pcm_hdr_mask = (struct avtp_aaf_pcm_hdr *)&aaf_hdr_mask; + + pcm_hdr->nsr = aaf_fmt->nsr; + pcm_hdr_mask->nsr = 0xf; + + AAF_PCM_CHANNELS_PER_FRAME_SET(pcm_hdr, AVDECC_FMT_AAF_PCM_CHANNELS_PER_FRAME(format)); + AAF_PCM_CHANNELS_PER_FRAME_SET(pcm_hdr_mask, 0x3ff); + + pcm_hdr->bit_depth = pcm_fmt->bit_depth; + pcm_hdr_mask->bit_depth = 0xff; + } else { + const struct avdecc_format_aaf_aes3_t *aes3_fmt = &aaf_fmt->format_u.aes3; + struct avtp_aaf_aes3_hdr *aes3_hdr = (struct avtp_aaf_aes3_hdr *)&aaf_hdr; + struct avtp_aaf_aes3_hdr *aes3_hdr_mask = (struct avtp_aaf_aes3_hdr *)&aaf_hdr_mask; + + aes3_hdr->nfr = aaf_fmt->nsr; + aes3_hdr_mask->nfr = 0xf; + + AAF_AES3_STREAMS_PER_FRAME_SET(aes3_hdr, AVDECC_FMT_AAF_AES3_STREAMS_PER_FRAME(format)); + AAF_AES3_STREAMS_PER_FRAME_SET(aes3_hdr_mask, 0x3ff); + + aes3_hdr->aes3_dt_ref = aes3_fmt->aes3_dt_ref; + aes3_hdr_mask->aes3_dt_ref = 0x7; + + AAF_AES3_DATA_TYPE_SET(aes3_hdr, aes3_fmt->aes3_data_type); + switch (aes3_fmt->aes3_dt_ref) { + case AAF_AES3_DT_UNSPECIFIED: + case AAF_AES3_DT_PCM: + AAF_AES3_DATA_TYPE_SET(aes3_hdr_mask, 0x0); + break; + default: + AAF_AES3_DATA_TYPE_SET(aes3_hdr_mask, 0xffff); + break; + } + + } + + hdr = (u32 *)&aaf_hdr.format; + hdr_mask = (u32 *)&aaf_hdr_mask.format; + + stream->subtype_data.aaf.hdr[0] = hdr[0]; + stream->subtype_data.aaf.hdr[1] = hdr[1]; + + stream->subtype_data.aaf.hdr_mask[0] = hdr_mask[0]; + stream->subtype_data.aaf.hdr_mask[1] = hdr_mask[1]; + + stream->net_rx = aaf_net_rx; + + return 0; +} + +int listener_stream_aaf_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags) +{ + int rc; + + rc = aaf_check_format(format, 0); + if (rc < 0) + goto err; + + stream->init = listener_stream_aaf_init; + +err: + return rc; +} + +static void talker_stream_aaf_init(struct stream_talker *stream, unsigned int *hdr_len) +{ + struct avdecc_format const *format = &stream->format; + + stream->subtype_data.aaf.sparse = 0; + + *hdr_len = aaf_prepare_header((struct avtp_aaf_hdr *)stream->avtp_hdr, format, &stream->id, stream->subtype_data.aaf.sparse); + + avtp_data_header_set_len(stream->avtp_hdr, stream->payload_size); + + if (stream->subtype_data.aaf.sparse) + stream->subtype_data.aaf.frames_per_timestamp = stream->frames_per_packet * AAF_PACKETS_PER_TIMESTAMP_SPARSE; + else + stream->subtype_data.aaf.frames_per_timestamp = stream->frames_per_packet * AAF_PACKETS_PER_TIMESTAMP_NORMAL; + + stream->common.flags |= STREAM_FLAG_CLOCK_GENERATION; + + stream->net_tx = aaf_net_tx; +} + +int talker_stream_aaf_check(struct stream_talker *stream, struct avdecc_format const *format, struct ipc_avtp_connect *ipc) +{ + int rc; + + rc = aaf_check_format(format, 1); + if (rc < 0) + goto err; + + stream->init = talker_stream_aaf_init; + +err: + return rc; +} + +#endif /* CFG_AVTP_1722A */ diff --git a/avtp/aaf.h b/avtp/aaf.h new file mode 100644 index 0000000..a962a7e --- /dev/null +++ b/avtp/aaf.h @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP Audio Format (AAF) handling functions + @details +*/ + +#ifndef _AAF_H_ +#define _AAF_H_ + +#ifdef CFG_AVTP_1722A +#include "common/net.h" +#include "genavb/aaf.h" +#include "genavb/avdecc.h" + +#include "stream.h" + +int listener_stream_aaf_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags); +int talker_stream_aaf_check(struct stream_talker *stream, struct avdecc_format const *format, struct ipc_avtp_connect *ipc); + +#endif + +#endif /* _AAF_H_ */ diff --git a/avtp/acf.c b/avtp/acf.c new file mode 100644 index 0000000..98442d3 --- /dev/null +++ b/avtp/acf.c @@ -0,0 +1,406 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2019, 2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP Control Format (ACF) handling functions + @details +*/ + +#ifdef CFG_AVTP_1722A +#include "common/log.h" + +#include "avtp.h" +#include "acf.h" + +static unsigned int avtp_tscf_prepare_header(struct stream_talker *stream) +{ + avtp_data_header_init(stream->avtp_hdr, AVTP_SUBTYPE_TSCF, &stream->id); + avtp_data_header_set_protocol_specific(stream->avtp_hdr, 0); + avtp_data_header_set_timestamp_invalid(stream->avtp_hdr); + + return sizeof(struct avtp_stream_hdr); +} + +static void acf_tscf_net_tx(struct stream_talker *stream) +{ + struct media_rx_desc *media_desc_array[NET_TX_BATCH], *media_desc; + struct net_tx_desc *net_desc; + struct avtp_stream_hdr *tscf_hdr = stream->avtp_hdr; + unsigned int tscf_data_length; + void *buf; + unsigned int tx_batch; + int n_now, i; + int rc; + + tx_batch = stream->tx_batch; + + /* manage flow control between the network transmit queue and the media interface */ + if (stream_tx_flow_control(stream, &tx_batch) < 0) + goto transmit_disabled; + + if ((rc = media_rx(&stream->media, media_desc_array, tx_batch)) < 0) { + stream->stats.media_err++; +// os_log(LOG_ERR, "talker(%p) media.rx failed %d %d\n", stream, rc, stream->tx_batch); + goto media_rx_fail; + } + + stream->stats.media_rx += rc; + + n_now = rc; + + if (rc && media_desc_array[0]->ts_n) { + u32 delay = media_desc_array[0]->avtp_ts[0].val - stream->gptp_current; + + if ((delay < stream->max_transit_time) || + (delay > (stream_presentation_offset(stream->max_transit_time, stream->latency) + stream->latency))) { + stream->stats.clock_invalid++; + } + } + + i = 0; + while (i < n_now) { + media_desc = media_desc_array[i]; + + net_desc = &media_desc->net; + + tscf_data_length = net_desc->len; + + net_desc->l2_offset -= stream->header_len; + net_desc->len += stream->header_len; + net_desc->flags = 0; + + buf = NET_DATA_START(net_desc); + + tscf_hdr->stream_data_length = htons(tscf_data_length); + + if (media_desc->ts_n) { + net_desc->flags = NET_TX_FLAGS_TS; + avtp_data_header_set_timestamp(tscf_hdr, media_desc->avtp_ts[0].val); + stream->ts_launch = media_desc->avtp_ts[0].val - stream->max_transit_time; + net_desc->ts = stream->ts_launch; + } else + avtp_data_header_set_timestamp_invalid(tscf_hdr); + + os_memcpy(buf, stream->header_template, stream->header_len); + + tscf_hdr->sequence_num++; + + i++; + + stream->media_count += tscf_data_length; + } + + if (stream_net_tx(stream, media_desc_array, i) < 0) + goto transmit_fail; + + return; + +media_rx_fail: + /* Reset the avtp stream since it was reset by the media stack */ + stream->media_count = 0; + +transmit_disabled: +transmit_fail: + return; +} + +static unsigned int avtp_ntscf_prepare_header(struct stream_talker *stream) +{ + struct avtp_ntscf_hdr *hdr = (struct avtp_ntscf_hdr *)stream->avtp_hdr; + + hdr->subtype = AVTP_SUBTYPE_NTSCF; + hdr->version = AVTP_VERSION_0; + + if (stream->common.flags & STREAM_FLAG_SR) + hdr->sv = 1; + else + hdr->sv = 0; + + hdr->r = 0; + + copy_64(&hdr->stream_id, &stream->id); + + return sizeof(struct avtp_ntscf_hdr); +} + +static void acf_ntscf_net_tx(struct stream_talker *stream) +{ + struct media_rx_desc *media_desc_array[NET_TX_BATCH], *media_desc; + struct net_tx_desc *net_desc; + struct avtp_ntscf_hdr *ntscf_hdr = (struct avtp_ntscf_hdr *)stream->avtp_hdr; + unsigned int ntscf_data_length; + void *buf; + unsigned int tx_batch; + int n_now, i; + int rc; + + tx_batch = stream->tx_batch; + + /* manage flow control between the network transmit queue and the media interface */ + if (stream_tx_flow_control(stream, &tx_batch) < 0) + goto transmit_disabled; + + if ((rc = media_rx(&stream->media, media_desc_array, tx_batch)) < 0) { + stream->stats.media_err++; +// os_log(LOG_ERR, "talker(%p) media.rx failed %d %d\n", stream, rc, stream->tx_batch); + goto media_rx_fail; + } + + stream->stats.media_rx += rc; + + n_now = rc; + + i = 0; + while (i < n_now) { + media_desc = media_desc_array[i]; + net_desc = &media_desc->net; + + ntscf_data_length = net_desc->len; + + net_desc->l2_offset -= stream->header_len; + net_desc->len += stream->header_len; + net_desc->flags = 0; + + buf = NET_DATA_START(net_desc); + + NTSCF_DATA_LENGTH_SET(ntscf_hdr, ntscf_data_length); + + os_memcpy(buf, stream->header_template, stream->header_len); + + ntscf_hdr->sequence_num++; + + i++; + + stream->media_count += ntscf_data_length; + } + + if (stream_net_tx(stream, media_desc_array, i) < 0) + goto transmit_fail; + + return; + +media_rx_fail: + /* Reset the avtp stream since it was reset by the media stack */ + stream->media_count = 0; + +transmit_disabled: +transmit_fail: + return; +} + +static void acf_tscf_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + struct media_desc *media_desc[NET_RX_BATCH]; + unsigned int media_n = 0; + unsigned int payload_size; + unsigned int stats = 0; + int i; + + os_log(LOG_DEBUG, "enter stream(%p)\n", stream); + + for (i = 0; i < n; i++) { + + payload_size = desc[i]->l4_len; + + media_desc[media_n] = (struct media_desc *)desc[i]; + media_desc[media_n]->l2_offset = desc[i]->l4_offset; + media_desc[media_n]->len = payload_size; + media_desc[media_n]->bytes_lost = 0; + media_desc[media_n]->flags = desc[i]->flags & (AVTP_MEDIA_CLOCK_RESTART | AVTP_PACKET_LOST | AVTP_TIMESTAMP_UNCERTAIN); + media_desc[media_n]->ts = desc[i]->desc.ts; // This is actually a copy onto itself. Kept for clarity... + + if (!(desc[i]->flags & AVTP_TIMESTAMP_INVALID)) { + media_desc[media_n]->avtp_ts[0].offset = 0; + media_desc[media_n]->avtp_ts[0].flags = 0; + media_desc[media_n]->avtp_ts[0].val = desc[i]->avtp_timestamp; + media_desc[media_n]->n_ts = 1; + + if (!stats) { + stats = 1; + avtp_latency_stats(stream, desc[i]); + } + } else { + if (media_desc[media_n]->flags) { + media_desc[media_n]->avtp_ts[0].offset = 0; + media_desc[media_n]->avtp_ts[0].flags = AVTP_FLAGS_TO_MEDIA_DESC(AVTP_TIMESTAMP_INVALID); + media_desc[media_n]->n_ts = 1; + } else + media_desc[media_n]->n_ts = 0; + } + + media_n++; + } + + stream_media_tx(stream, media_desc, media_n); +} + +static void acf_ntscf_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + struct media_desc *media_desc[NET_RX_BATCH]; + unsigned int media_n = 0; + unsigned int payload_size, l4_max_len; + unsigned int stats = 0; + int i; + + os_log(LOG_DEBUG, "enter stream(%p)\n", stream); + + for (i = 0; i < n; i++) { + struct avtp_ntscf_hdr *hdr = (struct avtp_ntscf_hdr *)((char *)desc[i] + desc[i]->desc.l3_offset); + + /* drop truncated packets */ + desc[i]->l4_len = NTSCF_DATA_LENGTH(hdr); + desc[i]->l4_offset = desc[i]->desc.l3_offset + sizeof(struct avtp_ntscf_hdr); + + l4_max_len = desc[i]->desc.len - (desc[i]->l4_offset - desc[i]->desc.l2_offset); + + if (desc[i]->l4_len > l4_max_len) { + stream->stats.format_err++; + continue; + } + + /* Check for lost packets using sequence_num */ + if (likely(stream->stats.media_tx)) { + if (unlikely(hdr->sequence_num != ((stream->sequence_num + 1) & 0xff))) { + + desc[i]->flags |= AVTP_PACKET_LOST; + stream->stats.pkt_lost++; + } + } + + stream->sequence_num = hdr->sequence_num; + + payload_size = desc[i]->l4_len; + + media_desc[media_n] = (struct media_desc *)desc[i]; + media_desc[media_n]->l2_offset = desc[i]->l4_offset; + media_desc[media_n]->len = payload_size; + media_desc[media_n]->bytes_lost = 0; + media_desc[media_n]->flags = desc[i]->flags & AVTP_PACKET_LOST; + media_desc[media_n]->ts = desc[i]->desc.ts; // This is actually a copy onto itself. Kept for clarity... + + if (media_desc[media_n]->flags) { + media_desc[media_n]->avtp_ts[0].offset = 0; + media_desc[media_n]->avtp_ts[0].flags = AVTP_FLAGS_TO_MEDIA_DESC(AVTP_TIMESTAMP_INVALID); + media_desc[media_n]->n_ts = 1; + } else + media_desc[media_n]->n_ts = 0; + + if (!stats) { + stats = 1; + avtp_latency_stats(stream, desc[i]); + } + + media_n++; + } + + stream_media_tx(stream, media_desc, media_n); +} + + +static int listener_stream_acf_tscf_init(struct stream_listener *stream) +{ + stream->net_rx = acf_tscf_net_rx; + + return 0; +} + +int listener_stream_acf_tscf_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags) +{ + int rc = GENAVB_SUCCESS; + + if (flags & IPC_AVTP_FLAGS_MCR) { + os_log(LOG_ERR, "stream_id(%016"PRIx64") Media clock recovery not supported\n", ntohll(stream->id)); + rc = -GENAVB_ERR_STREAM_PARAMS; + goto exit; + } + + stream->init = listener_stream_acf_tscf_init; + +exit: + return rc; +} + +static int listener_acf_ntscf_init(struct stream_listener *stream) +{ + stream->net_rx = acf_ntscf_net_rx; + + return 0; +} + +int listener_acf_ntscf_check(struct stream_listener *stream, u16 flags) +{ + int rc = GENAVB_SUCCESS; + + if (flags & IPC_AVTP_FLAGS_MCR) { + os_log(LOG_ERR, "stream_id(%016"PRIx64") Media clock recovery not supported\n", ntohll(stream->id)); + rc = -GENAVB_ERR_STREAM_PARAMS; + goto exit; + } + + stream->init = listener_acf_ntscf_init; +exit: + return rc; +} + +static void talker_stream_acf_tscf_init(struct stream_talker *stream, unsigned int *hdr_len) +{ + *hdr_len = avtp_tscf_prepare_header(stream); + + stream->common.flags |= STREAM_FLAG_MEDIA_WAKEUP; + + stream->net_tx = acf_tscf_net_tx; +} + +int talker_stream_acf_tscf_check(struct stream_talker *stream, struct avdecc_format const *format, struct ipc_avtp_connect *ipc) +{ + int rc = GENAVB_SUCCESS; + + if ((!ipc->talker.max_frame_size) || (ipc->talker.max_frame_size > avtp_mtu(AVTP_SUBTYPE_TSCF))) { + rc = -GENAVB_ERR_INVALID_PARAMS; + goto err; + } + + if ((!ipc->talker.max_interval_frames) || (ipc->talker.max_interval_frames > sr_class_max_interval_frames(stream->class))) { + rc = -GENAVB_ERR_INVALID_PARAMS; + goto err; + } + + stream->init = talker_stream_acf_tscf_init; +err: + return rc; +} + +void talker_acf_ntscf_init(struct stream_talker *stream, unsigned int *hdr_len) +{ + *hdr_len = avtp_ntscf_prepare_header(stream); + + stream->common.flags |= STREAM_FLAG_MEDIA_WAKEUP; + + stream->net_tx = acf_ntscf_net_tx; +} + +int talker_acf_ntscf_check(struct stream_talker *stream, struct ipc_avtp_connect *ipc) +{ + int rc = GENAVB_SUCCESS; + + if ((!ipc->talker.max_frame_size) || (ipc->talker.max_frame_size > avtp_mtu(AVTP_SUBTYPE_NTSCF))) { + rc = -GENAVB_ERR_INVALID_PARAMS; + goto err; + } + + if ((!ipc->talker.max_interval_frames) || (ipc->talker.max_interval_frames> sr_class_max_interval_frames(stream->class))) { + rc = -GENAVB_ERR_INVALID_PARAMS; + goto err; + } + + stream->init = talker_acf_ntscf_init; +err: + return rc; +} + +#endif /* CFG_AVTP_1722A */ diff --git a/avtp/acf.h b/avtp/acf.h new file mode 100644 index 0000000..e71304a --- /dev/null +++ b/avtp/acf.h @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP Control Format (ACF) handling functions + @details +*/ + +#ifndef _ACF_H_ +#define _ACF_H_ + +#ifdef CFG_AVTP_1722A +#include "common/net.h" +#include "genavb/acf.h" +#include "genavb/avdecc.h" + +#include "stream.h" + +int listener_stream_acf_tscf_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags); +int listener_acf_ntscf_check(struct stream_listener *stream, u16 flags); +int talker_stream_acf_tscf_check(struct stream_talker *stream, struct avdecc_format const *format, struct ipc_avtp_connect *ipc); +int talker_acf_ntscf_check(struct stream_talker *stream, struct ipc_avtp_connect *ipc); + +#endif + +#endif /* _ACF_H_ */ diff --git a/avtp/avtp.c b/avtp/avtp.c new file mode 100644 index 0000000..a36296c --- /dev/null +++ b/avtp/avtp.c @@ -0,0 +1,845 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP common code + @details Common AVTP code entry points +*/ + +#include "os/stdlib.h" +#include "os/clock.h" + +#include "common/log.h" +#include "common/net.h" +#include "common/avtp.h" + +#include "avtp.h" +#include "stream.h" +#include "media_clock.h" + +/* FIXME add port information to clock domain, grids, ... structures and remove the global variable */ +static struct avtp_ctx *_avtp; + +static struct avtp_port *logical_to_avtp_port(struct avtp_ctx *avtp, unsigned int port) +{ + int i; + + for (i = 0; i < avtp->port_max; i++) + if (port == avtp->port[i].logical_port) + return &avtp->port[i]; + + return NULL; +} + +unsigned int avtp_to_logical_port(unsigned int port_id) +{ + return _avtp->port[port_id].logical_port; +} + +unsigned int avtp_to_clock(unsigned int port_id) +{ + return _avtp->port[port_id].clock_gptp; +} + +__init static struct avtp_ctx *avtp_alloc(unsigned int ports, unsigned int timer_n) +{ + struct avtp_ctx *avtp; + unsigned int size; + + size = sizeof(struct avtp_ctx) + ports * sizeof(struct avtp_port); + size += timer_pool_size(timer_n); + + avtp = os_malloc(size); + if (!avtp) + goto err; + + os_memset(avtp, 0, size); + + avtp->port_max = ports; + + avtp->timer_ctx = (struct timer_ctx *)((u8 *)avtp + sizeof(struct avtp_ctx) + ports * sizeof(struct avtp_port)); + + return avtp; + +err: + return NULL; +} + +/** Initializes avtp global context + * + * Called from avtp platform dependent code. + * + * \return 0 on success, -1 in case of error + * \param avtp pointer to avtp global context + * \param cfg pointer to avtp configuration + * \param priv platform dependent code private data + */ +__init void *avtp_init(struct avtp_config *cfg, unsigned long priv) +{ + struct avtp_ctx *avtp; + struct avtp_port *port; + unsigned int timer_n = CFG_AVTP_MAX_TIMERS; + int i; + + avtp = avtp_alloc(cfg->port_max, timer_n); + if (!avtp) + goto err_malloc; + + log_level_set(avtp_COMPONENT_ID, cfg->log_level); + + _avtp = avtp; + + if (ipc_tx_init(&avtp->ipc_tx_stats, IPC_AVTP_STATS) < 0) + goto err_ipc_tx_stats; + + if (ipc_rx_init_no_notify(&avtp->ipc_rx_media_stack, IPC_MEDIA_STACK_AVTP) < 0) + goto err_ipc_rx_media_stack; + + if (ipc_tx_init(&avtp->ipc_tx_media_stack, IPC_AVTP_MEDIA_STACK) < 0) + goto err_ipc_tx_media_stack; + + if (ipc_rx_init_no_notify(&avtp->ipc_rx_clock_domain, IPC_MEDIA_STACK_CLOCK_DOMAIN) < 0) + goto err_ipc_rx_clock_domain; + + if (ipc_tx_init(&avtp->ipc_tx_clock_domain, IPC_CLOCK_DOMAIN_MEDIA_STACK) < 0) + goto err_ipc_tx_clock_domain; + + if (ipc_tx_init(&avtp->ipc_tx_clock_domain_sync, IPC_CLOCK_DOMAIN_MEDIA_STACK_SYNC) < 0) + goto err_ipc_tx_clock_domain_sync; + + if (timer_pool_init(avtp->timer_ctx, timer_n, priv) < 0) + goto err_timer_pool_init; + + for (i = 0; i < avtp->port_max; i++) { + port = &avtp->port[i]; + + port->logical_port = cfg->logical_port_list[i]; + + port->clock_gptp = cfg->clock_gptp_list[i]; + + list_head_init(&port->talker); + list_head_init(&port->listener); + + clock_source_init(&port->ptp_source, GRID_PRODUCER_PTP, i, priv); + } + + list_head_init(&avtp->stream_destroyed); + + for (i = 0; i < AVTP_CFG_NUM_DOMAINS; i++) + clock_domain_init(&avtp->domain[i], i, priv); + + avtp->priv = priv; + + os_log(LOG_INIT, "avtp(%p) done\n", avtp); + + return avtp; + +err_timer_pool_init: + ipc_tx_exit(&avtp->ipc_tx_clock_domain_sync); + +err_ipc_tx_clock_domain_sync: + ipc_tx_exit(&avtp->ipc_tx_clock_domain); + +err_ipc_tx_clock_domain: + ipc_rx_exit(&avtp->ipc_rx_clock_domain); + +err_ipc_rx_clock_domain: + ipc_tx_exit(&avtp->ipc_tx_media_stack); + +err_ipc_tx_media_stack: + ipc_rx_exit(&avtp->ipc_rx_media_stack); + +err_ipc_rx_media_stack: + ipc_tx_exit(&avtp->ipc_tx_stats); + +err_ipc_tx_stats: + os_free(avtp); + +err_malloc: + return NULL; +} + +/** Cleans up avtp global context + * + * Called from avtp platform dependent code. + * + * \return 0 on success, -1 in case of error + * \param avtp pointer to avtp global context + */ +int avtp_exit(void *avtp_ctx) +{ + struct avtp_ctx *avtp = (struct avtp_ctx *)avtp_ctx; + struct avtp_port *port; + struct list_head *entry, *next; + struct stream_listener *stream; + int i; + + os_log(LOG_INIT, "avtp(%p)\n", avtp); + + /* Destroy all current streams */ + for (i = 0; i < avtp->port_max; i++) { + port = &avtp->port[i]; + + for (entry = list_first(&port->listener); next = list_next(entry), entry != &port->listener; entry = next) { + stream = container_of(entry, struct stream_listener, common.list); + stream_destroy(stream, &avtp->ipc_tx_stats); + } + + for (entry = list_first(&port->talker); next = list_next(entry), entry != &port->talker; entry = next) { + struct stream_talker *stream = container_of(entry, struct stream_talker, common.list); + stream_destroy(stream, &avtp->ipc_tx_stats); + } + + clock_source_exit(&port->ptp_source); + } + + stream_free_all(avtp); + + for (i = 0; i < AVTP_CFG_NUM_DOMAINS; i++) + clock_domain_exit(&avtp->domain[i]); + + timer_pool_exit(avtp->timer_ctx); + + ipc_tx_exit(&avtp->ipc_tx_clock_domain_sync); + + ipc_tx_exit(&avtp->ipc_tx_clock_domain); + + ipc_rx_exit(&avtp->ipc_rx_clock_domain); + + ipc_tx_exit(&avtp->ipc_tx_media_stack); + + ipc_rx_exit(&avtp->ipc_rx_media_stack); + + ipc_tx_exit(&avtp->ipc_tx_stats); + + os_free(avtp); + + os_log(LOG_INIT, "done\n"); + + return 0; +} + +static void process_stats_print(struct ipc_avtp_process_stats *msg) +{ + struct process_stats *stats = &msg->stats; + + stats_compute(&stats->events); + stats_compute(&stats->sched_intvl); + stats_compute(&stats->processing_time); + + os_log(LOG_INFO, "events %2d/%2d/%2d/%2"PRIu64" sched_intvl % 10d/% 10d/% 10d processing time % 10d/% 10d/% 10d (ns)\n", + stats->events.min, stats->events.mean, stats->events.max, stats->events.variance, + stats->sched_intvl.min, stats->sched_intvl.mean, stats->sched_intvl.max, + stats->processing_time.min, stats->processing_time.mean, stats->processing_time.max); +} + + +void stats_ipc_rx(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + switch (desc->type) { + case IPC_AVTP_PROCESS_STATS: + process_stats_print((struct ipc_avtp_process_stats *)&desc->u); + break; + + case IPC_AVTP_STREAM_TALKER_STATS: + stream_talker_stats_print((struct ipc_avtp_talker_stats *)&desc->u); + break; + + case IPC_AVTP_STREAM_LISTENER_STATS: + stream_listener_stats_print((struct ipc_avtp_listener_stats *)&desc->u); + break; + + case IPC_AVTP_CLOCK_DOMAIN_STATS: + clock_domain_stats_print((struct ipc_avtp_clock_domain_stats *)&desc->u); + break; + + case IPC_AVTP_CLOCK_GRID_STATS: + clock_grid_stats_print((struct ipc_avtp_clock_grid_stats *)&desc->u); + break; + + case IPC_AVTP_CLOCK_GRID_CONSUMER_STATS: + clock_grid_consumer_stats_print((struct ipc_avtp_clock_grid_consumer_stats *)&desc->u); + break; + + default: + break; + } + + ipc_free(rx, desc); +} + +static void process_stats_dump(struct avtp_ctx *avtp, struct process_stats *stats) +{ + struct ipc_tx *tx = &avtp->ipc_tx_stats; + struct ipc_desc *desc; + struct ipc_avtp_process_stats *msg; + + desc = ipc_alloc(tx, sizeof(*msg)); + if (!desc) + goto err_ipc_alloc; + + desc->type = IPC_AVTP_PROCESS_STATS; + desc->len = sizeof(*msg); + desc->flags = 0; + + msg = (struct ipc_avtp_process_stats *)&desc->u; + + os_memcpy(&msg->stats, stats, sizeof(*stats)); + + if (ipc_tx(tx, desc) < 0) + goto err_ipc_tx; + + stats_reset(&stats->events); + stats_reset(&stats->sched_intvl); + stats_reset(&stats->processing_time); + + return; + +err_ipc_tx: + ipc_free(tx, desc); + +err_ipc_alloc: + return; +} + + +void avtp_media_event(void *data) +{ + struct media_rx *media = (struct media_rx *)data; + struct stream_talker *stream = container_of(media, struct stream_talker, media); + + stream->net_tx(stream); +} + +void avtp_net_tx_event(void *data) +{ + struct net_tx *tx = (struct net_tx *)data; + struct stream_talker *stream = container_of(tx, struct stream_talker, tx); + + stream->net_tx(stream); +} + +void avtp_stats_dump(void *avtp_ctx, struct process_stats *stats) +{ + struct avtp_ctx *avtp = (struct avtp_ctx *)avtp_ctx; + struct avtp_port *port; + int i; + + process_stats_dump(avtp, stats); + + for (i = 0; i < AVTP_CFG_NUM_DOMAINS; i++) + clock_domain_stats_dump(&avtp->domain[i], &avtp->ipc_tx_stats); + + for (i = 0; i < avtp->port_max; i++) { + port = &avtp->port[i]; + stream_stats_dump(port, &avtp->ipc_tx_stats); + } +} + +void avtp_alternative_header_init(struct avtp_alternative_hdr *avtp_alt, u8 subtype, u8 h) +{ + avtp_alt->subtype = subtype; + avtp_alt->h = h; + avtp_alt->version = AVTP_VERSION_0; +} + +unsigned int avtp_data_header_init(struct avtp_data_hdr *avtp_data, u8 subtype, void *stream_id) +{ + avtp_data->subtype = subtype; + avtp_data->sv = 1; + avtp_data->version = AVTP_VERSION_0; + avtp_data->mr = 0; +#ifdef CFG_AVTP_1722A + avtp_data->f_s_d = 0; +#else + avtp_data->r = 0; + avtp_data->gv = 0; +#endif + avtp_data->tv = 0; + avtp_data->sequence_num = 0; +#ifdef CFG_AVTP_1722A + avtp_data->format_specific_data_1 = 0; +#else + avtp_data->reserved = 0; +#endif + avtp_data->tu = 0; + copy_64(&avtp_data->stream_id, stream_id); +#ifdef CFG_AVTP_1722A + avtp_data->format_specific_data_2 = 0; +#else + avtp_data->gateway_info = 0; +#endif + + return sizeof(struct avtp_data_hdr); +} + +/** AVTP alternative format receive descriptor flush + * + * Flushes received avtp descriptors to the upper protocol layer + * + * \return none + * \param stream pointer to stream context + * \param desc array of avtp receive descriptors + * \param desc_n descriptor array length + */ +static inline void avtp_alternative_desc_flush(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int *desc_n) +{ + if (*desc_n) { + stream->net_rx(stream, desc, *desc_n); + + *desc_n = 0; + } +} + + +/** AVTP stream format receive descriptor flush + * + * Flushes received avtp descriptors to the upper protocol layer, or frees them in case of error. + * + * \return none + * \param stream pointer to stream context + * \param desc array of avtp receive descriptors + * \param desc_n descriptor array length + * \param ts array of avtp timestamps + * \param ts_n timestamp array length + */ +static inline void avtp_stream_desc_flush(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int *desc_n, + struct timestamp *ts, unsigned *ts_n) +{ + /* Flush timestamps if clock recovery is enabled and array is not empty */ + if (stream->source) { + if (*ts_n) { + stream->stats.clock_tx += *ts_n; + clock_producer_stream_rx(&stream->source->grid, ts, ts_n, 0); + *ts_n = 0; + } + } + + if (*desc_n) { + stream->net_rx(stream, desc, *desc_n); + + *desc_n = 0; + } +} + +void avtp_alternative_net_rx(struct net_rx *net_rx, struct net_rx_desc **desc, unsigned int n) +{ + struct stream_listener *stream = container_of(net_rx, struct stream_listener, rx); + struct avtp_rx_desc *avtp_desc, **avtp_desc_first = NULL; + struct avtp_alternative_hdr *hdr; + unsigned int desc_n; + int i; + + if (os_clock_gettime32(stream->clock_gptp, &stream->gptp_current) < 0) + stream->stats.gptp_err++; + + stats_update(&stream->stats.batch, n); + + stream->stats.rx += n; + + for (i = 0, desc_n = 0; i < n; i++) { + + avtp_desc = (struct avtp_rx_desc *)desc[i]; + + hdr = (struct avtp_alternative_hdr *)((char *)desc[i] + desc[i]->l3_offset); + + avtp_desc->flags = 0; + + if (unlikely(hdr->subtype != stream->subtype)) { + stream->stats.subtype_err++; + + avtp_alternative_desc_flush(stream, avtp_desc_first, &desc_n); + + net_rx_free(&avtp_desc->desc); + + /* skip this descriptor */ + continue; + } + + stream->pkt_received++; + + if (!desc_n) + avtp_desc_first = (struct avtp_rx_desc **)&desc[i]; + + desc_n++; + } + + avtp_alternative_desc_flush(stream, avtp_desc_first, &desc_n); +} + +/** AVTP stream network receive callback + * + * Parses and validates AVTP stream headers, extracting timestamp information, for a batch of descriptors. + * Valid packets are sent to the upper protocol layer (in one or more batches). + * The function takes ownership of the received descriptors and is responsible for freeing them. + * + * \return none + * \param net_rx pointer to network receive context + * \param desc array of network receive descriptors + * \param n array length + */ +void avtp_stream_net_rx(struct net_rx *net_rx, struct net_rx_desc **desc, unsigned int n) +{ + struct stream_listener *stream = container_of(net_rx, struct stream_listener, rx); + struct avtp_rx_desc *avtp_desc, **avtp_desc_first = NULL; + struct avtp_data_hdr *hdr; + int i = 0; + unsigned int desc_n, ts_n; + struct timestamp ts[NET_RX_BATCH]; + + if (os_clock_gettime32(stream->clock_gptp, &stream->gptp_current) < 0) + stream->stats.gptp_err++; + + stats_update(&stream->stats.batch, n); + + stream->stats.rx += n; + + for (i = 0, desc_n = 0, ts_n = 0; i < n; i++) { + + os_log(LOG_DEBUG, "port %d, ethertype %x, len %d, timestamp %u\n", desc[i]->port, desc[i]->ethertype, desc[i]->len, desc[i]->ts); + + avtp_desc = (struct avtp_rx_desc *)desc[i]; + + hdr = (struct avtp_data_hdr *)((char *)desc[i] + desc[i]->l3_offset); + + avtp_desc->flags = 0; + + if (unlikely(hdr->subtype != stream->subtype)) { + stream->stats.subtype_err++; + + avtp_stream_desc_flush(stream, avtp_desc_first, &desc_n, ts, &ts_n); + + net_rx_free(&avtp_desc->desc); + + /* skip this descriptor */ + continue; + } + + if (likely(stream->pkt_received)) { + if (unlikely(hdr->sequence_num != ((stream->sequence_num + 1) & 0xff))) { + avtp_stream_desc_flush(stream, avtp_desc_first, &desc_n, ts, &ts_n); + + avtp_desc->flags |= AVTP_PACKET_LOST; + stream->stats.pkt_lost++; + } + } + + if (unlikely(hdr->mr != stream->mr)) { + avtp_stream_desc_flush(stream, avtp_desc_first, &desc_n, ts, &ts_n); + + avtp_desc->flags |= AVTP_MEDIA_CLOCK_RESTART; + stream->mr = hdr->mr; + stream->stats.mr++; + } + + stream->sequence_num = hdr->sequence_num; + + stream->pkt_received++; + +#ifdef CFG_AVTP_1722A + avtp_desc->format_specific_data_2 = hdr->format_specific_data_2; +#endif + + if (unlikely(hdr->tu)) { + avtp_desc->flags |= AVTP_TIMESTAMP_UNCERTAIN; + stream->stats.tu++; + } + + if (likely(hdr->tv)) { + avtp_desc->avtp_timestamp = ntohl(hdr->avtp_timestamp); + + if (stream->source) { + ts[ts_n].ts_nsec = avtp_desc->avtp_timestamp; + ts[ts_n].flags = avtp_desc->flags; + ts_n++; + } + } else { + avtp_desc->flags |= AVTP_TIMESTAMP_INVALID; + } + + avtp_desc->protocol_specific_header = hdr->protocol_specific_header; + + avtp_desc->l4_len = ntohs(hdr->stream_data_length); + + avtp_desc->l4_offset = desc[i]->l3_offset + sizeof(struct avtp_data_hdr); + + if (!desc_n) + avtp_desc_first = (struct avtp_rx_desc **)&desc[i]; + + desc_n++; + } + + avtp_stream_desc_flush(stream, avtp_desc_first, &desc_n, ts, &ts_n); +} + +static void avtp_ipc_send_listener_connect_response(struct avtp_ctx *avtp, unsigned int ipc_dst, u8 *stream_id, struct stream_listener *stream) +{ + struct ipc_desc *desc; + struct ipc_avtp_listener_connect_response *response; + + desc = ipc_alloc(&avtp->ipc_tx_media_stack, sizeof(struct ipc_avtp_listener_connect_response)); + if (desc) { + desc->dst = ipc_dst; + desc->type = IPC_AVTP_LISTENER_CONNECT_RESPONSE; + desc->len = sizeof(struct ipc_avtp_listener_connect_response); + + response = &desc->u.avtp_listener_connect_response; + + os_memcpy(&response->stream_id, stream_id, 8); + + if (stream) + response->status = GENAVB_SUCCESS; + else + response->status = GENAVB_ERR_STREAM_PARAMS; + + if (ipc_tx(&avtp->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avtp(%p) ipc_tx() failed\n", avtp); + ipc_free(&avtp->ipc_tx_media_stack, desc); + } + } +} + +static void avtp_ipc_send_talker_connect_response(struct avtp_ctx *avtp, unsigned int ipc_dst, u8 *stream_id, struct stream_talker *stream) +{ + struct ipc_desc *desc; + struct ipc_avtp_talker_connect_response *response; + + desc = ipc_alloc(&avtp->ipc_tx_media_stack, sizeof(struct ipc_avtp_talker_connect_response)); + if (desc) { + desc->dst = ipc_dst; + desc->type = IPC_AVTP_TALKER_CONNECT_RESPONSE; + desc->len = sizeof(struct ipc_avtp_talker_connect_response); + + response = &desc->u.avtp_talker_connect_response; + + os_memcpy(&response->stream_id, stream_id, 8); + + if (stream) { + response->status = GENAVB_SUCCESS; + response->latency = stream->latency; + response->batch = stream->tx_batch; + response->max_payload_size = stream->payload_size; + } else + response->status = GENAVB_ERR_STREAM_PARAMS; + + if (ipc_tx(&avtp->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avtp(%p) ipc_tx() failed\n", avtp); + ipc_free(&avtp->ipc_tx_media_stack, desc); + } + } +} + +static void avtp_ipc_send_disconnect_response(struct avtp_ctx *avtp, unsigned int ipc_dst, u8 *stream_id, u16 status) +{ + struct ipc_desc *desc; + struct ipc_avtp_disconnect_response *response; + + desc = ipc_alloc(&avtp->ipc_tx_media_stack, sizeof(struct ipc_avtp_disconnect_response)); + if (desc) { + desc->dst = ipc_dst; + desc->type = IPC_AVTP_DISCONNECT_RESPONSE; + desc->len = sizeof(struct ipc_avtp_disconnect_response); + + response = &desc->u.avtp_disconnect_response; + + os_memcpy(&response->stream_id, stream_id, 8); + response->status = status; + + if (ipc_tx(&avtp->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avtp(%p) ipc_tx() failed\n", avtp); + ipc_free(&avtp->ipc_tx_media_stack, desc); + } + } +} + +static void avtp_ipc_error_response(struct avtp_ctx *avtp, unsigned int ipc_dst, u32 type, u32 len, u32 status) +{ + struct ipc_desc *desc; + struct ipc_error_response *error; + + /* Send error response to media stack */ + desc = ipc_alloc(&avtp->ipc_tx_media_stack, sizeof(struct ipc_error_response)); + if (desc) { + desc->dst = ipc_dst; + desc->type = GENAVB_MSG_ERROR_RESPONSE; + desc->len = sizeof(struct ipc_error_response); + desc->flags = 0; + + error = &desc->u.error; + + error->type = type; + error->len = len; + error->status = status; + + if (ipc_tx(&avtp->ipc_tx_media_stack, desc) < 0) { + os_log(LOG_ERR, "avtp(%p) ipc_tx() failed\n", avtp); + ipc_free(&avtp->ipc_tx_media_stack, desc); + } + } else + os_log(LOG_ERR, "avtp(%p) ipc_alloc() failed\n", avtp); +} + +/** AVTP ipc receive + * + * The function takes ownership of the received descriptor and is responsible for freeing it. + * + * \return none + * \param rx pointer to ipc receive context + * \param desc pointer to ipc descriptor + */ +static void avtp_ipc_rx_media_stack(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct avtp_ctx *avtp = container_of(rx, struct avtp_ctx, ipc_rx_media_stack); + u16 status; + struct avtp_port *port; + + os_log(LOG_INFO, "\n"); + + switch (desc->type) { + case IPC_AVTP_CONNECT: + if (desc->len != sizeof(struct ipc_avtp_connect)) { + os_log(LOG_ERR, "ipc_rx(%p) stream creation failed: corrupt IPC desc(%p)\n", ipc_rx, desc); + status = GENAVB_ERR_CTRL_LEN; + goto err; + break; + } + + port = logical_to_avtp_port(avtp, desc->u.avtp_connect.port); + if (!port) { + os_log(LOG_ERR, "ipc_rx(%p) stream creation failed: unknown port %d (ipc_desc(%p))\n", ipc_rx, desc->u.avtp_connect.port, desc); + status = GENAVB_ERR_INVALID_PARAMS; + goto err; + break; + } + + switch (desc->u.avtp_connect.direction) { + case AVTP_DIRECTION_LISTENER: { + struct stream_listener *stream; + + stream = stream_listener_create(avtp, port, &desc->u.avtp_connect); + if (!stream) + os_log(LOG_ERR, "ipc_rx(%p) listener stream creation failed (ipc_desc(%p))\n", ipc_rx, desc); + + avtp_ipc_send_listener_connect_response(avtp, desc->src, desc->u.avtp_connect.stream_id, stream); + + break; + } + + case AVTP_DIRECTION_TALKER: { + struct stream_talker *stream; + + stream = stream_talker_create(avtp, port, &desc->u.avtp_connect); + if (!stream) + os_log(LOG_ERR, "ipc_rx(%p) talker stream creation failed (ipc_desc(%p))\n", ipc_rx, desc); + + avtp_ipc_send_talker_connect_response(avtp, desc->src, desc->u.avtp_connect.stream_id, stream); + + break; + } + + default: + os_log(LOG_ERR, "ipc_rx(%p) stream creation failed: unknown direction %d (ipc_desc(%p))\n", ipc_rx, desc->u.avtp_connect.direction, desc); + status = GENAVB_ERR_CTRL_INVALID; + goto err; + break; + } + + break; + + case IPC_AVTP_DISCONNECT: + { + if (desc->len != sizeof(struct ipc_avtp_disconnect)) { + status = GENAVB_ERR_CTRL_LEN; + goto err; + break; + } + + port = logical_to_avtp_port(avtp, desc->u.avtp_disconnect.port); + if (!port) { + os_log(LOG_ERR, "ipc_rx(%p) stream destruction failed: unknown port %d (ipc_desc(%p)\n", ipc_rx, desc->u.avtp_disconnect.port, desc); + status = GENAVB_ERR_INVALID_PARAMS; + goto err; + break; + } + + switch (desc->u.avtp_disconnect.direction) { + case AVTP_DIRECTION_LISTENER: + { + struct stream_listener *stream; + + stream = stream_listener_find(port, &desc->u.avtp_disconnect.stream_id); + if (stream) + stream_destroy(stream, &avtp->ipc_tx_stats); + + avtp_ipc_send_disconnect_response(avtp, desc->src, desc->u.avtp_disconnect.stream_id, GENAVB_SUCCESS); + + break; + } + + case AVTP_DIRECTION_TALKER: + { + struct stream_talker *stream; + + stream = stream_talker_find(port, &desc->u.avtp_disconnect.stream_id); + if (stream) + stream_destroy(stream, &avtp->ipc_tx_stats); + + avtp_ipc_send_disconnect_response(avtp, desc->src, desc->u.avtp_disconnect.stream_id, GENAVB_SUCCESS); + + break; + } + + default: + os_log(LOG_ERR, "ipc_rx(%p) stream destruction failed: unknown direction %d (ipc_desc(%p))\n", ipc_rx, desc->u.avtp_disconnect.direction, desc); + status = GENAVB_ERR_CTRL_INVALID; + goto err; + + break; + } + + break; + } + + case IPC_HEARTBEAT: + /* We do not send a response in that case: not getting an error when sending the HEARTBEAT is enough for the + * GenAVB library to know the GenAVB stack is up and running, and non-streaming apps will not have an ipc_rx + * set up to receive the response anyway. + */ + break; + + default: + os_log(LOG_ERR, "ipc_rx(%p) unknown IPC %d (ipc_desc(%p))\n", ipc_rx, desc->type, desc); + status = GENAVB_ERR_CTRL_INVALID; + goto err; + + break; + } + + ipc_free(rx, desc); + + return; + +err: + avtp_ipc_error_response(avtp, desc->src, desc->type, desc->len, status); + + ipc_free(rx, desc); +} + +void avtp_ipc_rx(void *avtp_ctx) +{ + struct avtp_ctx *avtp = (struct avtp_ctx *)avtp_ctx; + struct ipc_desc *desc; + + desc = __ipc_rx(&avtp->ipc_rx_clock_domain); + if (desc) + clock_domain_ipc_rx_media_stack(&avtp->ipc_rx_clock_domain, desc); + + desc = __ipc_rx(&avtp->ipc_rx_media_stack); + if (desc) + avtp_ipc_rx_media_stack(&avtp->ipc_rx_media_stack, desc); +} diff --git a/avtp/avtp.cmake b/avtp/avtp.cmake new file mode 100644 index 0000000..9de83fb --- /dev/null +++ b/avtp/avtp.cmake @@ -0,0 +1,22 @@ +if(CONFIG_AVTP) + + genavb_include_os(${TARGET_OS}/avtp.cmake) + + genavb_add_library(NAME avtp + SRCS + avtp.c + stream.c + media_clock.c + 61883_iidc.c + cvf.c + acf.c + aaf.c + crf.c + clock_domain.c + clock_grid.c + clock_source.c + ) + + genavb_link_libraries(TARGET ${avb} LIB avtp) + +endif() diff --git a/avtp/avtp.h b/avtp/avtp.h new file mode 100644 index 0000000..972a707 --- /dev/null +++ b/avtp/avtp.h @@ -0,0 +1,121 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP main header file + @details Definition of AVTP stack component entry point functions and global context structure. +*/ + +#ifndef _AVTP_H_ +#define _AVTP_H_ + +#include "common/net.h" +#include "common/ipc.h" +#include "common/list.h" +#include "common/avtp.h" +#include "common/timer.h" +#include "common/61883_iidc.h" +#include "common/log.h" +#include "clock_domain.h" +#include "media_clock.h" + +#include "avtp_entry.h" + + +struct avtp_port { + struct list_head talker; + + struct list_head listener; + + struct clock_source ptp_source; + + unsigned int logical_port; + + os_clock_id_t clock_gptp; +}; + +/** + * AVTP global context structure + */ +struct avtp_ctx { + struct ipc_rx ipc_rx_media_stack; + struct ipc_tx ipc_tx_media_stack; + + struct ipc_rx ipc_rx_clock_domain; + struct ipc_tx ipc_tx_clock_domain; + struct ipc_tx ipc_tx_clock_domain_sync; + + struct ipc_tx ipc_tx_stats; + + struct list_head stream_destroyed; + + struct clock_domain domain[AVTP_CFG_NUM_DOMAINS]; + + struct timer_ctx *timer_ctx; + + unsigned long priv; + + unsigned int port_max; + + /* variable size array */ + struct avtp_port port[]; +}; + + +/** + * AVTP receive descriptor + */ +struct avtp_rx_desc { + struct net_rx_desc desc; + + u16 l4_offset; /**< offset (in bytes) to layer 4 header */ + u16 l4_len; /**< length (in bytes) of layer 4 header and payload */ + u32 format_specific_data_2; + u16 protocol_specific_header; + u16 flags; + u32 avtp_timestamp; +}; + +struct ipc_avtp_process_stats { + struct process_stats stats; +}; + +unsigned int avtp_to_logical_port(unsigned int port_id); +unsigned int avtp_to_clock(unsigned int port_id); +unsigned int avtp_data_header_init(struct avtp_data_hdr *avtp_data, u8 subtype, void *stream_id); +void avtp_alternative_net_rx(struct net_rx *net_rx, struct net_rx_desc **desc, unsigned int n); +void avtp_stream_net_rx(struct net_rx *, struct net_rx_desc **, unsigned int); + +static inline void avtp_data_header_set_timestamp(struct avtp_data_hdr *avtp_data, u32 tstamp) +{ + avtp_data->avtp_timestamp = htonl(tstamp); + avtp_data->tv = 1; +} + +static inline void avtp_data_header_set_timestamp_invalid(struct avtp_data_hdr *avtp_data) +{ + avtp_data->avtp_timestamp = 0; + avtp_data->tv = 0; +} + +static inline void avtp_data_header_set_len(struct avtp_data_hdr *avtp_data, u16 len) +{ + avtp_data->stream_data_length = htons(len); +} + +static inline void avtp_data_header_toggle_mcr(struct avtp_data_hdr *avtp_data) +{ + avtp_data->mr ^= 1; +} + +static inline void avtp_data_header_set_protocol_specific(struct avtp_data_hdr *avtp_data, u16 protocol_specific_header) +{ + avtp_data->protocol_specific_header = protocol_specific_header; +} + +#endif /* _AVTP_H_ */ diff --git a/avtp/avtp_control.h b/avtp/avtp_control.h new file mode 100644 index 0000000..f556ecf --- /dev/null +++ b/avtp/avtp_control.h @@ -0,0 +1,18 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP control protocol handling functions + @details +*/ + +#ifndef _AVTP_CONTROL_H_ +#define _AVTP_CONTROL_H_ + + +#endif /* _AVTP_CONTROL_H_ */ diff --git a/avtp/avtp_entry.h b/avtp/avtp_entry.h new file mode 100644 index 0000000..d9292db --- /dev/null +++ b/avtp/avtp_entry.h @@ -0,0 +1,37 @@ +/* + * Copyright 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP stack component entry points + @details +*/ + +#ifndef _AVTP_ENTRY_H_ +#define _AVTP_ENTRY_H_ + +#include "os/sys_types.h" +#include "genavb/init.h" +#include "common/types.h" +#include "common/stats.h" +#include "common/avtp.h" + +struct process_stats { + struct stats events; + struct stats sched_intvl; + struct stats processing_time; +}; + +void *avtp_init(struct avtp_config *cfg, unsigned long priv); +int avtp_exit(void *avtp_ctx); +void avtp_stats_dump(void *avtp_ctx, struct process_stats *stats); +void avtp_media_event(void *data); +void avtp_net_tx_event(void *data); +void stats_ipc_rx(struct ipc_rx const *rx, struct ipc_desc *desc); +void avtp_ipc_rx(void *avtp_ctx); +void avtp_stream_free(void *avtp_ctx, u64 current_time); + +#endif /* _AVTP_ENTRY_H_ */ diff --git a/avtp/clock_domain.c b/avtp/clock_domain.c new file mode 100644 index 0000000..3649129 --- /dev/null +++ b/avtp/clock_domain.c @@ -0,0 +1,797 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Clock Domain interface handling + @details +*/ +#include "os/clock.h" +#include "os/string.h" + +#include "common/log.h" + +#include "avtp.h" +#include "stream.h" +#include "clock_domain.h" + +const char *clock_domain_status2string(genavb_clock_domain_status_t status) +{ + switch (status) { + case2str(GENAVB_CLOCK_DOMAIN_STATUS_UNLOCKED); + case2str(GENAVB_CLOCK_DOMAIN_STATUS_LOCKED); + case2str(GENAVB_CLOCK_DOMAIN_STATUS_FREE_WHEELING); + case2str(GENAVB_CLOCK_DOMAIN_STATUS_HW_ERROR); + default: + return (char *)"Unknown clock domain status"; + } +} + +static struct clock_source *clock_domain_find_source(struct clock_domain *domain, struct avtp_ctx *avtp, + struct genavb_msg_clock_domain_set_source *set_source) +{ + struct clock_source *source = NULL; + + if (set_source->source_type == GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM) + source = &domain->stream_source; + else if (set_source->source_type == GENAVB_CLOCK_SOURCE_TYPE_INTERNAL) { + switch (set_source->local_id) { + case GENAVB_CLOCK_SOURCE_AUDIO_CLK: + source = &domain->hw_source; + break; + + case GENAVB_CLOCK_SOURCE_PTP_CLK: + source = &avtp->port[0].ptp_source; + break; + + default: + break; + } + } + + return source; +} + +int __clock_domain_update_source(struct clock_domain *domain, struct clock_source *new_source, void *data) +{ + struct avtp_ctx *avtp = container_of(domain, struct avtp_ctx, domain[domain->id]); + struct avtp_port *port; + int i; + + /* Setting a null source is not supported. */ + if (!new_source) { + os_log(LOG_ERR, "clock_domain(%p) can not set null source\n", domain); + goto err; + } + + /* Look up all talker streams and disable clock generation */ + for (i = 0; i < avtp->port_max; i++) { + struct list_head *entry; + + port = &avtp->port[i]; + + for (entry = list_first(&port->talker); entry != &port->talker; entry = list_next(entry)) { + struct stream_talker *stream = container_of(entry, struct stream_talker, common.list); + + /* For talkers in the specified domain, disable clock consumer (if not previously disabled). */ + if (stream->domain == domain) + stream_clock_consumer_disable(stream); + } + } + + /* Setup domain with new source */ + if (clock_domain_set_source(domain, new_source, data) < 0) + goto err; + + /* Start with new source */ + for (i = 0; i < avtp->port_max; i++) { + struct list_head *entry; + + port = &avtp->port[i]; + + for (entry = list_first(&port->talker); entry != &port->talker; entry = list_next(entry)) { + struct stream_talker *stream = container_of(entry, struct stream_talker, common.list); + + /* Enable clock consumer for all talkers in the domain. The loop (with consumer disable) above guarantees + * that all talkers are disabled at this stage. + */ + if (stream->domain == domain) { + if (stream_clock_consumer_enable(stream) < 0) + goto err; + } + } + } + + return 0; + +err: + return -1; +} + +static int clock_domain_update_source(struct clock_domain *domain, struct clock_source *new_source) +{ + struct avtp_ctx *avtp = container_of(domain, struct avtp_ctx, domain[domain->id]); + struct avtp_port *port; + struct stream_listener *stream_source = NULL; + int i; + + os_log(LOG_INFO, "new source(%p) for domain(%p): %d\n", new_source, domain, domain->id); + + /* If new source is a stream, look it up */ + if (domain->source_type == GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM) { + for (i = 0; i < avtp->port_max; i++) { + port = &avtp->port[i]; + + stream_source = stream_listener_find(port, domain->stream_id); + if (stream_source) + break; + } + } + + /* + * stream_source can be NULL and attached later + * but domain needs to be updated with new source + */ + + return __clock_domain_update_source(domain, new_source, stream_source); +} + +static void clock_domain_status_indication(struct clock_domain *domain, struct ipc_tx *tx, unsigned int ipc_dst) +{ + struct ipc_desc *desc; + + if (!(domain->source_flags & CLOCK_DOMAIN_SOURCE_FLAG_USER)) + return; + + os_log(LOG_INFO, "domain(%p): %u, state: %40s\n", domain, domain->id, clock_domain_status2string(domain->status)); + + desc = ipc_alloc(tx, sizeof(struct genavb_msg_clock_domain_status)); + if (desc) { + desc->dst = ipc_dst; + desc->flags = 0; + desc->len = sizeof(struct genavb_msg_clock_domain_status); + desc->type = GENAVB_MSG_CLOCK_DOMAIN_STATUS; + + desc->u.clock_domain_status.domain = GENAVB_CLOCK_DOMAIN_0 + domain->id; + desc->u.clock_domain_status.source_type = domain->source_type; + + if (domain->source_type == GENAVB_CLOCK_SOURCE_TYPE_INTERNAL) + desc->u.clock_domain_status.local_id = domain->local_id; + else + os_memcpy(desc->u.clock_domain_status.stream_id, domain->stream_id, 8); + + desc->u.clock_domain_status.status = domain->status; + + if (ipc_tx(tx, desc) < 0) { + os_log(LOG_DEBUG, "domain(%p) ipc_tx(%p) failed\n", domain, tx); + ipc_free(tx, desc); + } + } else + os_log(LOG_ERR, "domain(%p) ipc_tx(%p) IPC descriptor allocation failed\n", domain, tx); +} + +static void clock_domain_response(struct clock_domain *domain, unsigned int status, struct ipc_tx *tx, unsigned int ipc_dst) +{ + struct ipc_desc *desc; + + os_log(LOG_INFO, "domain(%p): %d\n", domain, domain->id); + + desc = ipc_alloc(tx, sizeof(struct genavb_msg_clock_domain_response)); + if (desc) { + desc->dst = ipc_dst; + desc->flags = 0; + desc->len = sizeof(struct genavb_msg_clock_domain_response); + desc->type = GENAVB_MSG_CLOCK_DOMAIN_RESPONSE; + + desc->u.clock_domain_response.domain = GENAVB_CLOCK_DOMAIN_0 + domain->id; + desc->u.clock_domain_response.status = status; + + if (ipc_tx(tx, desc) < 0) { + os_log(LOG_DEBUG, "domain(%p) ipc_tx(%p) failed\n", domain, tx); + ipc_free(tx, desc); + } + } else + os_log(LOG_ERR, "domain(%p) ipc_tx(%p) IPC descriptor allocation failed\n", domain, tx); +} + +static void clock_domain_error(struct ipc_desc *rx_desc, unsigned int status, struct ipc_tx *tx) +{ + struct ipc_desc *desc; + + desc = ipc_alloc(tx, sizeof(struct genavb_msg_error_response)); + if (desc) { + desc->dst = rx_desc->src; + desc->flags = 0; + desc->len = sizeof(struct genavb_msg_error_response); + desc->type = GENAVB_MSG_ERROR_RESPONSE; + + desc->u.error.type = rx_desc->type; + desc->u.error.len = rx_desc->len; + desc->u.error.status = status; + + if (ipc_tx(tx, desc) < 0) { + os_log(LOG_DEBUG, "ipc_tx(%p) failed\n", tx); + ipc_free(tx, desc); + } + } else + os_log(LOG_ERR, "ipc_tx(%p) IPC descriptor allocation failed\n", tx); +} + +static int clock_domain_set_source_hdlr(struct clock_domain *domain, struct avtp_ctx *avtp, struct genavb_msg_clock_domain_set_source *set_source) +{ + struct clock_source *new_source; + + new_source = clock_domain_find_source(domain, avtp, set_source); + if (!new_source) + goto err; + + if ((new_source == domain->source) && + (domain->source_type != GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM || !os_memcmp(domain->stream_id, set_source->stream_id, 8))) + return 0; + + /* Save domain source info */ + domain->source_type = set_source->source_type; + if (domain->source_type == GENAVB_CLOCK_SOURCE_TYPE_INTERNAL) + domain->local_id = set_source->local_id; + else if (domain->source_type == GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM) + copy_64(domain->stream_id, set_source->stream_id); + + if (clock_domain_update_source(domain, new_source) < 0) + goto err; + + return 0; + +err: + return -1; +} + +int clock_domain_set_source_legacy(struct clock_domain *domain, struct avtp_ctx *avtp, struct ipc_avtp_connect *ipc) +{ + struct genavb_msg_clock_domain_set_source set_source; + + /* If domain source was set using new API, skip */ + if (domain->source_flags & CLOCK_DOMAIN_SOURCE_FLAG_USER) + goto err; + + /* If domain source is a stream and it's active, skip */ + if (domain->source + && (domain->source_type == GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM) + && domain->source->grid.producer.u.stream.rec) + goto out; + + switch (ipc->clock_domain) { + case GENAVB_MEDIA_CLOCK_DOMAIN_STREAM: + if (ipc->direction == AVTP_DIRECTION_TALKER) + goto err; + + if (ipc->flags & IPC_AVTP_FLAGS_MCR) { + /* Keep compatibiity if no HW support */ + if (!domain->hw_sync) + goto out; + + set_source.source_type = GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM; + copy_64(set_source.stream_id, &ipc->stream_id); + } else + goto out; + + break; + + case GENAVB_MEDIA_CLOCK_DOMAIN_PTP: + if (ipc->direction == AVTP_DIRECTION_LISTENER) + goto err; + + set_source.source_type = GENAVB_CLOCK_SOURCE_TYPE_INTERNAL; + set_source.local_id = GENAVB_CLOCK_SOURCE_PTP_CLK; + break; + + default: + goto out; + break; + } + + return clock_domain_set_source_hdlr(domain, avtp, &set_source); + +out: + return 0; + +err: + return -1; +} + +/** Clock Domain ipc receive callback + * + * The function takes ownership of the received descriptor and is responsible for freeing it. + * + * \return none + * \param rx pointer to ipc receive context + * \param desc pointer to ipc descriptor + */ +void clock_domain_ipc_rx_media_stack(struct ipc_rx const *rx, struct ipc_desc *desc) +{ + struct avtp_ctx *avtp = container_of(rx, struct avtp_ctx, ipc_rx_clock_domain); + struct genavb_msg_clock_domain_set_source *set_source; + unsigned int domain_id; + struct ipc_tx *tx; + struct clock_domain *domain; + + os_log(LOG_INFO, "\n"); + + if (desc->flags & IPC_FLAGS_AVB_MSG_SYNC) + tx = &avtp->ipc_tx_clock_domain_sync; + else + tx = &avtp->ipc_tx_clock_domain; + + switch (desc->type) { + case GENAVB_MSG_CLOCK_DOMAIN_SET_SOURCE: + if (desc->len != sizeof(struct genavb_msg_clock_domain_set_source)) { + clock_domain_error(desc, GENAVB_ERR_CTRL_LEN, tx); + break; + } + + set_source = &desc->u.clock_domain_set_source; + domain_id = set_source->domain; + + if ((domain_id < GENAVB_CLOCK_DOMAIN_0) || (domain_id >= GENAVB_CLOCK_DOMAIN_MAX)) { + clock_domain_error(desc, GENAVB_ERR_CTRL_INVALID, tx); + break; + } + + domain = clock_domain_get(avtp, domain_id); + if (!domain) { + clock_domain_error(desc, GENAVB_ERR_CTRL_FAILED, tx); + break; + } + + if (clock_domain_set_source_hdlr(domain, avtp, set_source) < 0) { + clock_domain_response(domain, GENAVB_ERR_CTRL_FAILED, tx, desc->src); + break; + } + + domain->source_flags |= CLOCK_DOMAIN_SOURCE_FLAG_USER; + + clock_domain_response(domain, GENAVB_SUCCESS, tx, desc->src); + + break; + + case GENAVB_MSG_CLOCK_DOMAIN_GET_STATUS: + domain_id = desc->u.clock_domain_get_status.domain; + + if ((domain_id < GENAVB_CLOCK_DOMAIN_0) || (domain_id >= GENAVB_CLOCK_DOMAIN_MAX)) { + clock_domain_error(desc, GENAVB_ERR_CTRL_INVALID, tx); + break; + } + + domain = clock_domain_get(avtp, domain_id); + if (!domain) { + clock_domain_error(desc, GENAVB_ERR_CTRL_INVALID, tx); + break; + } + + if (!(domain->source_flags & CLOCK_DOMAIN_SOURCE_FLAG_USER)) { + clock_domain_error(desc, GENAVB_ERR_CTRL_INVALID, tx); + break; + } + + clock_domain_status_indication(domain, tx, desc->src); + + break; + + default: + clock_domain_error(desc, GENAVB_ERR_CTRL_UNKNOWN, tx); + + break; + } + + ipc_free(rx, desc); +} + +static const genavb_clock_domain_status_t state_to_status[] = { + [0] = GENAVB_CLOCK_DOMAIN_STATUS_UNLOCKED, + [CLOCK_DOMAIN_STATE_LOCKED] = GENAVB_CLOCK_DOMAIN_STATUS_LOCKED, + [CLOCK_DOMAIN_STATE_FREE_WHEELING] = GENAVB_CLOCK_DOMAIN_STATUS_UNLOCKED, + [CLOCK_DOMAIN_STATE_FREE_WHEELING | CLOCK_DOMAIN_STATE_LOCKED] = GENAVB_CLOCK_DOMAIN_STATUS_FREE_WHEELING +}; + +static void clock_domain_set_status(struct clock_domain *domain, unsigned int status) +{ + struct avtp_ctx *avtp = container_of(domain, struct avtp_ctx, domain[domain->id]); + + if (domain->status != status) { + domain->status = status; + + if (domain->state & CLOCK_DOMAIN_STATE_LOCKED) + domain->locked_count++; + + clock_domain_status_indication(domain, &avtp->ipc_tx_clock_domain, IPC_DST_ALL); + } +} + +void clock_domain_clear_state(struct clock_domain *domain, clock_domain_state_t state) +{ + domain->state &= ~state; + + if (state == CLOCK_DOMAIN_STATE_LOCKED) + clock_domain_set_status(domain, state_to_status[domain->state]); +} + +void clock_domain_set_state(struct clock_domain *domain, clock_domain_state_t state) +{ + domain->state |= state; + + if (state == CLOCK_DOMAIN_STATE_LOCKED) + clock_domain_set_status(domain, state_to_status[domain->state]); +} + +void clock_domain_stats_print(struct ipc_avtp_clock_domain_stats *msg) +{ + os_log(LOG_INFO, "domain(%p): %u, state: %40s, locked count: %10u\n", msg->domain, msg->domain_id, clock_domain_status2string(msg->domain_status), msg->domain_locked_count); +} + +static void _clock_domain_stats_dump(struct clock_domain *domain, struct ipc_tx *tx) +{ + struct ipc_desc *desc; + struct ipc_avtp_clock_domain_stats *msg; + + desc = ipc_alloc(tx, sizeof(*msg)); + if (!desc) + goto err_ipc_alloc; + + desc->type = IPC_AVTP_CLOCK_DOMAIN_STATS; + desc->len = sizeof(*msg); + desc->flags = 0; + + msg = (struct ipc_avtp_clock_domain_stats *)&desc->u; + + msg->domain = domain; + msg->domain_id = domain->id; + msg->domain_status = domain->status; + msg->domain_locked_count = domain->locked_count; + + if (ipc_tx(tx, desc) < 0) + goto err_ipc_tx; + + return; + +err_ipc_tx: + ipc_free(tx, desc); + +err_ipc_alloc: + return; +} + +void clock_domain_stats_dump(struct clock_domain *domain, struct ipc_tx *tx) +{ + struct clock_grid *grid; + struct list_head *entry; + + if (!list_empty(&domain->grids)) { + _clock_domain_stats_dump(domain, tx); + + for (entry = list_first(&domain->grids); entry != &domain->grids; entry = list_next(entry)) { + grid = container_of(entry, struct clock_grid, list); + + clock_grid_stats_dump(grid, tx); + } + } +} + +/** User clock generation wake-up scheduler. + * This scheduler is woken-up by the lower-level HW clock, updates HW user clocks wake-up counters + * and if expired calls the network transmit callback associated to the stream. + * \return none + * \param clock pointer to media_clock_gen_hw + */ +void clock_domain_sched(struct clock_domain *domain, unsigned int prio) +{ + struct list_head *entry, *next; + struct stream_talker *stream; + unsigned int reset; + + clock_grid_ts_update(&domain->source->grid, domain->ts_update_n, &reset); + + for (entry = list_first(&domain->sched_streams[prio]); next = list_next(entry), entry != &domain->sched_streams[prio]; entry = next) { + stream = container_of(entry, struct stream_talker, consumer.list); + + stream_net_tx_handler(stream); + } +} + +void clock_domain_exit_consumer_wakeup(struct clock_grid_consumer *consumer) +{ + /* Check if the consumer is in the sched_streams list. */ + if (consumer->list.next) { //FIXME WAKEUP to be removed once wake-up is replaced by media/net event + struct clock_domain *domain = consumer->grid->domain; + + /* Remove consumer from the list. */ + list_del(&consumer->list); + + /* If last user clock, stop HW*/ + if (list_empty(&domain->sched_streams[consumer->prio])) + os_timer_stop(&domain->source->timer[consumer->prio]); + } +} + + +/** + * This will initialize the wake-up for the consumer. + */ +int clock_domain_init_consumer_wakeup(struct clock_domain *domain, struct clock_grid_consumer *consumer, unsigned int wake_freq_p, unsigned int wake_freq_q) +{ + struct clock_source *source = domain->source; + struct clock_scheduling_params *sched_params = &source->sched_params[consumer->prio]; + + if (!wake_freq_p) { + os_log(LOG_ERR, "clock_grid_consumer(%p) null wake period\n", consumer); + goto err; + } + + if (clock_source_ready(source) < 0) { + os_log(LOG_ERR, "clock_source(%p) is not ready\n", consumer); + goto err; + } + + if (((u64)NSECS_PER_SEC * wake_freq_q) < ((u64)source->tick_period * MCG_WAKE_UP_MIN * wake_freq_p)) { + os_log(LOG_ERR, "clock_grid_consumer(%p) wake period (%u ns) is too small (%u ns)\n", + consumer, (unsigned int)(((u64)NSECS_PER_SEC * wake_freq_q) / wake_freq_p), MCG_WAKE_UP_MIN * source->tick_period); + + goto err; + } + + /* First user clock, start HW */ + if (list_empty(&domain->sched_streams[consumer->prio])) { + + if (os_timer_start(&source->timer[consumer->prio], 0, wake_freq_p, wake_freq_q, 0) < 0) + goto err; + + domain->ts_update_n = ((u64)source->grid.nominal_freq_p * wake_freq_q + source->grid.nominal_freq_q * wake_freq_p - 1) / (source->grid.nominal_freq_q * wake_freq_p); + + sched_params->wake_freq_p = wake_freq_p; + sched_params->wake_freq_q = wake_freq_q; + + os_log(LOG_INFO, "HW clock_source(%p) wake-up period : %u/%u %u ns\n", + source, sched_params->wake_freq_q, sched_params->wake_freq_p, + (unsigned int)(((u64)NSECS_PER_SEC * sched_params->wake_freq_q) / sched_params->wake_freq_p)); + + } else { + if ((sched_params->wake_freq_p * wake_freq_q) != (sched_params->wake_freq_q * wake_freq_p)) { + os_log(LOG_ERR, "clock consumer(%p) frequency (%u Hz) mismatch, current (%u Hz)\n", + consumer, wake_freq_p / wake_freq_q, sched_params->wake_freq_p / sched_params->wake_freq_q); + + goto err; + } + } + + list_add_tail(&domain->sched_streams[consumer->prio], &consumer->list); + + return 0; + +err: + return -1; +} + +void clock_domain_exit_consumer(struct clock_grid_consumer *consumer) +{ + clock_grid_consumer_exit(consumer); +} + + +/** Clock domain consumer initialization function. + * \return 0 if success, -1 otherwise + * \param domain pointer to clock domain + * \param consumer pointer to clock consumer + * \param offset consumer maximum timestamp offset + * \param nominal_freq_p consumer timestamp nominal frequency (in the form p/q Hz) + * \param nominal_freq_q consumer timestamp nominal frequency + * \param alignment consumer timestamp alignment +*/ +int clock_domain_init_consumer(struct clock_domain *domain, struct clock_grid_consumer *consumer, unsigned int offset, u32 nominal_freq_p, u32 nominal_freq_q, unsigned int alignment, unsigned int prio) +{ + struct clock_grid *grid; + int rc = 0; + + consumer->prio = prio; + + if (!domain->source) { + os_log(LOG_ERR, "clock_domain(%p) has no source defined\n", domain); + return -1; + } + + switch (domain->source->grid.producer.type) { + case GRID_PRODUCER_HW: + case GRID_PRODUCER_PTP: + case GRID_PRODUCER_STREAM: + + grid = clock_domain_find_grid(domain, nominal_freq_p, nominal_freq_q); + if (!grid) { + grid = clock_grid_alloc(); + if (!grid) { + os_log(LOG_ERR, "clock_domain(%p) Could not allocate new clock_grid\n", domain); + break; + } + + rc = clock_grid_init_mult(grid, domain, &domain->source->grid, nominal_freq_p, nominal_freq_q); + if (rc < 0) { + clock_grid_free(grid); + break; + } + } + + clock_grid_consumer_attach(consumer, grid, offset, alignment); + + break; + + default: + os_log(LOG_ERR, "clock_source(%p) type(%d) invalid\n", domain->source, domain->source->grid.producer.type); + rc = -1; + break; + } + + return rc; +} + + +struct clock_grid *clock_domain_find_grid(struct clock_domain *domain, u32 nominal_freq_p, u32 nominal_freq_q) +{ + struct list_head *entry, *next; + struct clock_grid *grid; + + for (entry = list_first(&domain->grids); next = list_next(entry), entry != &domain->grids; entry = next) { + grid = container_of(entry, struct clock_grid, list); + + if (((u64)grid->nominal_freq_p * nominal_freq_q) == ((u64)grid->nominal_freq_q * nominal_freq_p)) + return grid; + } + + return NULL; +} + +void clock_domain_add_grid(struct clock_domain *domain, struct clock_grid *grid) +{ + grid->domain = domain; + list_add_tail(&domain->grids, &grid->list); +} + +void clock_domain_remove_grid(struct clock_grid *grid) +{ + list_del(&grid->list); + grid->domain = NULL; +} + +unsigned int clock_domain_is_source_stream(struct clock_domain *domain, void *stream_id) +{ + if ((!domain->source) || (domain->source_type != GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM)) + return 0; + + return cmp_64(domain->stream_id, stream_id); +} + +unsigned int clock_domain_is_locked(struct clock_domain *domain) +{ + if (domain->status == GENAVB_CLOCK_DOMAIN_STATUS_UNLOCKED) + return 0; + + return 1; +} + +int clock_domain_set_source(struct clock_domain *domain, struct clock_source *source, void *data) +{ + /* Close previous source */ + if (domain->source) { + clock_source_close(domain->source); + clock_domain_remove_grid(&domain->source->grid); + domain->source = NULL; + } + + /* Add new source */ + domain->source = source; + clock_domain_add_grid(domain, &source->grid); + if (clock_source_open(domain->source, data) < 0) { + clock_domain_remove_grid(&domain->source->grid); + domain->source = NULL; + + os_log(LOG_ERR, "domain(%p): %d => clock_source_open(%p) error , %s\n", + domain, domain->id, source, clock_grid_producer_type2string(source->grid.producer.type)); + return -1; + } + + os_log(LOG_INFO, "domain(%p): %d => clock source grid(%p), %s\n", + domain, domain->id, source, clock_grid_producer_type2string(source->grid.producer.type)); + + return 0; +} + +int clock_domain_has_stream(struct avtp_ctx *avtp, struct clock_domain *domain) +{ + struct stream_talker *talker; + struct stream_listener *listener; + struct list_head *entry; + struct avtp_port *port; + int i; + + for (i = 0; i < avtp->port_max; i++) { + port = &avtp->port[i]; + + for (entry = list_first(&port->talker); entry != &port->talker; entry = list_next(entry)) { + talker = container_of(entry, struct stream_talker, common.list); + + if (talker->domain == domain) + return 1; + } + + for (entry = list_first(&port->listener); entry != &port->listener; entry = list_next(entry)) { + listener = container_of(entry, struct stream_listener, common.list); + + if (listener->domain == domain) + return 1; + } + } + return 0; +} + +struct clock_domain *clock_domain_get(struct avtp_ctx *avtp, unsigned int id) +{ + struct clock_domain *domain = NULL; + + if (id >= GENAVB_CLOCK_DOMAIN_0) { + /* New clock domain API */ + if (id >= GENAVB_CLOCK_DOMAIN_MAX) + return NULL; + + domain = &avtp->domain[id - GENAVB_CLOCK_DOMAIN_0]; + + } else { + /* Legacy support */ + switch (id) { + case GENAVB_MEDIA_CLOCK_DOMAIN_STREAM: + case GENAVB_MEDIA_CLOCK_DOMAIN_MASTER_CLK: + domain = &avtp->domain[0]; + break; + + case GENAVB_MEDIA_CLOCK_DOMAIN_PTP: + domain = &avtp->domain[1]; + break; + + default: + domain = NULL; + goto exit; + break; + } + } + + os_log(LOG_INFO, "ipc id %d => domain(%p): %d\n", id, domain, domain->id); + +exit: + return domain; +} + +__init void clock_domain_init(struct clock_domain *domain, unsigned int id, unsigned long priv) +{ + int i; + + domain->id = id; + list_head_init(&domain->grids); + + for (i = 0; i < CFG_SR_CLASS_MAX; i++) + list_head_init(&domain->sched_streams[i]); + + clock_source_init(&domain->stream_source, GRID_PRODUCER_STREAM, id, priv); + clock_source_init(&domain->hw_source, GRID_PRODUCER_HW, id, priv); + + domain->hw_sync = media_clock_rec_init(id); + + os_log(LOG_INFO, "domain(%p): %d\n", domain, id); +} + +__exit void clock_domain_exit(struct clock_domain *domain) +{ + clock_source_exit(&domain->hw_source); + clock_source_exit(&domain->stream_source); + if (domain->hw_sync) + media_clock_rec_exit(domain->hw_sync); + + os_log(LOG_INFO, "domain(%p): %d\n", domain, domain->id); +} diff --git a/avtp/clock_domain.h b/avtp/clock_domain.h new file mode 100644 index 0000000..b12f6fb --- /dev/null +++ b/avtp/clock_domain.h @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Clock Domain interface handling + @details +*/ +#ifndef _CLOCK_DOMAIN_H_ +#define _CLOCK_DOMAIN_H_ + +#include "common/ipc.h" +#include "common/list.h" +#include "common/timer.h" + +#include "clock_grid.h" + +#include "clock_source.h" + +struct avtp_ctx; + +struct clock_domain { + unsigned int id; + unsigned int source_type; + union { + u8 stream_id[8]; + u16 local_id; + }; + unsigned int source_flags; + unsigned int status; + unsigned int state; + unsigned int locked_count; + unsigned int ts_update_n; + struct clock_source *source; + struct list_head grids; /**< List of existing grids for the clock_domain. Each grid may be used by one or more consumers. */ + struct list_head sched_streams[CFG_SR_CLASS_MAX]; /**< FIXME WAKEUP list of consumer streams to schedule for this domain (will be removed when switching to media and net event wake-up). */ + struct media_clock_rec *hw_sync; + struct clock_source hw_source; + struct clock_source stream_source; +}; + +#define CLOCK_DOMAIN_SOURCE_FLAG_USER (1 << 0) + +typedef enum { + CLOCK_DOMAIN_STATE_LOCKED = (1 << 0), + CLOCK_DOMAIN_STATE_FREE_WHEELING = (1 << 1) +} clock_domain_state_t; + + +struct ipc_avtp_clock_domain_stats { + void *domain; + unsigned int domain_id; + unsigned int domain_status; + unsigned int domain_locked_count; +}; + +void clock_domain_stats_print(struct ipc_avtp_clock_domain_stats *msg); +void clock_domain_ipc_rx_media_stack(struct ipc_rx const *rx, struct ipc_desc *desc); +int clock_domain_set_source(struct clock_domain *domain, struct clock_source *source, void *data); +int clock_domain_set_wakeup(struct clock_domain *domain, unsigned int freq_p, unsigned int freq_q); +void clock_domain_clear_state(struct clock_domain *domain, clock_domain_state_t state); +void clock_domain_set_state(struct clock_domain *domain, clock_domain_state_t state); +unsigned int clock_domain_is_locked(struct clock_domain *domain); +struct clock_domain * clock_domain_get(struct avtp_ctx *avtp, unsigned int id); +unsigned int clock_domain_is_source_stream(struct clock_domain *domain, void *stream_id); +int __clock_domain_update_source(struct clock_domain *domain, struct clock_source *new_source, void *data); +int clock_domain_set_source_legacy(struct clock_domain *domain, struct avtp_ctx *avtp, struct ipc_avtp_connect *ipc); +void clock_domain_stats_dump(struct clock_domain *domain, struct ipc_tx *tx); +void clock_domain_sched(struct clock_domain *domain, unsigned int prio); +int clock_domain_init_consumer(struct clock_domain *domain, struct clock_grid_consumer *consumer, unsigned int offset, u32 nominal_freq_p, u32 nominal_freq_q, unsigned int alignment, unsigned int prio); +void clock_domain_exit_consumer(struct clock_grid_consumer *consumer); +int clock_domain_init_consumer_wakeup(struct clock_domain *domain, struct clock_grid_consumer *consumer, unsigned int wake_freq_p, unsigned int wake_freq_q); +void clock_domain_exit_consumer_wakeup(struct clock_grid_consumer *consumer); +struct clock_grid *clock_domain_find_grid(struct clock_domain *domain, u32 nominal_freq_p, u32 nominal_freq_q); +void clock_domain_add_grid(struct clock_domain *domain, struct clock_grid *grid); +void clock_domain_remove_grid(struct clock_grid *grid); +void clock_domain_init(struct clock_domain *domain, unsigned int id, unsigned long priv); +void clock_domain_exit(struct clock_domain *domain); + +#endif /* _CLOCK_DOMAIN_H_ */ diff --git a/avtp/clock_grid.c b/avtp/clock_grid.c new file mode 100644 index 0000000..c2d9963 --- /dev/null +++ b/avtp/clock_grid.c @@ -0,0 +1,251 @@ +/* + * Copyright 2016, 2018-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Clock grid implementation + @details +*/ +#include "os/stdlib.h" +#include "os/string.h" +#include "os/clock.h" + +#include "common/log.h" + +#include "clock_grid.h" +#include "media_clock.h" + +const char *clock_grid_producer_type2string(clock_grid_producer_type_t type) +{ + switch (type) { + case2str(GRID_PRODUCER_MULT); + case2str(GRID_PRODUCER_PTP); + case2str(GRID_PRODUCER_STREAM); + case2str(GRID_PRODUCER_HW); + case2str(GRID_PRODUCER_NONE); + default: + return (char *)"Unknown clock grid producer type"; + } +} + +void clock_grid_stats_print(struct ipc_avtp_clock_grid_stats *msg) +{ + struct clock_grid_stats *stats = &msg->stats; + + stats_compute(&stats->period); + + os_log(LOG_INFO, "domain(%p) clock_grid(%p) %s err_period: %10u period: %9d /%9d /%9d\n", + msg->domain, msg->grid, clock_grid_producer_type2string(msg->type), stats->err_period, + stats->period.min, stats->period.mean, stats->period.max); +} + +void clock_grid_stats_dump(struct clock_grid *grid, struct ipc_tx *tx) +{ + struct ipc_desc *desc; + struct ipc_avtp_clock_grid_stats *msg; + + desc = ipc_alloc(tx, sizeof(*msg)); + if (!desc) + goto err_ipc_alloc; + + desc->type = IPC_AVTP_CLOCK_GRID_STATS; + desc->len = sizeof(*msg); + desc->flags = 0; + + msg = (struct ipc_avtp_clock_grid_stats *)&desc->u; + + msg->grid = grid; + msg->domain = grid->domain; + msg->type = grid->producer.type; + + os_memcpy(&msg->stats, &grid->stats, sizeof(grid->stats)); + + stats_reset(&grid->stats.period); + + if (ipc_tx(tx, desc) < 0) + goto err_ipc_tx; + + switch (grid->producer.type) { + case GRID_PRODUCER_MULT: + clock_grid_consumer_stats_dump(&grid->producer.u.mult.source, tx); + break; + + case GRID_PRODUCER_HW: + case GRID_PRODUCER_PTP: + case GRID_PRODUCER_STREAM: + break; + + default: + os_log(LOG_ERR, "clock_grid(%p) unsupported producer type %d\n", grid, grid->producer.type); + break; + } + return; + +err_ipc_tx: + ipc_free(tx, desc); + +err_ipc_alloc: + return; +} + +void clock_grid_update_valid_count(struct clock_grid *grid) +{ + grid->valid_count = min(grid->count, grid->max_valid_count); +} + +unsigned int clock_grid_start_count(struct clock_grid *grid) +{ + return min(grid->count, grid->max_start_count); +} + +void clock_grid_ts_update(struct clock_grid *grid, unsigned int requested, unsigned int *reset) +{ + if (grid->ts_update) { + grid->ts_update(grid, requested, reset); + clock_grid_update_valid_count(grid); + } +} + +struct clock_grid *clock_grid_alloc(void) +{ + struct clock_grid *grid = os_malloc(sizeof(struct clock_grid)); + + if (grid) + os_memset(grid, 0, sizeof(struct clock_grid)); + + return grid; +} + +void clock_grid_free(struct clock_grid *grid) +{ + os_free(grid); +} + +static void clock_grid_release(struct clock_grid *grid) +{ + clock_domain_remove_grid(grid); + + clock_grid_free(grid); +} + +int clock_grid_ref(struct clock_grid *grid) +{ + int rc = 0; + + if (grid->ref_count == 0) { //First consumer of the grid, take any required action. + switch (grid->producer.type) { + case GRID_PRODUCER_HW: + case GRID_PRODUCER_PTP: + case GRID_PRODUCER_MULT: + case GRID_PRODUCER_STREAM: + break; + + default: + os_log(LOG_ERR, "clock_grid(%p) unsupported producer type %d\n", grid, grid->producer.type); + break; + } + } + + if (rc == 0) + grid->ref_count++; + + if (grid->ref_count == 0) { + grid->ref_count--; + os_log(LOG_ERR, "clock_grid(%p) ref_count overflow, cannot use the grid.\n", grid); + rc = -1; + } + + return rc; +} + + +void clock_grid_unref(struct clock_grid *grid) +{ + if (!grid->ref_count) { + os_log(LOG_ERR, "grid(%p) WARNING: un-referencing a clock grid with ref count 0\n", grid); + return; + } + + grid->ref_count--; + + if (grid->ref_count == 0) { + switch (grid->producer.type) { + case GRID_PRODUCER_HW: + case GRID_PRODUCER_PTP: + case GRID_PRODUCER_MULT: + case GRID_PRODUCER_STREAM: + break; + + default: + os_log(LOG_ERR, "clock_grid(%p) unsupported producer type %d\n", grid, grid->producer.type); + break; + } + + /* Clock grid was allocated dynamically and linked to a domain, so get rid of it. */ + if (!(grid->flags & CLOCK_GRID_FLAGS_STATIC)) { + clock_grid_exit(grid); + clock_grid_release(grid); + } + } +} + + +int clock_grid_init(struct clock_grid *grid, clock_grid_producer_type_t type, u32 *ring_base, unsigned int ring_size, u32 nominal_freq_p, u32 nominal_freq_q, void (*ts_update)(struct clock_grid *, unsigned int , unsigned int *)) +{ + int rc = 0; + + if (!nominal_freq_p) { + os_log(LOG_ERR, "(%p) null nominal timestamp frequency\n", grid); + rc = -1; + goto err; + } + + grid->ref_count = 0; + grid->nominal_freq_p = nominal_freq_p; + grid->nominal_freq_q = nominal_freq_q; + grid->nominal_period = ((u64)nominal_freq_q * NSECS_PER_SEC) / nominal_freq_p; + grid->period_jitter = (grid->nominal_period * CLOCK_GRID_VALID_PPM) / 1000000; + grid->ts = ring_base; + grid->ring_size = ring_size; + grid->ts_update = ts_update; + + // FIXME Arbitrary value + grid->max_valid_count = min(grid->ring_size / 2, grid->nominal_freq_p / (grid->nominal_freq_q * (USECS_PER_SEC / CLOCK_GRID_VALID_TIME_US))); + + // Place the initial consumer read index behind write index by 1/4 of the grid size or CLOCK_GRID_VALID_TIME_US usecs (whichever is smaller). + grid->max_start_count = min(grid->ring_size / 4, grid->nominal_freq_p / (2 * grid->nominal_freq_q * (USECS_PER_SEC / CLOCK_GRID_VALID_TIME_US))); + + grid->write_index = 0; + grid->valid_count = 0; + grid->count = 0; + + grid->stats.err_period = 0; + stats_init(&grid->stats.period, 31, NULL, NULL); + + os_log(LOG_INFO, "grid(%p) nominal_freq %u/%u nominal_period %u jitter %u\n", + grid, nominal_freq_p, nominal_freq_q, grid->nominal_period, grid->period_jitter); +err: + return rc; +} + +void clock_grid_exit(struct clock_grid *grid) +{ + switch (grid->producer.type) { + case GRID_PRODUCER_HW: + case GRID_PRODUCER_PTP: + break; + + case GRID_PRODUCER_MULT: + clock_grid_mult_exit(grid); + break; + + default: + os_log(LOG_ERR, "clock_grid(%p) unsupported producer type %d\n", grid, grid->producer.type); + break; + } + + grid->producer.type = GRID_PRODUCER_NONE; +} diff --git a/avtp/clock_grid.h b/avtp/clock_grid.h new file mode 100644 index 0000000..b39c9c2 --- /dev/null +++ b/avtp/clock_grid.h @@ -0,0 +1,147 @@ +/* + * Copyright 2016, 2018-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Clock grid implementation + @details +*/ +#ifndef _CLOCK_GRID_H_ +#define _CLOCK_GRID_H_ + +#include "common/stats.h" +#include "common/list.h" +#include "common/ipc.h" +#include "os/media_clock.h" +#include "os/timer.h" + +struct clock_domain; + +typedef enum { + GEN_INIT, + GEN_RUNNING, +} clock_grid_producer_state_t; + +typedef enum { + GRID_PRODUCER_MULT = 0, + GRID_PRODUCER_PTP, + GRID_PRODUCER_STREAM, + GRID_PRODUCER_HW, + GRID_PRODUCER_MAX, + GRID_PRODUCER_NONE = GRID_PRODUCER_MAX +} clock_grid_producer_type_t; + + +struct clock_grid_consumer { + struct clock_grid *grid; + u32 gptp_current; + u32 alignment; + u32 read_index; + u32 offset; + u32 prev_ts; /* updated each time a timestamp is consumed */ + u32 prev_period; /* initialized with nominal period, updated each time a timestamp is consumed */ + u32 init; /* flag that indicates if prev_ts is valid */ + u32 count; + unsigned int prio; + struct list_head list; /* FIXME WAKEUP Used for scheduling. Likely to be removed when switching to media/net event wake-up scheme. */ + struct clock_grid_consumer_stats { + unsigned int ts; + unsigned int err_offset; + unsigned int err_reset; + unsigned int err_starved; + + struct stats ts_err; + struct stats ts_batch; + } stats; +}; + + +struct clock_grid_producer_mult { + struct clock_grid_consumer source; + + clock_grid_producer_state_t state; + unsigned int interval; /**< Interval between 2 timestamps, in ns */ + unsigned int interval_rem; /**< Fractional part of interval, in units of 1/nominal_freq_p ns */ + unsigned int slot; /**< Current position in the interval between the 2 source timestamps, in units of 1/nominal_freq_p ns */ + u32 ts_last; /**< Previous ts stored in grid (in ns) */ + u32 ts_last_frac; /**< Fractional part of ts_last, in units of 1/nominal_freq_p ns */ + u32 hw_ts_last; /**< Previous source timestamp (in ns) */ +}; + +struct clock_grid_producer_stream { + struct stream_listener *stream; + struct media_clock_rec *rec; + unsigned int stitch_ts_offset; +}; + +struct clock_grid_producer { + clock_grid_producer_type_t type; + + union { + struct clock_grid_producer_mult mult; + + struct clock_grid_producer_stream stream; + + struct os_media_clock_gen hw; + } u; + + unsigned int last_ts; +}; + +#define CLOCK_GRID_FLAGS_STATIC (1 << 0) + +struct clock_grid { + struct list_head list; + unsigned int flags; + u16 ref_count; /* number of consumers using the grid */ + u32 nominal_freq_p; + u32 nominal_freq_q; + u32 nominal_period; /**< Cached from nominal_freq */ + u32 period_jitter; + u32 max_valid_count; + u32 max_start_count; + struct clock_domain *domain; + + unsigned int ring_size; /**< Number of timestamps in the ring buffer */ + u32 *ts; /* switch to u64 in the future */ + u32 valid_count; + u32 count; + u32 write_index; + + void (*ts_update)(struct clock_grid *grid, unsigned int requested, unsigned int *reset); + struct clock_grid_producer producer; /* no need for a pointer, since a given producer should always produce the same grid. */ + struct clock_grid_stats { + unsigned int err_period; + struct stats period; + } stats; +}; + +struct ipc_avtp_clock_grid_stats { + void *grid; + void *domain; + clock_grid_producer_type_t type; + + struct clock_grid_stats stats; +}; + +struct clock_grid *clock_grid_alloc(void); +void clock_grid_free(struct clock_grid *grid); + +int clock_grid_init(struct clock_grid *grid, clock_grid_producer_type_t type, u32 *ring_base, unsigned int ring_size, u32 nominal_freq_p, u32 nominal_freq_q, void (*ts_update)(struct clock_grid *, unsigned int , unsigned int *)); +void clock_grid_exit(struct clock_grid *grid); + +void clock_grid_ts_update(struct clock_grid *grid, unsigned int requested, unsigned int *reset); +unsigned int clock_grid_start_count(struct clock_grid *grid); +void clock_grid_update_valid_count(struct clock_grid *grid); +int clock_grid_ref(struct clock_grid *grid); +void clock_grid_unref(struct clock_grid *grid); + +void clock_grid_stats_print(struct ipc_avtp_clock_grid_stats *msg); +void clock_grid_stats_dump(struct clock_grid *grid, struct ipc_tx *tx); + +const char *clock_grid_producer_type2string(clock_grid_producer_type_t type); + +#endif /* _CLOCK_GRID_H_ */ diff --git a/avtp/clock_source.c b/avtp/clock_source.c new file mode 100644 index 0000000..491bb6f --- /dev/null +++ b/avtp/clock_source.c @@ -0,0 +1,496 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2019-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Clock source implementation + @details +*/ +#include "os/stdlib.h" +#include "common/log.h" + +#include "clock_source.h" +#include "stream.h" + + +static void clock_source_grid_expand(struct clock_grid *grid, unsigned int start, unsigned int count, unsigned int avail) +{ + unsigned int ts = grid->ts[start]; + unsigned int period; + int i; + + if (avail > 1) { + /* Extrapolate timestamps based on actual period */ + period = grid->ts[(start + 1) & (grid->ring_size - 1)] - ts; + if (os_abs((int)period - (int)grid->nominal_period) > grid->period_jitter) + period = grid->nominal_period; + } else { + /* Extrapolate timestamps based on nominal period */ + period = grid->nominal_period; + } + + for (i = 0; i < count; i++) { + start = (start - 1) & (grid->ring_size - 1); + ts -= period; + + grid->ts[start] = ts; + } +} + +static void clock_source_hw_update(struct clock_grid *grid, unsigned int requested, unsigned int *reset) +{ + struct clock_source *source = container_of(grid, struct clock_source, grid); + unsigned int w_idx, count, i; + u32 ts, period, avail; + + os_media_clock_gen_ts_update(&grid->producer.u.hw, &w_idx, &count); + + avail = (w_idx - grid->write_index) & (grid->ring_size - 1); + + if (source->flags & CLOCK_SOURCE_FLAGS_FIRST_UPDATE) { + if (avail > 0) { + + if (avail < source->min_valid_count) { + unsigned int extra_count = source->min_valid_count - avail; + + clock_source_grid_expand(grid, grid->write_index, extra_count, avail); + + grid->write_index = (grid->write_index - extra_count) & (grid->ring_size - 1); + source->extra_count = extra_count; + avail += extra_count; + } + + ts = grid->ts[grid->write_index]; + period = grid->ts[(grid->write_index + 1) & (grid->ring_size - 1)] - ts; + grid->producer.last_ts = ts - period; + source->flags &= ~CLOCK_SOURCE_FLAGS_FIRST_UPDATE; + } + } + + if (requested) + avail = min(avail, requested); + + /* + * Verify there is no discontinuity in hardware generated timestamps. + * If one is found, reset the hardware grid. + */ + if (avail) { + i = grid->write_index; + while (i != w_idx) { + ts = grid->ts[i]; + period = ts - grid->producer.last_ts; + + /* Signal a clock domain unlock and increase stats if a single timestamp is wrong */ + if (os_abs((int)period - (int)grid->nominal_period) > grid->period_jitter) { + if (&grid->domain->source->grid == grid) + clock_domain_clear_state(grid->domain, CLOCK_DOMAIN_STATE_LOCKED); + + grid->stats.err_period++; + } else { + stats_update(&grid->stats.period, period); + } + + i = (i + 1) & (grid->ring_size - 1); + grid->producer.last_ts = ts; + } + } + + grid->write_index = w_idx; + grid->count = count + source->extra_count; + + if (&grid->domain->source->grid == grid) + clock_domain_set_state(grid->domain, CLOCK_DOMAIN_STATE_LOCKED); +} + +static void clock_source_sched(struct os_timer *t, int count, unsigned int prio) +{ + struct clock_source *source = container_of(t, struct clock_source, timer[prio]); + struct clock_domain *domain; + + domain = source->grid.domain; + if (!domain) { + os_log(LOG_ERR, "invalid domain for source(%p)\n", source); + return; + } + + clock_domain_sched(domain, prio); +} + +static void clock_source_sched_low(struct os_timer *t, int count) +{ + clock_source_sched(t, count, SR_PRIO_LOW); +} + +static void clock_source_sched_high(struct os_timer *t, int count) +{ + clock_source_sched(t, count, SR_PRIO_HIGH); +} + +static int clock_source_hw_start(struct clock_source *source) +{ + struct clock_grid *grid = &source->grid; + + if (os_media_clock_gen_start(&grid->producer.u.hw, &grid->write_index) < 0) + goto err; + + source->flags |= CLOCK_SOURCE_FLAGS_FIRST_UPDATE; + source->extra_count = 0; + + grid->count = 0; + grid->valid_count = 0; + + return 0; + +err: + return -1; +} + +static void clock_source_hw_stop(struct clock_source *source) +{ + struct clock_grid *grid = &source->grid; + + os_media_clock_gen_stop(&grid->producer.u.hw); + os_media_clock_gen_reset(&grid->producer.u.hw); +} + +static int clock_source_ptp_open(struct clock_source *source) +{ + struct clock_grid *grid = &source->grid; + + if (!(source->flags & CLOCK_SOURCE_FLAGS_READY)) { + os_log(LOG_ERR, "os_clock_producer not ready for source(%p)\n", source); + return -1; + } + + if (clock_source_hw_start(source) < 0) { + os_log(LOG_ERR, "clock_source_hw_start for source(%p) failed\n", source); + return -1; + } + + if (grid->domain->hw_sync) + media_clock_rec_open_ptp(grid->domain->hw_sync); + + return 0; +} + +static void clock_source_ptp_close(struct clock_source *source) +{ + struct clock_grid *grid = &source->grid; + + clock_source_hw_stop(source); + + if (grid->domain->hw_sync) + media_clock_rec_close_ptp(grid->domain->hw_sync); +} + +static int clock_source_hw_open(struct clock_source *source) +{ + if (!(source->flags & CLOCK_SOURCE_FLAGS_READY)) { + os_log(LOG_ERR, "os_clock_producer not ready for source(%p)\n", source); + return -1; + } + + if (clock_source_hw_start(source) < 0) { + os_log(LOG_ERR, "clock_source_hw_start for source(%p) failed\n", source); + return -1; + } + + return 0; +} + +static void clock_source_hw_close(struct clock_source *source) +{ + clock_source_hw_stop(source); +} + +static int clock_source_stream_open(struct clock_source *source, struct stream_listener *stream) +{ + struct clock_grid *grid = &source->grid; + unsigned int ts_freq_p; + unsigned int ts_freq_q; + + if (!grid->domain->hw_sync) { + os_log(LOG_ERR, "source(%p), domain(%p) has no HW recovery support\n", + source, source->grid.domain); + return -1; + } + + if (!stream) + return 0; + + ts_freq_p = avdecc_fmt_sample_rate(&stream->format); + ts_freq_q = avdecc_fmt_samples_per_timestamp(&stream->format, stream->class); + + if (!ts_freq_p || !ts_freq_q) { + os_log(LOG_ERR, "source(%p) invalid ts_freq: %u/%u\n", + source, ts_freq_p, ts_freq_q); + return -1; + } + + /* + * Source is not yet bound to a stream. + */ + if (!grid->producer.u.stream.stream) { + if (clock_producer_stream_open(grid, stream, stream->domain->hw_sync, ts_freq_p, ts_freq_q) < 0) { + os_log(LOG_ERR, "listener_stream_id(%016"PRIx64") clock_source_stream_open error\n", ntohll(stream->id)); + goto err; + } + stream->source = source; + } +#if 0 + else + /* Legacy, do not fail is source is already setup on another stream */ + goto err; +#endif + return 0; +err: + return -1; +} + +static void clock_source_stream_close(struct clock_source *source) +{ + struct clock_grid *grid = &source->grid; + + if (grid->producer.u.stream.stream) { + grid->producer.u.stream.stream->source = NULL; + clock_producer_stream_close(grid); + } +} + +int clock_source_open(struct clock_source *source, void *data) +{ + int rc = -1; + + switch (source->grid.producer.type) { + case GRID_PRODUCER_HW: + rc = clock_source_hw_open(source); + break; + + case GRID_PRODUCER_PTP: + rc = clock_source_ptp_open(source); + break; + + case GRID_PRODUCER_STREAM: + rc = clock_source_stream_open(source, data); + break; + + default: + break; + } + + return rc; +} + +void clock_source_close(struct clock_source *source) +{ + struct clock_grid *grid = &source->grid; + + switch (grid->producer.type) { + case GRID_PRODUCER_STREAM: + clock_source_stream_close(source); + + clock_domain_clear_state(grid->domain, CLOCK_DOMAIN_STATE_LOCKED); + clock_domain_clear_state(grid->domain, CLOCK_DOMAIN_STATE_FREE_WHEELING); + break; + + case GRID_PRODUCER_HW: + clock_source_hw_close(source); + + clock_domain_clear_state(grid->domain, CLOCK_DOMAIN_STATE_LOCKED); + break; + + case GRID_PRODUCER_PTP: + clock_source_ptp_close(source); + + clock_domain_clear_state(grid->domain, CLOCK_DOMAIN_STATE_LOCKED); + break; + + default: + break; + } +} + +int clock_source_ready(struct clock_source *source) +{ + int rc = -1; + + if (!(source->flags & CLOCK_SOURCE_FLAGS_READY)) + goto exit; + + switch (source->grid.producer.type) { + case GRID_PRODUCER_STREAM: + case GRID_PRODUCER_HW: + case GRID_PRODUCER_PTP: + rc = 0; + break; + + default: + break; + } + +exit: + return rc; +} + +__init static int clock_source_hw_init(struct clock_source *source, clock_grid_producer_type_t type, int id) +{ + struct clock_grid *grid = &source->grid; + + if (os_media_clock_gen_init(&grid->producer.u.hw, id, type == GRID_PRODUCER_HW)) + goto err_hw_init; + + if (clock_grid_init(grid, type, grid->producer.u.hw.array_addr, grid->producer.u.hw.array_size, grid->producer.u.hw.ts_freq_p, grid->producer.u.hw.ts_freq_q, clock_source_hw_update) < 0) { + os_log(LOG_ERR, "clock_source(%p)\n", source); + goto err_grid_init; + } + + source->min_valid_count = min(grid->ring_size / 4, grid->nominal_freq_p / (grid->nominal_freq_q * (USECS_PER_SEC / CLOCK_SOURCE_MIN_VALID_TIME_US))); + + source->tick_period = grid->producer.u.hw.timer_period; + + return 0; + +err_grid_init: + os_media_clock_gen_exit(&grid->producer.u.hw); + +err_hw_init: + return -1; +} + +__exit static void clock_source_hw_exit(struct clock_source *source) +{ + clock_grid_exit(&source->grid); + + os_media_clock_gen_exit(&source->grid.producer.u.hw); +} + +static const struct { + clock_grid_producer_type_t type; + int domain_id; +} clock_id[OS_CLOCK_MAX] = { + [OS_CLOCK_MEDIA_HW_0] = { + .type = GRID_PRODUCER_HW, + .domain_id = 0 + }, + [OS_CLOCK_MEDIA_HW_1] = { + .type = GRID_PRODUCER_HW, + .domain_id = 1 + }, + [OS_CLOCK_MEDIA_PTP_0] = { + .type = GRID_PRODUCER_PTP, + .domain_id = 0 + }, + [OS_CLOCK_MEDIA_PTP_1] = { + .type = GRID_PRODUCER_PTP, + .domain_id = 1 + }, + [OS_CLOCK_MEDIA_REC_0] = { + .type = GRID_PRODUCER_STREAM, + .domain_id = 0 + }, + [OS_CLOCK_MEDIA_REC_1] = { + .type = GRID_PRODUCER_STREAM, + .domain_id = 1 + }, +}; + +static int clock_source_clock_id(clock_grid_producer_type_t type, int id) +{ + int i; + + for (i = 0; i < OS_CLOCK_MAX; i++) + if ((clock_id[i].type == type) && + (clock_id[i].domain_id == id)) + return i; + + return -1; +} + +__init int clock_source_init(struct clock_source *source, clock_grid_producer_type_t type, int id, unsigned long priv) +{ + int clock_id = clock_source_clock_id(type, id); + + os_memset(source, 0, sizeof(*source)); + + source->grid.flags = CLOCK_GRID_FLAGS_STATIC; + source->grid.producer.type = type; + source->clock_id = clock_id; + + if (clock_id < 0) + goto err; + + switch (type) { + case GRID_PRODUCER_HW: + case GRID_PRODUCER_PTP: + if (os_timer_create(&source->timer[SR_PRIO_HIGH], clock_id, 0, clock_source_sched_high, priv) < 0) + goto err_create_high; + + if (os_timer_create(&source->timer[SR_PRIO_LOW], clock_id, 0, clock_source_sched_low, priv) < 0) + goto err_create_low; + + if (clock_source_hw_init(source, type, id) < 0) + goto err_init; + + break; + + case GRID_PRODUCER_STREAM: + if (os_timer_create(&source->timer[SR_PRIO_HIGH], clock_id, 0, clock_source_sched_high, priv) < 0) + goto err_create_high; + + if (os_timer_create(&source->timer[SR_PRIO_LOW], clock_id, 0, clock_source_sched_low, priv) < 0) + goto err_create_low; + + break; + + default: + os_log(LOG_ERR, "clock_source(%p) type(%d) invalid\n", source, type); + goto err; + break; + } + + source->flags |= CLOCK_SOURCE_FLAGS_READY; + + os_log(LOG_INIT, "clock_source(%p) type: %s, id: %d\n", source, clock_grid_producer_type2string(type), id); + + return 0; + +err_init: + os_timer_destroy(&source->timer[SR_PRIO_LOW]); + +err_create_low: + os_timer_destroy(&source->timer[SR_PRIO_HIGH]); + +err_create_high: +err: + return -1; +} + +__exit void clock_source_exit(struct clock_source *source) +{ + if (source->flags & CLOCK_SOURCE_FLAGS_READY) { + + source->flags &= ~CLOCK_SOURCE_FLAGS_READY; + + switch (source->grid.producer.type) { + case GRID_PRODUCER_HW: + case GRID_PRODUCER_PTP: + os_timer_destroy(&source->timer[SR_PRIO_HIGH]); + os_timer_destroy(&source->timer[SR_PRIO_LOW]); + + clock_source_hw_exit(source); + break; + + case GRID_PRODUCER_STREAM: + os_timer_destroy(&source->timer[SR_PRIO_HIGH]); + os_timer_destroy(&source->timer[SR_PRIO_LOW]); + break; + + default: + break; + } + } +} diff --git a/avtp/clock_source.h b/avtp/clock_source.h new file mode 100644 index 0000000..c9b114e --- /dev/null +++ b/avtp/clock_source.h @@ -0,0 +1,58 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Clock source implementation + @details +*/ +#ifndef _CLOCK_SOURCE_H_ +#define _CLOCK_SOURCE_H_ + +#include "os/timer.h" + +#include "clock_grid.h" + +/* The source grid should have enough valid timestamps to cover the needs of all its consumers. + * Consumers read timestamp in batches, so we need to determine the longest batch. This is + * the same as the longest wakeup period. + */ +#define CLOCK_SOURCE_MIN_VALID_TIME_US (2 * (CFG_AVTP_MAX_LATENCY / 1000)) + + +struct clock_scheduling_params { + unsigned int wake_freq_p; + unsigned int wake_freq_q; +}; + +#define CLOCK_SOURCE_FLAGS_READY (1 << 0) +#define CLOCK_SOURCE_FLAGS_FIRST_UPDATE (1 << 1) /* tracks the first update after start */ + +struct clock_source { + unsigned int flags; + u32 extra_count; /* extra timestamps added to source grid on first update after start */ + u32 min_valid_count; /* minimum amount of timestamps to satisfy all possible consumers */ + + struct clock_scheduling_params sched_params[CFG_SR_CLASS_MAX]; + struct os_timer timer[CFG_SR_CLASS_MAX]; + + os_clock_id_t clock_id; + + unsigned int tick_period; + + struct clock_grid grid; +}; + + +int clock_source_init(struct clock_source *source, clock_grid_producer_type_t type, int id, unsigned long priv); +int clock_source_ready(struct clock_source *source); +void clock_source_exit(struct clock_source *source); +int clock_source_open(struct clock_source *source, void *data); +void clock_source_close(struct clock_source *source); + + +#endif /* _CLOCK_SOURCE_H_ */ diff --git a/avtp/config.h b/avtp/config.h new file mode 100644 index 0000000..5ead998 --- /dev/null +++ b/avtp/config.h @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2019, 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP static configuration + @details Contains all compile time configuration options for avtp +*/ + +#ifndef _AVTP_CFG_H_ +#define _AVTP_CFG_H_ + +#include "common/config.h" + +#define avtp_CFG_LOG CFG_LOG + +#define CFG_AVTP_MAX_TIMERS 2 /* one per CRF stream */ + +#define CFG_AVTP_61883_6_MAX_CHANNELS 32 +#define CFG_AVTP_AAF_PCM_MAX_CHANNELS 32 +#define CFG_AVTP_AAF_PCM_MAX_SAMPLES 256 /* Matches 1 packet per interval for SR Class C at 192KHz and SR Class D at 176.4KHz */ +#define CFG_AVTP_AAF_AES3_MAX_STREAMS 10 +#define CFG_AVTP_AAF_AES3_MAX_FRAMES 256 /* Matches 1 packet per interval for SR Class C at 192KHz and SR Class D at 176.4KHz */ + +#endif /* _AVTP_CFG_H_ */ diff --git a/avtp/crf.c b/avtp/crf.c new file mode 100644 index 0000000..42bd17b --- /dev/null +++ b/avtp/crf.c @@ -0,0 +1,781 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP Clock Reference Format (CRF) handling functions + @details +*/ + +#ifdef CFG_AVTP_1722A +#include "genavb/net_types.h" +#include "os/clock.h" +#include "os/stdlib.h" +#include "common/log.h" + +#include "avtp.h" +#include "crf.h" + +typedef enum { + CRF_STATE_LOCKED, + CRF_STATE_FREE_WHEELING, + CRF_STATE_FREE_WHEELING_TO_LOCKED, + CRF_STATE_LOCKED_TO_FREE_WHEELING +} crf_state_t; + +typedef enum { + CRF_EVENT_TIMEOUT, + CRF_EVENT_TIMESTAMPS +} crf_event_t; + +enum { + CRF_ACTION_TIMER_STOP = (1 << 0), + CRF_ACTION_GENERATE_TIMESTAMPS = (1 << 1) +} crf_actions_t; + + +unsigned int crf_stream_presentation_offset(struct stream_talker *stream) +{ + unsigned int latency = stream->latency; + + if (latency > CRF_LATENCY_MAX) + latency = CRF_LATENCY_MAX; + + return _avtp_stream_presentation_offset(stream->max_transit_time, latency); +} + + +static void crf_net_tx(struct stream_talker *stream) +{ + const struct avdecc_format_crf_t *crf_fmt = &stream->format.u.s.subtype_u.crf; + unsigned int ts_n, ts_batch; + unsigned int flags; + struct net_tx_desc *net_tx_desc_array[CRF_TX_BATCH], *net_desc; + u32 ts[CRF_TX_BATCH * CRF_TIMESTAMPS_PER_PDU_MAX * 2]; + int n_now; + void *buf; + int rc; + int i, j; + unsigned int alignment_ts = 0, do_align = 0; + u32 tnow_lsb = 0; + + flags = 0; + + rc = net_tx_alloc_multi(&stream->tx, net_tx_desc_array, stream->tx_batch, stream->header_len + crf_fmt->timestamps_per_pdu * 8); + if (rc <= 0) { + stream->stats.media_err++; + goto media_rx_fail; + } + + if (rc < stream->tx_batch) { + stream->stats.media_err++; + goto tx_batch_fail; + } + + stream->stats.media_rx += rc; + + ts_batch = rc * crf_fmt->timestamps_per_pdu * 2; + + if (ts_batch > (CRF_TX_BATCH * CRF_TIMESTAMPS_PER_PDU_MAX * 2)) { + os_log(LOG_ERR, "stream(%p) Unexpected number of timestamps needed(%u), clamping down to %u\n", stream, ts_batch, CRF_TX_BATCH * CRF_TIMESTAMPS_PER_PDU_MAX * 2); + ts_batch = CRF_TX_BATCH * CRF_TIMESTAMPS_PER_PDU_MAX * 2; + } + + if (stream_domain_phase_change(stream)) + do_align = 1; + + if (!stream->media_count) + do_align = 1; + + if (do_align) { + u64 tnow; + + if (os_clock_gettime64(stream->clock_gptp, &tnow) < 0) { + stream->stats.clock_err++; + goto media_clock_fail; + } + + stream->subtype_data.crf.ts_msb = tnow >> 32; + tnow_lsb = tnow & 0xffffffff; + + alignment_ts = tnow_lsb + avtp_stream_presentation_offset(stream); + flags |= MCG_FLAGS_DO_ALIGN; + } + + stream->consumer.gptp_current = stream->gptp_current; + ts_n = media_clock_gen_get_ts(&stream->consumer, ts, ts_batch, &flags, alignment_ts); + if (ts_n != ts_batch) { + stream->stats.clock_err++; + goto media_clock_fail; + } + + if (do_align) + if (tnow_lsb < ts[0]) + stream->subtype_data.crf.ts_msb++; + + n_now = rc; + ts_n = 0; + i = 0; + while (i < n_now) { + net_desc = net_tx_desc_array[i]; + + net_desc->len += stream->header_len + crf_fmt->timestamps_per_pdu * 8; + net_desc->flags = NET_TX_FLAGS_TS; + + buf = NET_DATA_START(net_desc); + + stream->media_count += stream->frames_per_packet; + + os_memcpy(buf, stream->header_template, stream->header_len); + + ((struct avtp_crf_hdr *)stream->avtp_hdr)->sequence_num++; + + net_desc->ts = ts[0] - stream->max_transit_time; + + for (j = 0; j < crf_fmt->timestamps_per_pdu; j++) { + unsigned int ts_now = ts[ts_n]; + + if (ts_now < stream->ts_last) + stream->subtype_data.crf.ts_msb++; + + *(u32 *)((char *)buf + stream->header_len + j * 8) = htonl(stream->subtype_data.crf.ts_msb); + *(u32 *)((char *)buf + stream->header_len + j * 8 + 4) = htonl(ts_now); + + stream->ts_last = ts_now; + + /* Skip every other timestamp, dividing the frequency by 2, + * workaround for minimum generator frequency */ + ts_n += 2; + } + + i++; + } + + if (stream_net_tx(stream, (struct media_rx_desc **)net_tx_desc_array, i)) + goto transmit_fail; + + return; + +media_clock_fail: +tx_batch_fail: + net_free_multi((void **)net_tx_desc_array, rc); + +media_rx_fail: + stream->media_count = 0; + +transmit_fail: + return; +} + +void crf_os_timer_handler(struct os_timer *t, int count) +{ + struct stream_talker *stream = container_of(t, struct stream_talker, subtype_data.crf.t); + + stream_net_tx_handler(stream); +} + + +static unsigned int crf_prepare_header(struct avtp_crf_hdr *hdr, const struct avdecc_format *format, void *stream_id) +{ + const struct avdecc_format_crf_t *crf_fmt = &format->u.s.subtype_u.crf; + + /* AVTP stream common fields */ + hdr->subtype = AVTP_SUBTYPE_CRF; + hdr->version = AVTP_VERSION_0; + hdr->sv = 1; + + copy_64(&hdr->stream_id, stream_id); + + /* CRF fields */ + hdr->type = crf_fmt->type; + hdr->pull = crf_fmt->pull; + CRF_BASE_FREQUENCY_SET(hdr, AVDECC_FMT_CRF_BASE_FREQUENCY(format)); + hdr->crf_data_length = htons(crf_fmt->timestamps_per_pdu * 8); + hdr->timestamp_interval = htons(AVDECC_FMT_CRF_TIMESTAMP_INTERVAL(format)); + + return sizeof(struct avtp_crf_hdr); +} + +static int crf_measure_period(struct stream_listener *stream, struct timestamp *ts, unsigned *ts_n) +{ + struct crf_subtype_data *crf = &stream->subtype_data.crf; + unsigned int period; + int i; + int rc = 0; + + for (i = 0; i < *ts_n; i++) { + + if (crf->timestamp > 0) { + period = ts[i].ts_nsec - crf->received_ts_last; + + if (os_abs(period - crf->period_nominal) > crf->period_err) { + rc = -1; + crf->timestamp = 0; + } else { + if (crf->timestamp == 1) + crf->period = period; + else + crf->period = (crf->period * 3 + 2) / 4 + (period + 2) / 4; + } + } + + if (crf->timestamp < 0xffffffff) /* Just a big number to avoid overflow */ + crf->timestamp++; + + crf->received_ts_last = ts[i].ts_nsec; + } + + if (rc < 0) + return rc; + else if (crf->timestamp > crf->free_wheeling_to_locked_delay) + return 1; + else + return 0; +} + +static void crf_generate_timestamps(struct stream_listener *stream, struct timestamp *ts, unsigned *ts_n) +{ + struct crf_subtype_data *crf = &stream->subtype_data.crf; + unsigned int ts_last; + int i; + + if (crf->ts_last_set) + ts_last = crf->ts_last; + else + ts_last = stream->gptp_current + crf->period; + + for (i = 0; i < *ts_n; i++) { + ts_last += crf->period; + ts[i].ts_nsec = ts_last; + ts[i].flags = 0; + } +} + +static unsigned int crf_next_timeout(struct stream_listener *stream) +{ + struct crf_subtype_data *crf = &stream->subtype_data.crf; + unsigned int dt; + unsigned int stitch_ts_offset = 0; + + if (stream->source) + stitch_ts_offset = stream->source->grid.producer.u.stream.stitch_ts_offset; + + /* The first_timestamp in the packet is less than max_transit_time in the future, and it may actually be in the past, + * if actual_transit_time + rx_batching_processing_time > max_transit_time. + * The next_required_timestamp is the first_timestamp in the next packet, next_required_timestamp = first_timestamp + packet_period + * The free-wheeling timer should trigger before the next required timestamp, but after the worst case arrival time + * for the next packet. So trigger_time < next_required_timestamp and trigger_time > next_required_timestamp + rx_batching_processing_time. + * This, of course, is impossible. It can only work if we offset the timestamps at least maximum_rx_batching_processing_time, and trigger the timer + * at that point. The time stamps are offset of at least MCR_DELAY and we trigger the timer at MCR_DELAY / 2 after the next_required_timestamp */ + dt = (unsigned int)(crf->ts_last + stitch_ts_offset + crf->period + MCR_DELAY / 2 - stream->gptp_current) / (unsigned int)NSECS_PER_MS; + + return dt; +} + +static const char *crf_state_str[] = { + [CRF_STATE_LOCKED] = "Locked", + [CRF_STATE_FREE_WHEELING] = "Free Wheeling", + [CRF_STATE_FREE_WHEELING_TO_LOCKED] = "Free Wheeling to locked", + [CRF_STATE_LOCKED_TO_FREE_WHEELING] = "Locked to Free Wheeling" +}; + +static const char *crf_event_str[] = { + [CRF_EVENT_TIMEOUT] = "Timeout", + [CRF_EVENT_TIMESTAMPS] = "Timestamps" +}; + +static void crf_set_state(struct stream_listener *stream, crf_state_t state, crf_event_t event) +{ + struct crf_subtype_data *crf = &stream->subtype_data.crf; + + os_log(LOG_INFO, "stream_id(%016"PRIx64") gptp(%u) %s => %s on event %s\n", + ntohll(stream->id), stream->gptp_current, crf_state_str[crf->state], crf_state_str[state], crf_event_str[event]); + + crf->state = state; + + if (stream->source) { + switch (state) { + case CRF_STATE_FREE_WHEELING: + clock_domain_set_state(stream->source->grid.domain, CLOCK_DOMAIN_STATE_FREE_WHEELING); + break; + + case CRF_STATE_LOCKED: + clock_domain_clear_state(stream->source->grid.domain, CLOCK_DOMAIN_STATE_FREE_WHEELING); + break; + + default: + break; + } + } +} + +static void crf_state_handler(struct stream_listener *stream, crf_event_t event, struct timestamp *ts, unsigned *ts_n) +{ + struct crf_subtype_data *crf = &stream->subtype_data.crf; + unsigned int action = 0; + unsigned int dt; + int rc; + int do_stitch = 0; + + switch (crf->state) { + default: + case CRF_STATE_LOCKED: + /* Actively using network timestamps */ + switch (event) { + default: + case CRF_EVENT_TIMESTAMPS: + /* Keep using network timestamps, unless there is a discontinuity */ + + action = CRF_ACTION_TIMER_STOP; + + if (crf_measure_period(stream, ts, ts_n) < 0) { + crf_set_state(stream, CRF_STATE_FREE_WHEELING, event); + action |= CRF_ACTION_GENERATE_TIMESTAMPS; + } + + break; + + case CRF_EVENT_TIMEOUT: + /* Network timestamps not received, start generating locally */ + crf_set_state(stream, CRF_STATE_LOCKED_TO_FREE_WHEELING, event); + action = CRF_ACTION_GENERATE_TIMESTAMPS; + break; + } + + break; + + case CRF_STATE_FREE_WHEELING: + /* Actively generating timestamps locally */ + switch (event) { + default: + case CRF_EVENT_TIMEOUT: + /* Still no timestamps received, continue generating locally */ + action = CRF_ACTION_GENERATE_TIMESTAMPS; + break; + + case CRF_EVENT_TIMESTAMPS: + /* Received network timestamps, start validating them: + - if there is no discontinuity (relatively to last _used_ timestamp), switch immediately to LOCKED and use received timestamps + - if there is a discontinuity, switch to FREE_WHEELING_TO_LOCKED and generate timestamps locally */ + + action = CRF_ACTION_TIMER_STOP; + + /* make sure we check the period against the last used timestamp */ + crf->received_ts_last = crf->ts_last; + crf->timestamp = 1; + + rc = crf_measure_period(stream, ts, ts_n); + if (rc < 0) { + /* At this point the timestamp count was reset */ + crf_set_state(stream, CRF_STATE_FREE_WHEELING_TO_LOCKED, event); + + action |= CRF_ACTION_GENERATE_TIMESTAMPS; + } else + crf_set_state(stream, CRF_STATE_LOCKED, event); + + break; + } + + break; + + case CRF_STATE_FREE_WHEELING_TO_LOCKED: + /* Transition from free-wheeling to locked */ + switch (event) { + default: + case CRF_EVENT_TIMEOUT: + /* Another packet lost, go back to free-wheeling */ + crf_set_state(stream, CRF_STATE_FREE_WHEELING, event); + + action = CRF_ACTION_GENERATE_TIMESTAMPS; + + break; + + case CRF_EVENT_TIMESTAMPS: + /* Continue to receive timestamps, continue validating them: + - if there is a discontinuity (relative to last _received_ timestamp), switch to FREE_WHEELING + - if there is no discontinuity, stay for N seconds in FREE_WHEELING_TO_LOCKED, continue to generate timestamps locally + - after N seconds of no discontinuity in received timestamps, switch to LOCKED and start using them */ + action = CRF_ACTION_TIMER_STOP; + + rc = crf_measure_period(stream, ts, ts_n); + if (rc < 0) { + crf_set_state(stream, CRF_STATE_FREE_WHEELING, event); + action |= CRF_ACTION_GENERATE_TIMESTAMPS; + } else if (rc > 0) { + crf_set_state(stream, CRF_STATE_LOCKED, event); + do_stitch = 1; + } else + action |= CRF_ACTION_GENERATE_TIMESTAMPS; + + break; + } + + break; + + case CRF_STATE_LOCKED_TO_FREE_WHEELING: + /* Transition from locked to free wheeling */ + switch (event) { + default: + case CRF_EVENT_TIMESTAMPS: + /* Received timestamps, if they match generated ones switch back to locked (1 packet lost), + if they don't (possible discontinuity), continue generating timestamps and switch to free-wheeling */ + crf->received_ts_last = crf->ts_last; + + action = CRF_ACTION_TIMER_STOP; + + rc = crf_measure_period(stream, ts, ts_n); + if (rc < 0) { + crf_set_state(stream, CRF_STATE_FREE_WHEELING, event); + + action |= CRF_ACTION_GENERATE_TIMESTAMPS; + } else + crf_set_state(stream, CRF_STATE_LOCKED, event); + + break; + + case CRF_EVENT_TIMEOUT: + /* At least 2 packets lost, switch to free-wheeling */ + crf_set_state(stream, CRF_STATE_FREE_WHEELING, event); + action = CRF_ACTION_GENERATE_TIMESTAMPS; + break; + } + + break; + } + + if (action & CRF_ACTION_TIMER_STOP) + timer_stop(&crf->timer); + + if (action & CRF_ACTION_GENERATE_TIMESTAMPS) + crf_generate_timestamps(stream, ts, ts_n); + + crf->ts_last = ts[(*ts_n) - 1].ts_nsec; + crf->ts_last_set = 1; + + dt = crf_next_timeout(stream); + timer_start(&crf->timer, dt); + + if (stream->source) { + stream->stats.clock_tx += *ts_n; + clock_producer_stream_rx(&stream->source->grid, ts, ts_n, do_stitch); + *ts_n = 0; + } +} + +static void crf_timer_handler(void *data) +{ + struct stream_listener *stream = data; + const struct avdecc_format_crf_t *crf_fmt = &stream->format.u.s.subtype_u.crf; + struct timestamp ts[NET_RX_BATCH * CRF_TIMESTAMPS_PER_PDU_MAX]; + unsigned int ts_n = crf_fmt->timestamps_per_pdu; + + if (os_clock_gettime32(stream->clock_gptp, &stream->gptp_current) < 0) + stream->stats.gptp_err++; + + crf_state_handler(stream, CRF_EVENT_TIMEOUT, ts, &ts_n); +} + +static inline void crf_desc_flush(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int *desc_n, + struct timestamp *ts, unsigned *ts_n) +{ + struct crf_subtype_data *crf = &stream->subtype_data.crf; + + if (*ts_n) { + stream->stats.media_tx += *ts_n; + crf_state_handler(stream, CRF_EVENT_TIMESTAMPS, ts, ts_n); + } else if (!timer_is_running(&crf->timer)) { + /* If CRF packets are received but no valid timestamps were received yet, trigger free-wheeling. + The timeout value is small but mostly arbitrary */ + timer_start(&crf->timer, 1); + } + + if (*desc_n) { + + /* FIXME when the clock locks again we should indicate packets were lost */ + net_free_multi((void **)desc, *desc_n); + + *desc_n = 0; + } +} + +static void crf_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + const struct avdecc_format_crf_t *crf_fmt = &stream->format.u.s.subtype_u.crf; + struct avtp_rx_desc **avtp_desc_first = NULL; + struct avtp_crf_hdr *crf_hdr; + u32 *hdr; + unsigned int desc_n, ts_n; + struct timestamp ts[NET_RX_BATCH * CRF_TIMESTAMPS_PER_PDU_MAX]; + unsigned int flags; + unsigned int stats = 0; + int i, j; + + os_log(LOG_DEBUG, "enter stream(%p)\n", stream); + + for (i = 0, desc_n = 0, ts_n = 0; i < n; i++) { + + crf_hdr = (struct avtp_crf_hdr *)((char *)desc[i] + desc[i]->desc.l3_offset); + flags = 0; + + if (unlikely(crf_hdr->type != stream->subtype_data.crf.type)) { + stream->stats.format_err++; + + crf_desc_flush(stream, avtp_desc_first, &desc_n, ts, &ts_n); + + net_rx_free(&desc[i]->desc); + + continue; + } + + hdr = (u32 *)(&crf_hdr->stream_id + 1); + + if (unlikely((hdr[0] != stream->subtype_data.crf.hdr[0]) || + (hdr[1] != stream->subtype_data.crf.hdr[1]))) { + stream->stats.format_err++; + + crf_desc_flush(stream, avtp_desc_first, &desc_n, ts, &ts_n); + + net_rx_free(&desc[i]->desc); + + /* FIXME we should indicate packets were lost */ + continue; + } + + if (likely(stream->pkt_received)) { + if (unlikely(crf_hdr->sequence_num != ((stream->sequence_num + 1) & 0xff))) + stream->stats.pkt_lost++; + } + + if (unlikely(crf_hdr->mr != stream->mr)) { + stream->mr = crf_hdr->mr; + stream->stats.mr++; + } + + stream->sequence_num = crf_hdr->sequence_num; + + stream->pkt_received++; + + if (unlikely(crf_hdr->tu)) { + stream->stats.tu++; + } + + for (j = 0; j < crf_fmt->timestamps_per_pdu; j++) { + unsigned int ts32 = *(u32 *)((char *)(crf_hdr + 1) + j * 8 + 4); + + ts32 = ntohl(ts32); + + if (!j) { + desc[i]->avtp_timestamp = ts32; + + if (!is_avtp_ts_valid(desc[i]->desc.ts, ts32, stream->max_transit_time, stream->max_timing_uncertainty, 0)) { + stream->stats.media_tx_dropped += crf_fmt->timestamps_per_pdu; + break; + } + } + + ts[ts_n].ts_nsec = ts32; + ts[ts_n].flags = flags; + + ts_n++; + } + + if (!desc_n) + avtp_desc_first = (struct avtp_rx_desc **)&desc[i]; + + if (!stats) { + stats = 1; + avtp_latency_stats(stream, desc[i]); + } + + desc_n++; + } + + crf_desc_flush(stream, avtp_desc_first, &desc_n, ts, &ts_n); +} + + +static int crf_check_format(const struct avdecc_format *format) +{ + const struct avdecc_format_crf_t *crf_fmt = &format->u.s.subtype_u.crf; + unsigned int base_freq, freq, pdu_period, p, q; + int rc = 0; + + /* 1722rev1-2016 Table 26 */ + /* Only audio sample type supported for now */ + switch (crf_fmt->type) { + default: + case CRF_TYPE_USER: + case CRF_TYPE_VIDEO_FRAME: + case CRF_TYPE_VIDEO_LINE: + case CRF_TYPE_MACHINE_CYCLE: + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + break; + + case CRF_TYPE_AUDIO_SAMPLE: + break; + } + + base_freq = AVDECC_FMT_CRF_BASE_FREQUENCY(format); + + /* 1722rev1-2016 Table 27 */ + switch (crf_fmt->pull) { + case CRF_PULL_1_1: + p = 1, q = 1; + break; + + case CRF_PULL_1000_1001: + p = 1000, q = 1001; + break; + + case CRF_PULL_1001_1000: + p = 1001, q = 1000; + break; + + case CRF_PULL_24_25: + p = 24, q = 25; + break; + + case CRF_PULL_25_24: + p = 25, q = 24; + break; + + case CRF_PULL_1_8: + p = 1, q = 8; + break; + + default: + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + break; + } + + if ((crf_fmt->timestamps_per_pdu < CRF_TIMESTAMPS_PER_PDU_MIN) || (crf_fmt->timestamps_per_pdu > CRF_TIMESTAMPS_PER_PDU_MAX)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + + if ((AVDECC_FMT_CRF_TIMESTAMP_INTERVAL(format) < CRF_TIMESTAMP_INTERVAL_MIN) || (AVDECC_FMT_CRF_TIMESTAMP_INTERVAL(format) > CRF_TIMESTAMP_INTERVAL_MAX)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + + freq = (base_freq * p) / (AVDECC_FMT_CRF_TIMESTAMP_INTERVAL(format) * q); + if ((freq < MCG_MIN_FREQUENCY) || (freq > MCG_MAX_FREQUENCY)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + + pdu_period = ((u64)crf_fmt->timestamps_per_pdu * NSECS_PER_SEC * AVDECC_FMT_CRF_TIMESTAMP_INTERVAL(format) * q) / (base_freq * p); + + if (((pdu_period * CRF_TX_BATCH) < CFG_AVTP_MIN_LATENCY) || ((pdu_period * CRF_TX_BATCH) > CFG_AVTP_MAX_LATENCY)) { + rc = -GENAVB_ERR_STREAM_PARAMS; + goto err_format; + } + + return 0; + +err_format: + return rc; +} + +static void listener_crf_exit(struct stream_listener *stream) +{ + timer_destroy(&stream->subtype_data.crf.timer); +} + +static int listener_crf_init(struct stream_listener *stream) +{ + struct avdecc_format const *format = &stream->format; + const struct avdecc_format_crf_t *crf_fmt = &format->u.s.subtype_u.crf; + struct crf_subtype_data *crf = &stream->subtype_data.crf; + struct avtp_crf_hdr crf_hdr; + u32 *hdr; + + stream->subtype_data.crf.timer.func = crf_timer_handler; + stream->subtype_data.crf.timer.data = stream; + + if (timer_create(stream->common.avtp->timer_ctx, &stream->subtype_data.crf.timer, TIMER_TYPE_SYS, 0) < 0) + goto err_timer; + + os_memset(&crf_hdr, 0, sizeof(crf_hdr)); + + stream->subtype_data.crf.type = crf_fmt->type; + + /* Setup receive header pattern match */ + crf_hdr.pull = crf_fmt->pull; + CRF_BASE_FREQUENCY_SET(&crf_hdr, AVDECC_FMT_CRF_BASE_FREQUENCY(format)); + crf_hdr.crf_data_length = htons(crf_fmt->timestamps_per_pdu * 8); + crf_hdr.timestamp_interval = htons(AVDECC_FMT_CRF_TIMESTAMP_INTERVAL(format)); + + hdr = (u32 *)(&crf_hdr.stream_id + 1); + + stream->subtype_data.crf.hdr[0] = hdr[0]; + stream->subtype_data.crf.hdr[1] = hdr[1]; + + stream->common.flags |= STREAM_FLAG_NO_MEDIA; + + stream->net_rx = crf_net_rx; + stream->exit = listener_crf_exit; + + crf->period_nominal = ((u64)NSECS_PER_SEC * avdecc_fmt_samples_per_timestamp(format, stream->class)) / avdecc_fmt_sample_rate(format); + crf->period = crf->period_nominal; + crf->period_err = crf->period / 64; + crf->state = CRF_STATE_LOCKED; + crf->ts_last_set = 0; + crf->free_wheeling_to_locked_delay = (NSECS_PER_MS * (u64)CRF_FREE_WHEELING_TO_LOCKED_DELAY_MS) / crf->period; + + return 0; + +err_timer: + return -1; +} + +int listener_crf_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags) +{ + int rc; + + rc = crf_check_format(format); + if (rc < 0) + goto err; + + stream->init = listener_crf_init; + + return 0; + +err: + return rc; +} + +static void talker_crf_init(struct stream_talker *stream, unsigned int *hdr_len) +{ + struct avdecc_format const *format = &stream->format; + + *hdr_len = crf_prepare_header((struct avtp_crf_hdr *)stream->avtp_hdr, format, &stream->id); + + stream->subtype_data.crf.ts_period = ((u64)NSECS_PER_SEC * avdecc_fmt_samples_per_timestamp(format, stream->class)) / avdecc_fmt_sample_rate(format); + + stream->common.flags |= STREAM_FLAG_CLOCK_GENERATION | STREAM_FLAG_NO_MEDIA; + + stream->net_tx = crf_net_tx; +} + +int talker_crf_check(struct stream_talker *stream, struct avdecc_format const *format, struct ipc_avtp_connect *ipc) +{ + int rc; + + rc = crf_check_format(format); + if (rc < 0) + goto err; + + stream->init = talker_crf_init; + +err: + return rc; +} + +#endif /* CFG_AVTP_1722A */ diff --git a/avtp/crf.h b/avtp/crf.h new file mode 100644 index 0000000..f0dd007 --- /dev/null +++ b/avtp/crf.h @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP Clock Reference Format (CRF) handling functions + @details +*/ + +#ifndef _CRF_H_ +#define _CRF_H_ + +#ifdef CFG_AVTP_1722A +#include "common/net.h" +#include "genavb/crf.h" +#include "genavb/avdecc.h" + +#include "stream.h" + +int listener_crf_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags); +int talker_crf_check(struct stream_talker *stream, struct avdecc_format const *format, struct ipc_avtp_connect *ipc); +void crf_os_timer_handler(struct os_timer *t, int count); + +unsigned int crf_stream_presentation_offset(struct stream_talker *stream); + +#define CRF_FREE_WHEELING_TO_LOCKED_DELAY_MS 1000 /* 1 second. Amount of time to wait before trying to switch from free-wheeling to locked + (if there is a discontinuity in received timestamps) */ + +#define CRF_LATENCY_MAX 2000000 +#define CRF_TX_BATCH 1 /* Works well for 50Hz pdu rate (as specified in 1722-2016, Table 28) */ + +#endif + +#endif /* _CRF_H_ */ diff --git a/avtp/cvf.c b/avtp/cvf.c new file mode 100644 index 0000000..a4999e9 --- /dev/null +++ b/avtp/cvf.c @@ -0,0 +1,765 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP compressed video format (CVF) handling functions + @details +*/ + +#ifdef CFG_AVTP_1722A + +#include "common/log.h" +#include "common/cvf.h" + +#include "avtp.h" +#include "cvf.h" + +#include "os/string.h" + +static unsigned int avtp_cvf_h264_prepare_header(struct avtp_data_hdr *hdr, const struct avdecc_format *format); + +static void avtp_cvf_h264_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + struct avtp_cvf_h264_hdr *h264_avtp_hdr; + struct media_desc *media_desc[NET_RX_BATCH]; + unsigned int media_n = 0; + unsigned int stats = 0; + int i; + struct cvf_h264_nalu_header *nalu_header; + static const u32 sync_bytes = 0x00000001; /* 3 zero bytes sync word */ + struct cvf_h264_nalu_fu_header *nalu_fu_header; + u8 spec_version = 0; + u32 h264_timestamp; + struct cvf_h264_hdr *h264_hdr; + unsigned int is_single_packet = 0; + unsigned int is_last_fu; + unsigned int is_aggregation; + u16 l2_offset_orig, len_orig; + unsigned int free_rx_desc = 0; + + + spec_version = stream->format.u.s.subtype_u.cvf.format_u.h264.spec_version; + + os_log(LOG_DEBUG, "enter stream(%p) stream_id(%016"PRIx64") spec_version(%u)\n", stream, ntohll(stream->id), (unsigned int)spec_version); + + for (i = 0; i < n; i++) { + + h264_avtp_hdr = (struct avtp_cvf_h264_hdr *)((char *)desc[i] + desc[i]->desc.l3_offset); + + nalu_fu_header = NULL; + h264_hdr = NULL; + is_single_packet = 0; + is_last_fu = 0; + is_aggregation = 0; + free_rx_desc = 0; + + /* Save length and offset for error handling */ + l2_offset_orig = desc[i]->desc.l2_offset; + len_orig = desc[i]->desc.len; + + if (unlikely((h264_avtp_hdr->format != CVF_FORMAT_RFC) + || (h264_avtp_hdr->format_subtype != CVF_FORMAT_SUBTYPE_H264))) { + stream->stats.format_err++; + os_log(LOG_DEBUG,"stream_id(%016"PRIx64") CVF H264 not recognized: format %u format_subtype %u rsvd %u\n", ntohll(stream->id), h264_avtp_hdr->format, h264_avtp_hdr->format_subtype, h264_avtp_hdr->reserved); + + /* FIXME we should indicate packets were lost */ + free_rx_desc = 1; + goto next_iteration; + } + + /* FIXME The code below works but is very sensitive to operation order, because the source and destination structs + * actually point to the same memory area. Need to make it safer to avoid overwriting a valid field by mistake. + */ + media_desc[media_n] = (struct media_desc *)desc[i]; + + + if (spec_version == CVF_H264_IEEE_1722_2016) { /*h264 header is defined in the IEEE1722_2016 specification = H264_timestamps */ + media_desc[media_n]->l2_offset = desc[i]->l4_offset + sizeof(struct cvf_h264_hdr); + media_desc[media_n]->len = desc[i]->l4_len - sizeof(struct cvf_h264_hdr); + } else { /* No specific H264 header defined */ + media_desc[media_n]->l2_offset = desc[i]->l4_offset; + media_desc[media_n]->len = desc[i]->l4_len; + } + + media_desc[media_n]->bytes_lost = 0; + + media_desc[media_n]->flags = desc[i]->flags & (AVTP_MEDIA_CLOCK_RESTART | AVTP_PACKET_LOST | AVTP_TIMESTAMP_UNCERTAIN); + media_desc[media_n]->ts = desc[i]->desc.ts; // This is actually a copy onto itself. Kept for clarity... + + /*Save the h264_timestamp */ + h264_hdr = (struct cvf_h264_hdr *)((char *)desc[i] + media_desc[media_n]->l2_offset - sizeof(struct cvf_h264_hdr)); + h264_timestamp = ntohl(h264_hdr->h264_timestamp); + + nalu_header = (struct cvf_h264_nalu_header *)((char *)desc[i] + media_desc[media_n]->l2_offset); + + if(nalu_header->f) { + os_log(LOG_DEBUG, "stream_id(%016"PRIx64") CVF H264 NALU Header Forbidden bit is set\n", ntohll(stream->id)); + stream->stats.format_err++; + free_rx_desc = 1; + goto next_iteration; + } + + /* Parse the NAL Unit header */ + switch (nalu_header->type) { + case CVF_H264_NALU_TYPE_RESERVED0: + case CVF_H264_NALU_TYPE_RESERVED17: + case CVF_H264_NALU_TYPE_RESERVED18: + case CVF_H264_NALU_TYPE_RESERVED22: + case CVF_H264_NALU_TYPE_RESERVED23: + case CVF_H264_NALU_TYPE_RESERVED30: + case CVF_H264_NALU_TYPE_RESERVED31: + /* Undefined */ + os_log(LOG_DEBUG, "stream_id(%016"PRIx64") CVF_H264 NALU TYPE %d reserved and not defined\n", ntohll(stream->id), nalu_header->type); + stream->stats.format_err++; + free_rx_desc = 1; + goto next_iteration; + case CVF_H264_NALU_TYPE_STAP_A: + { + + /* Support only up to 2 NALU data for now */ + /* Simply add sync_bytes between NALU Data replacing STAP-A NAL header */ + /* and NALU size header, keeping only the NALU HDR + NALU Data */ + + /* Create the following packet as example: + SYNC_BYTES | NALU 1 HDR | NALU 1 DATA | SYNC_BYTES | NALU 2 HDR | NALU 2 DATA */ + unsigned int temp_offset = 0, payload_length, size_offset, num_data = 0; + u16 nalu_data_size = 0; + + /* Parse the full STAP-A packet to check how many Data payloads are defined */ + payload_length = media_desc[media_n]->len - 1; /* Remove the STAP-A NAL HDR */ + size_offset = 1; + + while (payload_length > 2) { + nalu_data_size = ntohs(*(u16 *)((char *)desc[i] + media_desc[media_n]->l2_offset + size_offset)); + os_log(LOG_DEBUG,"DEBUG STAP-A DATA%d packet of size %u\n", num_data + 1, nalu_data_size); + if (nalu_data_size > (payload_length - 2)) + nalu_data_size = payload_length - 2; + + /* Strip NALU size header */ + size_offset += 2; + payload_length -= 2; + + /* Go to next data packet */ + size_offset += nalu_data_size; + payload_length -= nalu_data_size; + + num_data++; + } + + if (num_data > 2) { + os_log(LOG_DEBUG,"stream_id(%016"PRIx64") CVF H264 STAP-A packet with %u DATA ! more than the 2 maximum supported\n", ntohll(stream->id), num_data); + stream->stats.format_err++; + free_rx_desc = 1; + goto next_iteration; + } + + + /* nalu_size = nalu_hdr size + nalu data payload size */ + nalu_data_size = ntohs(*(u16 *)((char *)desc[i] + media_desc[media_n]->l2_offset + sizeof(struct cvf_h264_nalu_header))); + + /* Add 2 sync bytes and remove the STAP-A NAL HDR and NALU size headers */ + temp_offset = num_data * sizeof(sync_bytes) - num_data * CVF_H264_STAP_A_NALU_SIZE_HDR_SIZE - sizeof(struct cvf_h264_nalu_header); + media_desc[media_n]->l2_offset -= temp_offset; + media_desc[media_n]->len += temp_offset; + + /* Add sync bytes for data1 */ + *(u32 *)((char *)desc[i] + media_desc[media_n]->l2_offset) = htonl(sync_bytes); + if (num_data == 2) { + /* Keep data2 in place, move data1 and insert sync_bytes between data payloads */ + os_memmove((char *) ((char *)desc[i] + media_desc[media_n]->l2_offset + sizeof(sync_bytes)), + (char *) ((char *)desc[i] + media_desc[media_n]->l2_offset + sizeof(sync_bytes) + CVF_H264_STAP_A_NALU_SIZE_HDR_SIZE), + nalu_data_size); + /* Add sync bytes for data2 */ + *(u32 *)((char *)desc[i] + media_desc[media_n]->l2_offset + sizeof(sync_bytes) + nalu_data_size) = htonl(sync_bytes); + } + + is_aggregation = 1; + break; + } + case CVF_H264_NALU_TYPE_STAP_B: + case CVF_H264_NALU_TYPE_MTAP16: + case CVF_H264_NALU_TYPE_MTAP24: + /* still not handled by the software */ + os_log(LOG_DEBUG, "stream_id(%016"PRIx64") CVF_H264 NALU TYPE %d still not handled\n", ntohll(stream->id), nalu_header->type); + stream->stats.format_err++; + free_rx_desc = 1; + goto next_iteration; + case CVF_H264_NALU_TYPE_FU_B: + case CVF_H264_NALU_TYPE_FU_A: + { + nalu_fu_header = (struct cvf_h264_nalu_fu_header *)(nalu_header + 1); + is_last_fu = 0; + if (nalu_fu_header->s) { /* NALU starts here */ + struct cvf_h264_nalu_header temp_nalu_header; + int temp_offset; + + /* Reconstruct NALU header based on the type defined in the FU header */ + temp_nalu_header.f = nalu_header->f; + temp_nalu_header.nri = nalu_header->nri; + temp_nalu_header.type = nalu_fu_header->type; + + temp_offset = sizeof(sync_bytes) - sizeof(struct cvf_h264_nalu_header); + media_desc[media_n]->l2_offset -= temp_offset; + media_desc[media_n]->len += temp_offset; + + /* Add 4 bytes sync_bytes */ + *(u32 *)((char *)desc[i] + media_desc[media_n]->l2_offset) = htonl(sync_bytes); + /* Replace NALU header after the sync_bytes */ + *(struct cvf_h264_nalu_header *)((char *)desc[i] + media_desc[media_n]->l2_offset + sizeof(sync_bytes)) = temp_nalu_header; + } else { /* Not a starting NALU packet, strip the 2 NALU header bytes */ + media_desc[media_n]->l2_offset += 2; + media_desc[media_n]->len -= 2; + if(nalu_fu_header->e) + is_last_fu = 1; + } + break; + } + default: /* SINGLE NAL Unit packet */ + { + /* The entire payload including the NAL unit header is the output */ + /* Just add the sync bytes */ + int temp_offset; + + is_single_packet = 1; + temp_offset = sizeof(sync_bytes); + + media_desc[media_n]->l2_offset -= temp_offset; + media_desc[media_n]->len += temp_offset; + + *(u32 *)((char *)desc[i] + media_desc[media_n]->l2_offset) = htonl(sync_bytes); + break; + } + } + + if (media_desc[media_n]->flags) { + /* Make room for packet-level flags by reserving one event. + * We cannot fit all possible flags in media_desc->avtp_ts[x].flags, so here we + * only reserve some space in the ts/event array to simplify accounting later on. The + * packet-level flags (from media_desc[media_n]->flags) will be posted to the media application + * by the media driver. */ + + media_desc[media_n]->avtp_ts[0].offset = 0; + media_desc[media_n]->avtp_ts[0].flags = AVTP_FLAGS_TO_MEDIA_DESC(AVTP_TIMESTAMP_INVALID); + media_desc[media_n]->n_ts = 1; + } else + media_desc[media_n]->n_ts = 0; + + /* Signal the End-of-Frame (end of NALU): + * - Marker bit is present : meaning end of Access Unit but not too reliable + * - The end of a fragmentation unit packets forming a NALU + * - On every Single NALU packet + * - FIXME: for the STAP/MTAP, ideally we should send as end-of-frames as number of NALUs + * - FIXME: We should send avtp timestamp with the h264 timestamp, for now we send + only the h264 timestamp as it represents the presentation timestamp*/ + + if (h264_avtp_hdr->M || (is_last_fu) || (is_single_packet) || (is_aggregation)) { + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].offset = media_desc[media_n]->len - 1; // Signal the End-of-Frame on the last byte of the packet + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].flags = AVTP_FLAGS_TO_MEDIA_DESC(AVTP_END_OF_FRAME); + /*Here we send the h264_timestamp using the avtp timestamp flag*/ + if (!h264_avtp_hdr->ptv) { + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].val = 0; + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].flags |= AVTP_FLAGS_TO_MEDIA_DESC(AVTP_TIMESTAMP_INVALID); + } else + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].val = h264_timestamp; + + /*If AVTP timestamp is valid, use it for stats calculation*/ + if (!(desc[i]->flags & AVTP_TIMESTAMP_INVALID) && !stats) { + stats = 1; + avtp_latency_stats(stream, desc[i]); + } + media_desc[media_n]->n_ts++; + } + + media_n++; +next_iteration: + if(free_rx_desc) { + desc[i]->desc.l2_offset = l2_offset_orig; + desc[i]->desc.len = len_orig; + net_rx_free(&desc[i]->desc); + } + } + + stream_media_tx(stream, media_desc, media_n); +} + + +/** Handles transmission of cvf-h264 avtp packets + * + * Reads data from media stack, converts media descriptors to network descriptors, including protocol encapsulation, + * and transmit packets. AVTP timestamps are read from media clock generation layer. + * + * \return none + * \param stream pointer to talker stream context + */ +static void avtp_cvf_h264_net_tx(struct stream_talker *stream) +{ + u32 i; + struct media_rx_desc *media_desc_array[NET_TX_BATCH], *media_desc; + struct net_tx_desc *net_desc; + u32 ts[TS_TX_BATCH]; + int rc; + int ts_n = 0,ts_idx; + unsigned int ts_batch, frames_in_packet = 0; + unsigned int flags = 0; + unsigned int partial,end_of_frame,set_single_packet, media_len; + unsigned int alignment_ts = 0; + unsigned start_fu = 0; + u8 * buf = NULL; + struct avtp_cvf_h264_hdr *cvf_hdr = NULL; + u8 * hdr_buf = NULL; + + /* Get H264 data from media stack */ + if (unlikely((rc = stream_media_rx(stream, media_desc_array, ts, &flags, &alignment_ts)) < 0)) { + goto media_rx_fail; + } + + /*Fetch Maximum Timestamps which will be stream->tx_batch */ + ts_batch = stream->tx_batch; + + stream->consumer.gptp_current = stream->gptp_current; + ts_n = media_clock_gen_get_ts(&stream->consumer, ts, ts_batch, &flags, alignment_ts); + + if (ts_n != ts_batch) { + stream->stats.clock_err++; + goto media_clock_fail; + } + if (flags & MCG_FLAGS_RESET) { + avtp_data_header_toggle_mcr(stream->avtp_hdr); + } + + stream->stats.clock_rx += ts_n; + + i = 0; + + media_len = 0; + ts_idx = 0; + + while (i < rc) { + media_desc = media_desc_array[i]; + net_desc = &media_desc->net; + media_len = net_desc->len; + start_fu = 0; + set_single_packet = 0; + partial = net_desc->flags & NET_TX_FLAGS_PARTIAL; + end_of_frame = net_desc->flags & NET_TX_FLAGS_END_FRAME; + net_desc->flags = 0; + + buf = NET_DATA_START(net_desc); + + /*Check if we expect a new NALU */ + if (!(stream->subtype_data.cvf_h264.prev_incomplete_nal)) + { + /*Check if it's a start of an FU-A packets or a single packet NALU */ + if (buf[0] == CVF_H264_NALU_TYPE_FU_A) + start_fu = 1; + else + set_single_packet = 1; + } + + /*Store the new nalu header depending on the received packet */ + if (set_single_packet) + stream->subtype_data.cvf_h264.nalu_header = buf[0]; + else if (start_fu) + stream->subtype_data.cvf_h264.nalu_header = buf[1]; + + if (!set_single_packet) + { + u8 * nalu_fu_header_buf = NULL; + struct cvf_h264_nalu_fu_header *fu_hdr = NULL; + struct cvf_h264_nalu_header *nalu_hdr = NULL; + + /*Set the FU indicator*/ + nalu_hdr = (struct cvf_h264_nalu_header *) buf; + nalu_hdr->f = 0; /*forbidden zero bit*/ + nalu_hdr->nri = ((stream->subtype_data.cvf_h264.nalu_header & 0x60) >> 5); + nalu_hdr->type = CVF_H264_NALU_TYPE_FU_A; + + /*Set the FU header*/ + nalu_fu_header_buf = buf + sizeof(struct cvf_h264_nalu_header); + + fu_hdr = (struct cvf_h264_nalu_fu_header *) nalu_fu_header_buf; + /*Set FU header to 0*/ + os_memset(fu_hdr, 0, sizeof(struct cvf_h264_nalu_fu_header)); + fu_hdr->type = stream->subtype_data.cvf_h264.nalu_header & 0x1f; + fu_hdr->r = 0; + + if(start_fu) + fu_hdr->s = 1; /*This is the first FU packet of the NALU*/ + else if (end_of_frame) + fu_hdr->e = 1; /*This is the last FU packet of the NALU*/ + } + + /*Only last packet of a NALU contains avtp timestamp*/ + if (end_of_frame) { + + avtp_data_header_set_timestamp(stream->avtp_hdr, ts[ts_idx]); + + stream->ts_n++; + ts_idx++; + } else + avtp_data_header_set_timestamp_invalid(stream->avtp_hdr); + + /*Set the right stream_data_length*/ + avtp_data_header_set_len(stream->avtp_hdr, sizeof(struct cvf_h264_hdr) + net_desc->len); + + cvf_hdr = (struct avtp_cvf_h264_hdr *) stream->avtp_hdr; + + /*Check if the NALU time is correctly sent (should be only sent with the first bytes of the NALU)*/ + if (!(media_desc->ts_n) && (set_single_packet || start_fu)) { + /*This is a start of NALU with no timestamps sent: + * Mark as invalid*/ + stream->subtype_data.cvf_h264.is_nalu_ts_valid = 0; + stream->stats.clock_invalid++; + } else if ((media_desc->ts_n && !(set_single_packet || start_fu))) { + /*This is a timestamp in a middle of NALU: + * Increment error stat but keep using the last valid timestamp for the rest of the fragments*/ + stream->stats.clock_invalid++; + } else if (media_desc->ts_n) { + /*This is a timestamp in at the start of NALU: + * Mark as valid and save for the rest of the fragments*/ + stream->subtype_data.cvf_h264.is_nalu_ts_valid = 1; + stream->subtype_data.cvf_h264.h264_timestamp = media_desc->avtp_ts[0].val; + } + + if(stream->subtype_data.cvf_h264.is_nalu_ts_valid){ + cvf_hdr->ptv = 1; + stream->subtype_data.cvf_h264.h264_hdr->h264_timestamp = htonl(stream->subtype_data.cvf_h264.h264_timestamp); + } else { + cvf_hdr->ptv = 0; + stream->subtype_data.cvf_h264.h264_hdr->h264_timestamp = htonl(0); + } + + net_desc->l2_offset -= stream->header_len; + net_desc->len += stream->header_len; + net_desc->flags = 0; + hdr_buf = NET_DATA_START(net_desc); + + os_memcpy(hdr_buf, stream->header_template, stream->header_len); + + if (end_of_frame) + stream->subtype_data.cvf_h264.prev_incomplete_nal = 0; + else + stream->subtype_data.cvf_h264.prev_incomplete_nal = 1; + + stream->avtp_hdr->sequence_num++; + + if (partial) + frames_in_packet = media_len; /*sample_stride for h264 is equal to 1 */ + else + frames_in_packet = stream->frames_per_packet; + + stream->media_count += frames_in_packet; + + i++; + } + + if (stream_net_tx(stream, media_desc_array, i)) + goto transmit_fail; + + return; +media_clock_fail: + net_free_multi((void **)media_desc_array, rc); + + return; +media_rx_fail: + stream->media_count = 0; + return; +transmit_fail: + return; + +} + +static void cvf_mjpeg_net_rx(struct stream_listener *stream, struct avtp_rx_desc **desc, unsigned int n) +{ + struct cvf_mjpeg_hdr *mjpeg_hdr; + struct media_desc *media_desc[NET_RX_BATCH]; + struct avtp_cvf_mjpeg_hdr *mjpeg_avtp_hdr; + unsigned int media_n = 0; + unsigned int stats = 0; + unsigned int scan_type; + unsigned int fragment_offset; + int i; + struct avdecc_format_cvf_mjpeg_t *mjpeg; + + os_log(LOG_DEBUG, "enter stream(%p) stream_id(%016"PRIx64") \n", stream, ntohll(stream->id)); + + for (i = 0; i < n; i++) { + + mjpeg_avtp_hdr = (struct avtp_cvf_mjpeg_hdr *)((char *)desc[i] + desc[i]->desc.l3_offset); + mjpeg_hdr = (struct cvf_mjpeg_hdr *)((char *)desc[i] + desc[i]->l4_offset); + + if (unlikely((mjpeg_avtp_hdr->format != CVF_FORMAT_RFC) + || (mjpeg_avtp_hdr->format_subtype != CVF_FORMAT_SUBTYPE_MJPEG))) { + stream->stats.format_err++; + os_log(LOG_DEBUG,"stream_id(%016"PRIx64") CVF MJPEG not recognized: format %u format_subtype %u rsvd %u\n", ntohll(stream->id), mjpeg_avtp_hdr->format, mjpeg_avtp_hdr->format_subtype, mjpeg_avtp_hdr->reserved); + net_rx_free(&desc[i]->desc); + + /* FIXME we should indicate packets were lost */ + continue; // Should never happen if stream _is_ CVF-MJPEG + } + + mjpeg = &stream->format.u.s.subtype_u.cvf.format_u.mjpeg; + if (unlikely(mjpeg->type != mjpeg_hdr->type)) { + stream->stats.format_err++; + os_log(LOG_DEBUG,"stream_id(%016"PRIx64") CVF MJPEG type mismatch: configured = %u but in-stream = %u\n", ntohll(stream->id), mjpeg->type, mjpeg_hdr->type); + } + + scan_type = mjpeg_hdr->type_specific == CVF_MJPEG_TYPE_SPEC_PROGRESSIVE ? + CVF_MJPEG_P_PROGRESSIVE : CVF_MJPEG_P_INTERLACE; + if (unlikely(mjpeg->p != scan_type)) { + stream->stats.format_err++; + os_log(LOG_DEBUG,"stream_id(%016"PRIx64") CVF MJPEG scan type mistmatch: configured = %s but in-stream = %s\n", + ntohll(stream->id), SCAN_TYPE_2_STR(mjpeg->p), SCAN_TYPE_2_STR(scan_type)); + } + + if (unlikely((mjpeg->width != mjpeg_hdr->width) + || (mjpeg->height != mjpeg_hdr->height))) { + stream->stats.format_err++; + os_log(LOG_DEBUG,"stream_id(%016"PRIx64") CVF MJPEG frame size mistmatch: configured = %ux%u but in-stream = %du%u\n", + ntohll(stream->id), mjpeg->width, mjpeg->height, mjpeg_hdr->width, mjpeg_hdr->height); + } + + + /* FIXME The code below works but is very sensitive to operation order, because the source and destination structs + * actually point to the same memory area. Need to make it safer to avoid overwriting a valid field by mistake. + */ + media_desc[media_n] = (struct media_desc *)desc[i]; + media_desc[media_n]->l2_offset = desc[i]->l4_offset + sizeof(struct cvf_mjpeg_hdr); + media_desc[media_n]->len = desc[i]->l4_len - sizeof(struct cvf_mjpeg_hdr); + fragment_offset = (mjpeg_hdr->fragment_offset_msb << 16) + ntohs(mjpeg_hdr->fragment_offset_lsb); + media_desc[media_n]->bytes_lost = 0; + if (fragment_offset != stream->subtype_data.cvf.current_frame_offset) { + stream->stats.format_err++; + if (fragment_offset > stream->subtype_data.cvf.current_frame_offset) + media_desc[media_n]->bytes_lost = fragment_offset - stream->subtype_data.cvf.current_frame_offset; + else + os_log(LOG_DEBUG, "stream_id(%016"PRIx64") CVF MJPEG desc(%p) fragment offset invalid (current packet overlaps %d bytes (%u - %u) of previous frame).\n", + ntohll(stream->id), desc[i], stream->subtype_data.cvf.current_frame_offset - fragment_offset, + stream->subtype_data.cvf.current_frame_offset, fragment_offset); + stream->subtype_data.cvf.current_frame_offset = fragment_offset; + + //Gather both cases under AVTP_PACKET_LOST to notify the media application + //FIXME Add new report flags? + desc[i]->flags &= AVTP_PACKET_LOST; + } + stream->subtype_data.cvf.current_frame_offset += media_desc[media_n]->len; + + media_desc[media_n]->flags = desc[i]->flags & (AVTP_MEDIA_CLOCK_RESTART | AVTP_PACKET_LOST | AVTP_TIMESTAMP_UNCERTAIN); + media_desc[media_n]->ts = desc[i]->desc.ts; // This is actually a copy onto itself. Kept for clarity... + + + if (media_desc[media_n]->flags) { + /* Make room for packet-level flags by reserving one event. + * We cannot fit all possible flags in media_desc->avtp_ts[x].flags (only 4 bits), so here we + * only reserve some space in the ts/event array to simplify accounting later on. The + * packet-level flags (from media_desc[media_n]->flags) will be posted to the media application + * by the media driver. */ + media_desc[media_n]->avtp_ts[0].offset = 0; + media_desc[media_n]->avtp_ts[0].flags = AVTP_FLAGS_TO_MEDIA_DESC(AVTP_TIMESTAMP_INVALID); + media_desc[media_n]->n_ts = 1; + } else + media_desc[media_n]->n_ts = 0; + + + if (mjpeg_avtp_hdr->M) { + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].offset = media_desc[media_n]->len - 1; // Signal the End-of-Frame on the last byte of the packet + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].flags = AVTP_FLAGS_TO_MEDIA_DESC(AVTP_END_OF_FRAME); + if (desc[i]->flags & AVTP_TIMESTAMP_INVALID) { + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].val = 0; + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].flags |= AVTP_FLAGS_TO_MEDIA_DESC(AVTP_TIMESTAMP_INVALID); + } else { + media_desc[media_n]->avtp_ts[media_desc[media_n]->n_ts].val = desc[i]->avtp_timestamp; + + if (!stats) { + stats = 1; + avtp_latency_stats(stream, desc[i]); + } + } + media_desc[media_n]->n_ts++; + stream->subtype_data.cvf.current_frame_offset = 0; + } + + media_n++; + } + + stream_media_tx(stream, media_desc, media_n); +} + + +/* P1722_D14 chapter I.2.1.4.1 */ +int stream_cvf_mjpeg_check_format(struct stream_listener const *stream, struct avdecc_format_cvf_mjpeg_t const *mjpeg) +{ + int rc = GENAVB_SUCCESS; + + /* width and height should be > 0 */ + if ((!mjpeg->width || !mjpeg->height)) { + os_log(LOG_ERR, "stream_id(%016"PRIx64") MJPEG height/width (%u,%u) parameters cannot be 0\n", + ntohll(stream->id), mjpeg->height, mjpeg->width); + goto err_format; + } + + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + +static int listener_stream_cvf_init(struct stream_listener *stream) +{ + struct avdecc_format const *format = &stream->format; + + switch (format->u.s.subtype_u.cvf.subtype) { /* P1722_D14 table 20 */ + case CVF_FORMAT_SUBTYPE_MJPEG: + stream->net_rx = cvf_mjpeg_net_rx; + stream->subtype_data.cvf.current_frame_offset = 0; + break; + case CVF_FORMAT_SUBTYPE_H264: + stream->net_rx = avtp_cvf_h264_net_rx; + break; + + default: + break; + } + + return 0; +} + +/* P1722_D14 chapter I.2.1.4 */ +int listener_stream_cvf_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags) +{ + int rc = GENAVB_SUCCESS; + + switch (format->u.s.subtype_u.cvf.format) { /* P1722_D14 table 19 */ + case CVF_FORMAT_RFC: + break; + + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") Compressed Video Format (%u) not supported\n", + ntohll(stream->id), format->u.s.subtype_u.cvf.format); + goto err_format; + } + + if (flags & IPC_AVTP_FLAGS_MCR) { + os_log(LOG_ERR, "stream_id(%016"PRIx64") Media clock recovery not supported\n", ntohll(stream->id)); + goto err_format; + } + + switch (format->u.s.subtype_u.cvf.subtype) { /* P1722_D14 table 20 */ + case CVF_FORMAT_SUBTYPE_MJPEG: + rc = stream_cvf_mjpeg_check_format(stream, &format->u.s.subtype_u.cvf.format_u.mjpeg); + break; + + case CVF_FORMAT_SUBTYPE_H264: + /* Nothing to care for now */ + break; + + /* unsupported formats for now */ + case CVF_FORMAT_SUBTYPE_JPEG2000: + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") CVF subtype (%u) not supported\n", + ntohll(stream->id), format->u.s.subtype_u.cvf.subtype); + goto err_format; + } + + stream->init = listener_stream_cvf_init; + + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + + +static unsigned int cvf_prepare_header(struct avtp_cvf_hdr *hdr, const struct avdecc_format *format, void *stream_id) +{ + /* AVTP stream common fields */ + hdr->subtype = AVTP_SUBTYPE_CVF; + hdr->version = AVTP_VERSION_0; + hdr->sv = 1; + + copy_64(&hdr->stream_id, stream_id); + + /* Format fields */ + hdr->format = format->u.s.subtype_u.cvf.format; + hdr->format_subtype = format->u.s.subtype_u.cvf.subtype; + + hdr->evt = 0; + + return sizeof(struct avtp_cvf_hdr); +} + + +static unsigned int avtp_cvf_h264_prepare_header(struct avtp_data_hdr *hdr, const struct avdecc_format *format) +{ + /*The h264 header contain only h264 timestamp, return the size only for now*/ + return sizeof(struct cvf_h264_hdr); +} + + +static void talker_stream_cvf_init(struct stream_talker *stream, unsigned int *hdr_len) +{ + struct avdecc_format const *format = &stream->format; + + switch (format->u.s.subtype_u.cvf.subtype) { + case CVF_FORMAT_SUBTYPE_H264: + stream->subtype_data.cvf_h264.h264_hdr = (struct cvf_h264_hdr *)(stream->avtp_hdr + 1); + stream->subtype_data.cvf_h264.prev_incomplete_nal = 0; + stream->subtype_data.cvf_h264.h264_timestamp = 0; + stream->subtype_data.cvf_h264.is_nalu_ts_valid = 0; + stream->net_tx = avtp_cvf_h264_net_tx; + *hdr_len = avtp_cvf_h264_prepare_header(stream->avtp_hdr, format); + break; + default: + break; + } + + *hdr_len += cvf_prepare_header((struct avtp_cvf_hdr *)stream->avtp_hdr, format, &stream->id); + + stream->common.flags |= STREAM_FLAG_CLOCK_GENERATION; + +} +/* Check stream format and initialize parameters */ +int talker_stream_cvf_check(struct stream_talker *stream, struct avdecc_format const *format, + struct ipc_avtp_connect *ipc) +{ + int rc = GENAVB_SUCCESS; + + switch (format->u.s.subtype_u.cvf.format) { + case CVF_FORMAT_RFC: + break; + + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") Compressed Video Format (%u) not supported\n", + ntohll(stream->id), format->u.s.subtype_u.cvf.format); + goto err_format; + } + + switch (format->u.s.subtype_u.cvf.subtype) { + + case CVF_FORMAT_SUBTYPE_H264: + /* Nothing to care for now */ + break; + + /* unsupported formats for now */ + case CVF_FORMAT_SUBTYPE_MJPEG: + case CVF_FORMAT_SUBTYPE_JPEG2000: + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") CVF subtype (%u) not supported\n", + ntohll(stream->id), format->u.s.subtype_u.cvf.subtype); + goto err_format; + } + + /*FIXME Assume nothing to do for now*/ + stream->init = talker_stream_cvf_init; + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + +#endif /* CFG_AVTP_1722A */ diff --git a/avtp/cvf.h b/avtp/cvf.h new file mode 100644 index 0000000..c10c6e8 --- /dev/null +++ b/avtp/cvf.h @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP compressed video format (CVF) handling functions + @details +*/ + +#ifndef _CVF_H_ +#define _CVF_H_ + +#ifdef CFG_AVTP_1722A +#include "common/net.h" +#include "genavb/cvf.h" +#include "genavb/avdecc.h" + +#include "stream.h" + +int listener_stream_cvf_check(struct stream_listener *stream, struct avdecc_format const *format, u16 flags); +int talker_stream_cvf_check(struct stream_talker *stream, struct avdecc_format const *format, + struct ipc_avtp_connect *ipc); + +#endif + +#endif /* _CVF_H_ */ diff --git a/avtp/linux/avtp.cmake b/avtp/linux/avtp.cmake new file mode 100644 index 0000000..c980256 --- /dev/null +++ b/avtp/linux/avtp.cmake @@ -0,0 +1 @@ +genavb_target_add_srcs(TARGET ${avb} SRCS main.c) diff --git a/avtp/linux/main.c b/avtp/linux/main.c new file mode 100644 index 0000000..82d9c2f --- /dev/null +++ b/avtp/linux/main.c @@ -0,0 +1,311 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVTP linux specific code + @details Setups linux thread for AVTP stack component. Implements AVTP main loop and event handling. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/net.h" +#include "common/ipc.h" +#include "common/list.h" +#include "common/avtp.h" +#include "common/timer.h" +#include "common/log.h" + +#include "os/sys_types.h" +#include "os/clock.h" + +#include "avtp/avtp_entry.h" + +#include "linux/avb.h" + +#define EPOLL_MAX_EVENTS 8 +#define EPOLL_TIMEOUT_MS 10 +#define IPC_POOLING_PERIOD_NS (10ULL * NSECS_PER_MS) +#define STATS_PERIOD_NS (10ULL * NSECS_PER_SEC) + +/* Linux specific AVTP code entry points */ + +static void stats_thread_cleanup(void *arg) +{ + struct ipc_rx *ipc_rx_stats = arg; + + ipc_rx_exit(ipc_rx_stats); + + os_log(LOG_INIT, "done\n"); +} + + +static void *stats_thread_main(void *arg) +{ + struct ipc_rx ipc_rx_stats; + int epoll_fd; + struct epoll_event event[EPOLL_MAX_EVENTS]; + struct sched_param param = { + .sched_priority = STATS_CFG_PRIORITY, + }; + int rc; + + rc = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); + if (rc) { + os_log(LOG_ERR, "pthread_setschedparam(), %s\n", strerror(rc)); + goto err_setschedparam; + } + + epoll_fd = epoll_create(1); + if (epoll_fd < 0) { + os_log(LOG_CRIT, "epoll_create(), %s\n", strerror(errno)); + goto err_epoll_create; + } + + if (ipc_rx_init(&ipc_rx_stats, IPC_AVTP_STATS, stats_ipc_rx, epoll_fd) < 0) + goto err_ipc_rx; + + pthread_cleanup_push(stats_thread_cleanup, &ipc_rx_stats); + + os_log(LOG_INIT, "started\n"); + + while (1) { + int ready, i; + struct linux_epoll_data *epoll_data; + + /* thread main loop */ + /* use epoll to wait for events from all open file descriptors */ + + pthread_testcancel(); + + ready = epoll_wait(epoll_fd, event, EPOLL_MAX_EVENTS, -1); + if (ready < 0) { + if (errno == EINTR) + continue; + + os_log(LOG_CRIT, "epoll_wait(), %s\n", strerror(errno)); + break; + } + + for (i = 0; i < ready; i++) { + if (event[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) + os_log(LOG_ERR, "event error, 0x%x, data = 0x%llx\n", event[i].events, event[i].data.u64); + + if (event[i].events & EPOLLIN) { + epoll_data = (struct linux_epoll_data *)event[i].data.ptr; + if (epoll_data->type == EPOLL_TYPE_IPC) + ipc_rx((struct ipc_rx *)epoll_data->ptr); + } + } + } + + pthread_cleanup_pop(1); + + close(epoll_fd); + + return (void *)0; + +err_ipc_rx: + close(epoll_fd); + +err_epoll_create: +err_setschedparam: + return (void *)-1; +} + +static void avtp_thread_cleanup(void *arg) +{ + struct avb_ctx *avb = arg; + struct avtp_ctx *avtp = avb->avtp; + + avtp_exit(avtp); + + avb->avtp = NULL; + + os_log(LOG_INIT, "done\n"); +} + +static void avtp_status(struct avb_ctx *avb, int status) +{ + pthread_mutex_lock(&avb->status_mutex); + + avb->avtp_status = status; + + pthread_cond_signal(&avb->avtp_cond); + + pthread_mutex_unlock(&avb->status_mutex); +} + +void *avtp_thread_main(void *arg) +{ + struct avb_ctx *avb = arg; + struct avtp_ctx *avtp; + int epoll_fd; + pthread_t stats_thread; + struct epoll_event event[EPOLL_MAX_EVENTS]; + struct sched_param param = { + .sched_priority = AVTP_CFG_PRIORITY, + }; + struct timespec tp; + u64 current_time = 0, previous_time = 0, ipc_time = 0, stats_time = 0; + struct process_stats stats; + int rc; + + rc = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); + if (rc) { + os_log(LOG_ERR, "pthread_setschedparam(), %s\n", strerror(rc)); + goto err_setschedparam; + } + + epoll_fd = epoll_create(1); + if (epoll_fd < 0) { + os_log(LOG_CRIT, "epoll_create(), %s\n", strerror(errno)); + goto err_epoll_create; + } + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &tp) == 0) { + current_time = tp.tv_sec * (u64)NSECS_PER_SEC + tp.tv_nsec; + + previous_time = current_time; + } + + rc = pthread_create(&stats_thread, NULL, stats_thread_main, NULL); + if (rc) { + os_log(LOG_CRIT, "pthread_create(): %s\n", strerror(rc)); + goto err_pthread_create; + } + + avtp = avtp_init(&avb->avtp_cfg, epoll_fd); + if (!avtp) + goto err_avtp_init; + + avb->avtp = avtp; + + pthread_cleanup_push(avtp_thread_cleanup, avb); + + os_log(LOG_INIT, "started\n"); + + avtp_status(avb, 1); + + stats_init(&stats.events, 31, NULL, NULL); + stats_init(&stats.sched_intvl, 31, NULL, NULL); + stats_init(&stats.processing_time, 31, NULL, NULL); + + while (1) { + int ready, i; + struct linux_epoll_data *epoll_data; + + /* thread main loop */ + /* use epoll to wait for events from all open file descriptors */ + + pthread_testcancel(); + + ready = epoll_wait(epoll_fd, event, EPOLL_MAX_EVENTS, EPOLL_TIMEOUT_MS); + if (ready < 0) { + if (errno == EINTR) + continue; + + os_log(LOG_CRIT, "epoll_wait(), %s\n", strerror(errno)); + break; + } + + stats_update(&stats.events, ready); + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &tp) == 0) { + current_time = tp.tv_sec * (u64)NSECS_PER_SEC + tp.tv_nsec; + + stats_update(&stats.sched_intvl, current_time - previous_time); + previous_time = current_time; + } + + for (i = 0; i < ready; i++) { + if (event[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) + os_log(LOG_ERR, "event error, 0x%x, data = 0x%llx\n", event[i].events, event[i].data.u64); + + if (event[i].events & EPOLLIN) { + epoll_data = (struct linux_epoll_data *)event[i].data.ptr; + + switch (epoll_data->type) { + case EPOLL_TYPE_NET_RX: + net_rx_multi((struct net_rx *)epoll_data->ptr); + break; + + case EPOLL_TYPE_TIMER: + os_timer_process((struct os_timer *)epoll_data->ptr); + break; + + case EPOLL_TYPE_MEDIA: + avtp_media_event(epoll_data->ptr); + break; + + default: + break; + } + } + + if (event[i].events & EPOLLOUT) { + epoll_data = (struct linux_epoll_data *)event[i].data.ptr; + + switch (epoll_data->type) { + case EPOLL_TYPE_NET_TX_EVENT: + avtp_net_tx_event(epoll_data->ptr); + break; + default: + break; + } + } + } + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &tp) == 0) { + current_time = tp.tv_sec * (u64)NSECS_PER_SEC + tp.tv_nsec; + + if ((current_time - ipc_time) > IPC_POOLING_PERIOD_NS) { + avtp_ipc_rx(avtp); + avtp_stream_free(avtp, current_time); + ipc_time = current_time; + } + + if ((current_time - stats_time) > STATS_PERIOD_NS) { + avtp_stats_dump(avtp, &stats); + stats_time = current_time; + } + + stats_update(&stats.processing_time, current_time - previous_time); + } + } + + pthread_cleanup_pop(1); + + pthread_cancel(stats_thread); + pthread_join(stats_thread, NULL); + + close(epoll_fd); + + return (void *)0; + +err_avtp_init: + pthread_cancel(stats_thread); + pthread_join(stats_thread, NULL); + +err_pthread_create: + close(epoll_fd); + +err_epoll_create: +err_setschedparam: + avtp_status(avb, -1); + + return (void *)-1; +} diff --git a/avtp/media_clock.c b/avtp/media_clock.c new file mode 100644 index 0000000..fa96ae0 --- /dev/null +++ b/avtp/media_clock.c @@ -0,0 +1,1169 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Media clock interface handling + @details +*/ +#include "os/stdlib.h" +#include "os/clock.h" + +#include "common/log.h" + +#include "media_clock.h" +#include "clock_domain.h" +#include "avtp.h" +#include "stream.h" + +/* + * ENET compare value restriction + * increment <= compare value <= (ENET_ATPER[PERIOD] - increment) + */ +static inline u32 ts_wa(u32 ts) +{ + if (ts < 0x8) + return 0x8; + else if (ts > 0xfffffff7) + return 0xfffffff7; + else + return ts; +} + + +/** Writes a timestamp into the array shared with the lower-level recovery layer. + * \return none + * \param rec pointer to media_clock_rec context + * \param ts timestamp + */ +static void media_clock_rec_write_ts(struct media_clock_rec *rec, u32 ts) +{ + unsigned int w_idx = *rec->write_idx; + + *(rec->array_addr + w_idx) = ts_wa(ts); + + w_idx = (w_idx + 1) & (rec->array_size - 1); + + /* This allows to stop ENET generation in case no more timestamps are received */ + *(rec->array_addr + w_idx) = 0; + + *rec->write_idx = w_idx; + + rec->nb_ts_total++; + rec->nb_pending++; +} + + +/** Gets the recovery status and the number of timestamps processed by the lower-level recovery layer. + * When using DMA based recovery, it cleans the DMA descriptors. + * This function updates the media_clock_rec nb_clean_total, nb_pending and clean_idx + * fields. + * \return os_media_clock_rec_state_t media clock recovery driver state + * \param rec pointer to media_clock_rec context + */ +static os_media_clock_rec_state_t media_clock_rec_clean(struct media_clock_rec *rec) +{ + unsigned int nb_clean; + os_media_clock_rec_state_t rc; + + rc = os_media_clock_rec_clean(&rec->os, &nb_clean); + if (rc < 0) { + os_log(LOG_ERR, "clock (%p): os_media_clock_rec_clean failed\n", rec); + goto exit; + } + + if (nb_clean) { + + rec->nb_clean_total += nb_clean; + rec->nb_pending -= nb_clean; + rec->clean_idx = (rec->clean_idx + nb_clean) & (rec->array_size - 1); + } + + if (rec->nb_pending >= rec->array_size) { + os_log(LOG_CRIT, "clock(%p) REC Driver overflow, idx %d nb_clean %u nb_write %u\n", rec, *rec->write_idx, rec->nb_clean_total, rec->nb_ts_total); + rc = OS_MCR_ERROR; + } else if (!rec->nb_pending) { + os_log(LOG_CRIT, "clock(%p) REC Driver underflow, idx %d nb_clean %u nb_write %u\n", rec, *rec->write_idx, rec->nb_clean_total, rec->nb_ts_total); + rc = OS_MCR_ERROR; + } + +exit: + return rc; +} + +static inline int period_error(unsigned int p1, unsigned int p2) +{ + /* Check if the periods differ by more than (100/1024) % */ + /* Make the check here less strict than the one in the CRF code, so that + * a timestamp that was accepted by the CRF will always be accepted here + * as well (to avoid quick LOCKED/UNLOCKED sequences). + */ + if (os_abs(p1 - p2) > (p1 >> 5)) + return 1; + else + return 0; +} + +void media_clock_rec_stats_print(struct ipc_avtp_clock_rec_stats *msg) +{ + struct clock_rec_stats *stats = &msg->stats; + + os_log(LOG_INFO, "clock(%p)\n", msg->clock_id); + + os_log(LOG_INFO, "running %10u running_locked: %10u, restart: %10u, uncertain: %10u, offset: %10u, period: %10u, rec driver: %10u, crit: %10u\n", + stats->running, stats->running_locked, stats->err_restart, stats->err_uncertain, stats->err_offset, + stats->err_period, stats->err_rec_driver, stats->err_crit); + + stats_compute(&stats->period); + + os_log(LOG_INFO, "period: %d/%d/%d (ns)\n", stats->period.min, stats->period.mean, stats->period.max); +} + + +void media_clock_rec_stats_dump(struct media_clock_rec *rec, struct ipc_avtp_clock_rec_stats *msg) +{ + struct clock_rec_stats *stats = &msg->stats; + + msg->clock_id = rec; + + os_memcpy(stats, &rec->stats, sizeof(rec->stats)); + + stats_reset(&rec->stats.period); +} + +static unsigned int ts_offset(unsigned int period) +{ + /* Media clock timestamps should be at least 2 clock periods in the future (since media clock recovery */ + /* hardware mechanism pre-loads 2 timestamps in advance) plus some margin to account for software processing */ + /* Everything must be aligned to the clock period */ + return 2 * period + ((MCR_DELAY + period - 1) / period) * period; +} + +/** Main media clock recovery function. + * Takes in argument an array of timestamps and performs measurements + * and sanity checks before providing the timestamps + * to the lower-level recovery mechanism. + * \return media clock state + * \param rec pointer to media_clock_rec context + * \param ts pointer to an array of timestamp struct + * \param num_ts number of elements in the array + */ +media_clock_rec_state_t media_clock_rec(struct media_clock_rec *rec, struct timestamp *ts, int num_ts) +{ + int i = 0; + u32 local_time = 0, curr_ts, period; + int offset, rc; + + /* Un-recoverable error state, exit */ + if ((rec->state == ERR) || (!rec->ts_period)) { + rec->stats.err_crit++; + goto exit; + } + +restart: + if (os_clock_gettime32(avtp_to_clock(CFG_DEFAULT_PORT_ID), &local_time) < 0) + goto exit; + + for (; i < num_ts; i++, ts++) { + + if (unlikely(ts->flags & AVTP_MEDIA_CLOCK_RESTART)) { +// os_log(LOG_ERR, "clock(%p) media clock restart\n", rec); + + rec->stats.err_restart++; + + rec->state = INIT; + + /* clear the flag so it won't trigger again */ + ts->flags &= ~AVTP_MEDIA_CLOCK_RESTART; + + goto restart; + } + + if (unlikely(ts->flags & AVTP_TIMESTAMP_UNCERTAIN)) { +// os_log(LOG_ERR, "clock(%p) timestamp uncertain\n", rec); + + rec->stats.err_uncertain++; + + rec->state = INIT; + + /* skip this timestamp */ + i++, ts++; + + goto restart; + } + + curr_ts = ts->ts_nsec; + + period = curr_ts - rec->last_ts; + + switch (rec->state) { + case INIT: + offset = curr_ts - local_time; + /* maximal accepted offset value */ + rec->max_offset_val = min((3 * rec->array_size / 4) * period, 100 * NS_PER_MS); + + /* Initialize with default period */ + rec->delay = ts_offset(rec->ts_period); + + if ((offset + (int)rec->delay) < 0) { +// os_log(LOG_ERR, "clock(%p) invalid timestamp, offset %u, ts %u, local %u\n", rec, offset, curr_ts, local_time); + + rec->stats.err_offset++; + + break; + } + + rec->state = MEASUREMENT; + rec->period_mean = 0; + rec->offset_max = offset; + rec->offset_min = offset; + *rec->write_idx = 0; + rec->clean_idx = 0; + rec->nb_clean_total = 0; + rec->nb_meas = 0; + rec->nb_ts_total = 0; + rec->nb_pending = 0; + + stats_reset(&rec->stats.period); + + if (rec->flags & MCR_FLAGS_RUNNING) { + if (os_media_clock_rec_stop(&rec->os) < 0) { + /* Un-recoverable error */ + rec->state = ERR; + break; + } + if (os_media_clock_rec_reset(&rec->os) < 0) { + /* Un-recoverable error */ + rec->state = ERR; + break; + } + rec->flags &= ~MCR_FLAGS_RUNNING; + } + + break; + + case MEASUREMENT: + offset = curr_ts - local_time; + + if (((offset + (int)rec->delay) < 0) || ((offset + (int)rec->delay) > rec->max_offset_val)) { + + rec->stats.err_offset++; + rec->state = INIT; + + continue; + } + + if (period > MCR_MAX_PERIOD) { + +// os_log(LOG_ERR, "clock(%p) invalid timestamp frequency, ts %u, period %d\n", rec, curr_ts, period); + + rec->stats.err_period++; + rec->state = INIT; + + continue; + } + + /* For now we don't do much with the measurements but they will be needed for + * supporting various clock stream input + */ + if (rec->nb_meas != 0) + rec->period_mean = (rec->period_mean + period) / 2; + else { + rec->period_min = period; + rec->period_max = period; + rec->period_mean = period; + } + + if (period < rec->period_min) + rec->period_min = period; + + if (period > rec->period_max) + rec->period_max = period; + + if (offset < rec->offset_min) + rec->offset_min = offset; + + if (offset > rec->offset_max) + rec->offset_max = offset; + + if (++rec->nb_meas >= MCR_NB_MEAS) { + + if (period_error(rec->period_min, rec->period_max)) { +// os_log(LOG_ERR, "clock(%p) period range too big %d %d\n", rec, rec->period_min, rec->period_max); + + rec->stats.err_period++; + rec->state = INIT; + + continue; + } + + /* Adjust based on actual measured period */ + rec->delay = ts_offset(rec->period_mean); + + rec->offset_max += rec->delay; + rec->offset_min += rec->delay; + + if ((rec->offset_max < 0) || (rec->offset_min < 0)) { + rec->stats.err_offset++; + rec->state = INIT; + + continue; + } + + /* Check if DDR array buffer is sufficient */ + if ((rec->offset_max / rec->period_mean) > (3 * rec->array_size / 4)) { +// os_log(LOG_ERR, "clock(%p) offset to local time too large offset max %u, period %u\n", rec, rec->offset_max, rec->period_mean); + + rec->stats.err_offset++; + rec->state = INIT; + + continue; + } + + if (period_error(rec->ts_period, rec->period_mean)) { + rec->stats.err_period++; + rec->state = INIT; + continue; + } + + rec->state = READY; + rec->ready = 0; + } + break; + + case READY: + if (period_error(rec->ts_period, period)) { +// os_log(LOG_ERR, "clock(%p) invalid timestamp frequency, ts %u, period %d (%d)\n", rec, curr_ts, period, rec->period_mean); + rec->stats.err_period++; + rec->state = INIT; + continue; + } + + if (rec->ready < 2) + rec->ts[rec->ready] = curr_ts + rec->delay; + else if (rec->ready < 4) { + media_clock_rec_write_ts(rec, curr_ts + rec->delay); + + if (rec->ready == 3) { + if (os_media_clock_rec_start(&rec->os, ts_wa(rec->ts[0]), ts_wa(rec->ts[1])) < 0) { + /* Un-recoverable error */ + rec->state = ERR; + break; + } + rec->state = RUNNING; + rec->flags |= MCR_FLAGS_RUNNING; + } + } + + rec->ready++; + + break; + + case RUNNING: + case RUNNING_LOCKED: + if (period_error(rec->ts_period, period)) { + +// os_log(LOG_ERR, "clock(%p) invalid timestamp frequency, ts %u, period %d (%d)\n", rec, curr_ts, period, rec->period_mean); + + rec->stats.err_period++; + rec->state = INIT; + + continue; + } + + offset = curr_ts + rec->delay - local_time; + if ((offset < 0) || (offset > rec->max_offset_val)) { + + rec->stats.err_offset++; + rec->state = INIT; + + continue; + } + + rec->stats.running++; + + if (rec->state == RUNNING_LOCKED) + rec->stats.running_locked++; + + stats_update(&rec->stats.period, period); + + media_clock_rec_write_ts(rec, curr_ts + rec->delay); + + if (!(*rec->write_idx & (MCR_CLEAN_BATCH - 1))) { + rc = media_clock_rec_clean(rec); + /* Check if the media clock driver has passed to locked state */ + if (rc == OS_MCR_RUNNING_LOCKED && rec->state == RUNNING) { + + rec->state = RUNNING_LOCKED; + + os_log(LOG_INFO, "clock(%p) locked: meas period %u/%u/%u, offset %u/%u, added delay %u\n", + rec, rec->period_min, rec->period_mean, rec->period_max, + rec->offset_min, rec->offset_max, rec->delay); + } else if (rc == OS_MCR_ERROR) { + rec->state = INIT; + rec->stats.err_rec_driver++; + } + } + + break; + + default: + os_log(LOG_ERR, "Invalid media clock rec state %d\n", rec->state); + break; + } + + rec->last_ts = curr_ts; + } +exit: + return rec->state; +} + +/** Opens a media clock recovery context to sync it to a gPTP based + * media clock. + * A media clock recovery context is associated to a HW recovery system. + * \return 0 if succes or negative in case of error + * \param rec pointer to media_clock_rec structure + */ + +int media_clock_rec_open_ptp(struct media_clock_rec *rec) +{ + if ((rec->flags & MCR_FLAGS_IN_USE)) { + os_log(LOG_ERR, "clock(%p) device already opened\n", rec); + return -1; + } + + if (os_media_clock_rec_set_ptp_sync(&rec->os) < 0) { + os_log(LOG_ERR, "clock(%p) ptp sync error\n", rec); + return -1; + } + + if (os_media_clock_rec_start(&rec->os, 0, 0) < 0) { + os_log(LOG_ERR, "clock(%p) start error\n", rec); + return -1; + } + + rec->flags |= MCR_FLAGS_IN_USE; + + return 0; +} + +/** Closes a media clock recovery context. + * \return none + * \param rec pointer to media_clock_rec context + */ +void media_clock_rec_close_ptp(struct media_clock_rec *rec) +{ + media_clock_rec_close(rec); +} + +/** Opens a media clock recovery context. + * A media clock recovery context is associated to a HW recovery system. + * \return 0 if succes or negative in case of error + * \param clk_array pointer to media_clock_rec array + * \param id id in the media_clock_rec array + * \param freq frequency + */ +int media_clock_rec_open(struct media_clock_rec *rec, + unsigned int ts_freq_p, unsigned int ts_freq_q, + void *context) +{ + if (!rec) { + os_log(LOG_ERR, "clock(%p)\n", rec); + goto err; + } + + if ((rec->flags & MCR_FLAGS_IN_USE)) { + os_log(LOG_ERR, "clock(%p) device already opened\n", rec); + goto err; + } + + if (os_media_clock_rec_set_ext_ts(&rec->os) < 0) { + os_log(LOG_ERR, "clock(%p) set ext ts error\n", rec); + goto err; + } + + if (os_media_clock_rec_reset(&rec->os) < 0) { + os_log(LOG_ERR, "clock(%p) reset error\n", rec); + goto err; + } + + if (os_media_clock_rec_set_ts_freq(&rec->os, ts_freq_p, ts_freq_q) < 0) { + os_log(LOG_ERR, "clock(%p) ts freq config error\n", rec); + goto err_set_ts_freq; + } + + rec->state = INIT; + rec->flags |= MCR_FLAGS_IN_USE; + rec->ts_period = ((u64)NSECS_PER_SEC * ts_freq_q) / ts_freq_p; + + os_memset(&rec->stats, 0, sizeof(rec->stats)); + stats_init(&rec->stats.period, 31, NULL, NULL); + + os_log(LOG_INFO, "clock(%p) ts freq: %u/%u = %u Hz, period: %u ns\n", + rec, ts_freq_p, ts_freq_q, ts_freq_p / ts_freq_q, rec->ts_period); + + return 0; + +err_set_ts_freq: +err: + return -1; +} + +/** Closes a media clock recovery context. + * \return none + * \param rec pointer to media_clock_rec context + */ +void media_clock_rec_close(struct media_clock_rec *rec) +{ + if (rec->flags & MCR_FLAGS_IN_USE) { + os_media_clock_rec_stop(&rec->os); + rec->flags &= ~(MCR_FLAGS_RUNNING | MCR_FLAGS_IN_USE); + } +} + +__init struct media_clock_rec *media_clock_rec_init(int domain_id) +{ + struct media_clock_rec *rec; + + rec = os_malloc(sizeof(*rec)); + if (!rec) + goto err_malloc; + + os_memset(rec, 0, sizeof(struct media_clock_rec)); + + if (os_media_clock_rec_init(&rec->os, domain_id) < 0) + goto err_init; + + rec->id = domain_id; + rec->array_addr = rec->os.array_addr; + rec->array_size = rec->os.array_size; + rec->write_idx = rec->array_addr + rec->array_size; + + /* Success */ + os_log(LOG_INIT, "clock id %d, init done\n", domain_id); + + return rec; + +err_init: + os_free(rec); + +err_malloc: + return NULL; +} + +__exit void media_clock_rec_exit(struct media_clock_rec *rec) +{ + os_media_clock_rec_exit(&rec->os); + + os_free(rec); + + os_log(LOG_INIT, "done\n"); +} + +static void clock_grid_mult_ts_update(struct clock_grid *grid, unsigned int requested, unsigned int *reset); + +/** This function opens a HW user clock generation (media_clock_gen_usr_hw) based on the + * HW clock generator (media_clock_gen_hw). The frequency of the user clock cannot be lower + * than the HW clock frequency and needs to be a mutiple of it. + * \return 0 if success, -1 otherwise + * \param clock pointer to media_clock_gen_hw context + * \param clk_usr pointer to media_clock_gen_usr_hw context + * \param freq_p timestamp frequency for the user clock generation (in the form p/q Hz) + * \param freq_q timestamp frequency for the user clock generation + * \param wake_freq_p wake-up frequency for the user clock generation (in the form p/q Hz) + * \param wake_freq_q wake-up frequency for the user clock generation + */ +int clock_grid_init_mult(struct clock_grid *grid, struct clock_domain *domain, struct clock_grid *grid_parent, unsigned int freq_p, unsigned int freq_q) +{ + u32 hw_freq = grid_parent->nominal_freq_p / grid_parent->nominal_freq_q; + struct clock_grid_producer_mult *mult = &grid->producer.u.mult; + u32 *ring_base; + + if (freq_p < hw_freq * freq_q) { + os_log(LOG_ERR, "clock grid(%p) frequency(%u Hz) cannot be under %u Hz\n", grid, freq_p / freq_q, hw_freq); + goto err; + } + + grid->flags = 0; + + ring_base = os_malloc(MCG_TS_SIZE * sizeof(u32)); + if (!ring_base) { + os_log(LOG_ERR, "clock grid(%p) could not allocate %zu bytes for clock grid\n", + grid, MCG_TS_SIZE * sizeof(u32)); + goto err; + } + + if (clock_grid_init(grid, GRID_PRODUCER_MULT, ring_base, MCG_TS_SIZE, freq_p, freq_q, clock_grid_mult_ts_update) < 0) + goto err_grid_init; + + mult->state = GEN_INIT; + mult->interval = grid->nominal_period; + mult->interval_rem = (u64)NSECS_PER_SEC * grid->nominal_freq_q - (u64)mult->interval * grid->nominal_freq_p; + + clock_grid_consumer_attach(&mult->source, grid_parent, 0, 0); + + clock_domain_add_grid(domain, grid); + + os_log(LOG_INFO, "clock grid(%p) successfully opened on parent grid(%p)\n", grid, grid_parent); + + return 0; + +err_grid_init: + os_free(ring_base); + +err: + return -1; +} + +void clock_grid_mult_exit(struct clock_grid *grid) +{ + clock_grid_consumer_exit(&grid->producer.u.mult.source); + os_free(grid->ts); + + os_log(LOG_INFO, "clock_grid MULT(%p) closed\n", grid); +} + +static unsigned int ts_available(struct clock_grid_consumer *consumer) +{ + unsigned int avail; + + if (consumer->grid->write_index >= consumer->read_index) + avail = consumer->grid->write_index - consumer->read_index; + else + avail = (consumer->grid->write_index + consumer->grid->ring_size) - consumer->read_index; + + return avail; +} + +static void ts_put(struct clock_grid *grid, unsigned int ts) +{ + if (grid->count >= 1) { + u32 prev_ts = grid->ts[(grid->write_index - 1) & (grid->ring_size - 1)]; + unsigned int period = ts - prev_ts; + + if (os_abs((int)period - (int)grid->nominal_period) > grid->period_jitter) + grid->stats.err_period++; + + stats_update(&grid->stats.period, ts - prev_ts); + } + + grid->ts[grid->write_index] = ts; + + grid->write_index = (grid->write_index + 1) & (grid->ring_size - 1); + grid->count++; + +} + +static unsigned int ts_peek_n(struct clock_grid_consumer *consumer, unsigned int n) +{ + return consumer->grid->ts[(consumer->read_index + n) & (consumer->grid->ring_size - 1)]; +} + +static unsigned int ts_peek(struct clock_grid_consumer *consumer) +{ + return consumer->grid->ts[consumer->read_index]; +} + +static void _clock_grid_consumer_reset(struct clock_grid_consumer *consumer) +{ + struct clock_grid *grid = consumer->grid; + u32 start_count = clock_grid_start_count(grid); + + os_log(LOG_DEBUG, "Resetting consumer(%p): count %u grid count %u read %u write %u start_count %u (grid size %u, valid_count %u)\n", + consumer, consumer->count, consumer->grid->count, consumer->read_index, grid->write_index, start_count, + consumer->grid->ring_size, consumer->grid->valid_count); + + consumer->read_index = (grid->write_index - start_count) % grid->ring_size; + consumer->count = grid->count - start_count; +} + +static unsigned int ts_get(struct clock_grid_consumer *consumer) +{ + struct clock_grid *grid = consumer->grid; + unsigned int ts = consumer->grid->ts[consumer->read_index]; + unsigned int period, next_ts, prev_offset; + + /* Detect discontinuities caused by the producer */ + if (consumer->init) { + period = ts - consumer->prev_ts; + + if (os_abs((int)period - (int)grid->nominal_period) > grid->period_jitter) { + /* Adjust consumer offset to hide discontinuity */ + + next_ts = consumer->prev_ts + consumer->prev_period; + prev_offset = consumer->offset; + consumer->offset += next_ts - ts; + + os_log(LOG_DEBUG, "consumer(%p) discontinuity %u, next: %u, %u %u %u %d %d\n", + consumer, consumer->count, next_ts, ts + consumer->offset, ts, consumer->prev_ts, consumer->offset, prev_offset); + } else { + consumer->prev_period = period; + } + } + + consumer->prev_ts = ts; + ts += consumer->offset; + + consumer->read_index = (consumer->read_index + 1) & (consumer->grid->ring_size - 1); + consumer->count++; + consumer->init = 1; + + return ts; +} + +static void clock_grid_consumer_reset(struct clock_grid_consumer *consumer) +{ + u32 new_ts; + + _clock_grid_consumer_reset(consumer); + + if (consumer->init) { + new_ts = ts_peek(consumer); + + consumer->offset += (consumer->prev_ts + consumer->prev_period - new_ts); + consumer->prev_ts = new_ts - consumer->prev_period; + } + + stats_reset(&consumer->stats.ts_err); + stats_reset(&consumer->stats.ts_batch); +} + +static void clock_grid_consumer_overflow_check(struct clock_grid_consumer *consumer) +{ + /* Check for overflow of the clock grid */ + if (((int)consumer->grid->count -(int)consumer->count) > (int)consumer->grid->valid_count) { + consumer->stats.err_reset++; + clock_grid_consumer_reset(consumer); + } +} + + +static void ts_generate_new(struct clock_grid *grid) +{ + struct clock_grid_producer_mult *mult = &grid->producer.u.mult; + + ts_put(grid, mult->ts_last); + + mult->ts_last += mult->interval; + + mult->ts_last_frac += mult->interval_rem; + if (mult->ts_last_frac >= grid->nominal_freq_p) { + mult->ts_last++; + mult->ts_last_frac -= grid->nominal_freq_p; + } +} + +static void clock_grid_consumer_ts_update(struct clock_grid_consumer *consumer, unsigned int requested, unsigned int *reset) +{ + struct clock_grid *grid = consumer->grid; + unsigned int ts_avail; + + if (grid->ts_update) { + + if (consumer->init) { + clock_grid_consumer_overflow_check(consumer); + + ts_avail = ts_available(consumer); + if (requested > ts_avail) + requested -= ts_avail; + else + goto exit; /* Amount requested is less than available, exit early */ + + } + + grid->ts_update(grid, requested, reset); + clock_grid_update_valid_count(grid); + + if (consumer->init) + clock_grid_consumer_overflow_check(consumer); + else + _clock_grid_consumer_reset(consumer); + } + +exit: + return; +} + +/** Updates the HW user clock grid based on the evolution of the HW clock. + * \return none + * \param clk_usr pointer to media_clock_gen_usr_hw context + * \param reset pointer to unsigned int + */ +static void clock_grid_mult_ts_update(struct clock_grid *grid, unsigned int requested, unsigned int *reset) +{ + struct clock_grid_producer_mult *mult = &grid->producer.u.mult; + struct clock_grid *source_grid = mult->source.grid; + unsigned int flags = 0; + unsigned int ts_n; + + *reset = 0; + + /* First TS, lock to HW generation */ +init: + if (mult->state == GEN_INIT) { + u32 alignment_ts; + + /* Align the multiplier grid to "now", so that timestamp offset grid stats can be easily + * checked + */ + if (os_clock_gettime32(avtp_to_clock(CFG_DEFAULT_PORT_ID), &alignment_ts) < 0) + return; + + flags |= MCG_FLAGS_DO_ALIGN; + + if (clock_grid_consumer_get_ts(&mult->source, &mult->hw_ts_last, 1, &flags, alignment_ts) < 1) + return; + + mult->interval = ((u64)NSECS_PER_SEC * grid->nominal_freq_q) / grid->nominal_freq_p; + mult->interval_rem = (u64)NSECS_PER_SEC * grid->nominal_freq_q - (u64)mult->interval * grid->nominal_freq_p; + + mult->slot = 0; + mult->state = GEN_RUNNING; + + mult->ts_last = mult->hw_ts_last; + mult->ts_last_frac = 0; + + os_log(LOG_DEBUG, "clk_grid(%p) start, read_index: %u, hw ts: %u, sw interval: %u\n", + grid, mult->source.read_index, mult->hw_ts_last, mult->interval); + os_log(LOG_DEBUG, "clk_grid(%p) start, write_index: %u, grid_count: %u, consumer_count: %u, offset %u, write_val %u, read_val %u\n", + grid, mult->source.grid->write_index, mult->source.grid->count, mult->source.count, mult->source.offset, + mult->source.grid->ts[mult->source.grid->write_index], mult->source.grid->ts[mult->source.read_index]); + + *reset = 1; + } + + /* Compute ts */ + if (requested) + ts_n = requested; + else + ts_n = 1; + + while (ts_n) { + u32 hw_freq = source_grid->nominal_freq_p / source_grid->nominal_freq_q; + + /* New interval */ + if (mult->slot >= grid->nominal_freq_p) { + u32 hw_ts, period; + + flags = 0; + + if (clock_grid_consumer_get_ts(&mult->source, &hw_ts, 1, &flags, 0) < 1) + break; + + period = hw_ts - mult->hw_ts_last; + if (os_abs((int)period - (int)mult->source.grid->nominal_period) > mult->source.grid->period_jitter) { +// os_log(LOG_ERR, "grid(%p) invalid hw ts %u %u %u\n", grid, hw_ts, mult->hw_ts_last, period); + + mult->state = GEN_INIT; + + if (!(*reset)) { + requested = ts_n; + + goto init; + } + + break; + } + + mult->slot -= grid->nominal_freq_p; + + if (!mult->slot) { + /* Integer multiple of reference period, reset timestamp offset */ + mult->interval = ((u64)(hw_ts - mult->hw_ts_last + hw_ts - mult->ts_last) * hw_freq * grid->nominal_freq_q) / grid->nominal_freq_p; + mult->interval_rem = ((u64)(hw_ts - mult->hw_ts_last + hw_ts - mult->ts_last) * hw_freq * grid->nominal_freq_q) - (u64)grid->nominal_freq_p * mult->interval; + } else { + mult->interval = ((u64)(hw_ts - mult->hw_ts_last) * hw_freq * grid->nominal_freq_q) / grid->nominal_freq_p; + mult->interval_rem = ((u64)(hw_ts - mult->hw_ts_last) * hw_freq * grid->nominal_freq_q) - (u64)grid->nominal_freq_p * mult->interval; + } + + mult->hw_ts_last = hw_ts; + } + + ts_generate_new(grid); + + mult->slot += hw_freq * grid->nominal_freq_q; + + if (requested) + ts_n--; + } + +} +static unsigned int clock_grid_consumer_mean_period(struct clock_grid_consumer *consumer) +{ + int n_ts = min(10, ts_available(consumer)); + unsigned int mean = 0; + + if (n_ts >= 2) { + int i; + unsigned int period; + + /* Look for any discontinuity */ + for (i = 0; i < (n_ts - 1); i++) { + period = ts_peek_n(consumer, i + 1) - ts_peek_n(consumer, i); + if (os_abs((int)period - (int)consumer->grid->nominal_period) > consumer->grid->period_jitter) + break; + } + + if (i >= 1) + mean = (ts_peek_n(consumer, i) - ts_peek_n(consumer, 0)) / i; + } + + /* + * Not enough timestamps or early discontinuity + */ + if (!mean) { + mean = consumer->grid->nominal_period; + os_log(LOG_ERR, "consumer(%p) failed to measure mean period, fallback to nominal period\n", consumer); + } + + return mean; +} + +static void clock_grid_consumer_compute_offset(struct clock_grid_consumer *consumer, unsigned int alignment_ts) +{ + if (ts_available(consumer)) { + unsigned int mean_period = clock_grid_consumer_mean_period(consumer); + u32 new_ts = ts_peek(consumer); + + consumer->offset = alignment_ts - new_ts; + + if (consumer->alignment) { + consumer->offset = (consumer->offset + consumer->alignment - 1) / consumer->alignment; + consumer->offset = consumer->offset * consumer->alignment; + } + + consumer->prev_ts = new_ts - mean_period; + + os_log(LOG_DEBUG, "consumer(%p) measured period %u, %u %u offset %d %u %u(ns)\n", + consumer, mean_period, alignment_ts, new_ts, consumer->offset, new_ts + consumer->offset, consumer->prev_ts); + } else { + consumer->offset = 0; + } + + if (consumer->offset > (CLOCK_GRID_VALID_TIME_US * 1000)) { + os_log(LOG_DEBUG, "consumer(%p) offset %u above threshold %u (ns)\n", consumer, consumer->offset, (CLOCK_GRID_VALID_TIME_US * 1000)); + consumer->stats.err_offset++; + } +} + + +int clock_grid_consumer_get_ts(struct clock_grid_consumer *consumer, u32 *ts, unsigned int ts_n, unsigned int *flags, unsigned int alignment_ts) +{ + unsigned int written = 0; + unsigned int reset = 0; + unsigned int ts_n_actual; + unsigned int ts_avail; + + if (ts_n) + clock_grid_consumer_ts_update(consumer, ts_n, &reset); + + if (*flags & MCG_FLAGS_DO_ALIGN) + clock_grid_consumer_compute_offset(consumer, alignment_ts); + + if (reset) { + *flags |= MCG_FLAGS_RESET; + consumer->stats.err_reset++; + } + + ts_avail = ts_available(consumer); + ts_n_actual = min(ts_n, ts_avail); + if (ts_n_actual < ts_n) { + consumer->stats.err_starved++; + os_log(LOG_DEBUG, "consumer(%p): Not enough timestamps: ts_n %u avail %u read %u write %u grid_count %u consumer_count %u\n", + consumer, ts_n, ts_n_actual, consumer->read_index, consumer->grid->write_index, consumer->grid->count, consumer->count); + } + + while (written < ts_n_actual) { + *ts = ts_get(consumer); + + if (!written) { + stats_update(&consumer->stats.ts_err, (int)*ts - (int)consumer->gptp_current); + //stats_update(&consumer->stats.ts_err, (consumer->grid->write_index - consumer->read_index) & (consumer->grid->ring_size - 1)); + } + + consumer->stats.ts++; + ts++; + written++; + } + + stats_update(&consumer->stats.ts_batch, written); + + return written; +} + + +void clock_grid_consumer_stats_print(struct ipc_avtp_clock_grid_consumer_stats *msg) +{ + struct clock_grid_consumer_stats *stats = &msg->stats; + + stats_compute(&stats->ts_err); + stats_compute(&stats->ts_batch); + + os_log(LOG_INFO, " clock_consumer(%p) grid(%p)\n", msg->consumer, msg->grid); + os_log(LOG_INFO, " ts: %10u, offset err: %10u, reset err: %10u, starved err: %10u ts_err: %10d /%10d /%10d (ns) ts batch: %3d/%3d/%3d\n", + stats->ts, stats->err_offset, stats->err_reset, stats->err_starved, + stats->ts_err.min, stats->ts_err.mean, stats->ts_err.max, + stats->ts_batch.min, stats->ts_batch.mean, stats->ts_batch.max); +} + +void clock_grid_consumer_stats_dump(struct clock_grid_consumer *consumer, struct ipc_tx *tx) +{ + struct ipc_desc *desc; + struct ipc_avtp_clock_grid_consumer_stats *msg; + + desc = ipc_alloc(tx, sizeof(*msg)); + if (!desc) + goto err_ipc_alloc; + + desc->type = IPC_AVTP_CLOCK_GRID_CONSUMER_STATS; + desc->len = sizeof(*msg); + desc->flags = 0; + + msg = (struct ipc_avtp_clock_grid_consumer_stats *)&desc->u; + + msg->consumer = consumer; + msg->grid = consumer->grid; + + os_memcpy(&msg->stats, &consumer->stats, sizeof(consumer->stats)); + + stats_reset(&consumer->stats.ts_err); + stats_reset(&consumer->stats.ts_batch); + + if (ipc_tx(tx, desc) < 0) + goto err_ipc_tx; + + return; + +err_ipc_tx: + ipc_free(tx, desc); + +err_ipc_alloc: + return; +} + +int clock_grid_consumer_attach(struct clock_grid_consumer *consumer, struct clock_grid *grid, unsigned int offset, unsigned int alignment) +{ + int rc = clock_grid_ref(grid); + + if (!rc) { + consumer->grid = grid; + consumer->offset = offset; + consumer->alignment = alignment; + consumer->init = 0; + consumer->prev_period = grid->nominal_period; + + stats_init(&consumer->stats.ts_err, 31, NULL, NULL); + stats_init(&consumer->stats.ts_batch, 31, NULL, NULL); + } + + return rc; +} + +void clock_grid_consumer_detach(struct clock_grid_consumer *consumer) +{ + if (consumer->grid) { + clock_grid_unref(consumer->grid); + + consumer->grid = NULL; + consumer->count = 0; + consumer->read_index = 0; + } +} + +void clock_grid_consumer_exit(struct clock_grid_consumer *consumer) +{ + clock_grid_consumer_detach(consumer); +} + +void clock_producer_stream_rx(struct clock_grid *grid, struct timestamp *ts, unsigned *ts_n, unsigned int do_stitch) +{ + struct clock_grid_producer_stream *producer = &grid->producer.u.stream; + struct media_clock_rec *rec = producer->rec; + int i; + + if (rec) { + /* If upper layer notifies that a timestamp discontinuity is happening but could be worthwhile + * to mask (going into a stable locked state) and if the discontinuity is enough to unlock + * the media clock recovery, offset the timestamps to "hide" the discontinuity. + * + * FIXME align on media clock sample rate + */ + if (rec->state == RUNNING_LOCKED) { + if (do_stitch && period_error(grid->nominal_period, ts[0].ts_nsec + producer->stitch_ts_offset - rec->last_ts)) { + producer->stitch_ts_offset = grid->nominal_period - (ts[0].ts_nsec - rec->last_ts); + clock_domain_clear_state(grid->domain, CLOCK_DOMAIN_STATE_LOCKED); + os_log(LOG_DEBUG, "stitch offset %d\n", producer->stitch_ts_offset); + } + + if (producer->stitch_ts_offset) { + for (i = 0; i < *ts_n; i++) + ts[i].ts_nsec += producer->stitch_ts_offset; + } + } + + media_clock_rec(rec, ts, *ts_n); + + grid->write_index = *rec->write_idx; + grid->count = rec->nb_ts_total; + + if (rec->state == RUNNING_LOCKED) + clock_domain_set_state(grid->domain, CLOCK_DOMAIN_STATE_LOCKED); + else { + clock_domain_clear_state(grid->domain, CLOCK_DOMAIN_STATE_LOCKED); + producer->stitch_ts_offset = 0; + } + } + else { + int i = 0; + + while (i < *ts_n) + ts_put(grid, ts[i++].ts_nsec); + } + + clock_grid_update_valid_count(grid); +} + +int clock_producer_stream_open(struct clock_grid *grid, struct stream_listener *stream, struct media_clock_rec *rec, + unsigned int ts_freq_p, unsigned int ts_freq_q) +{ + struct clock_grid_producer_stream *producer = &grid->producer.u.stream; + + if (media_clock_rec_open(rec, ts_freq_p, ts_freq_q, grid) < 0) + goto err_rec_open; + + if (clock_grid_init(grid, GRID_PRODUCER_STREAM, rec->array_addr, rec->array_size, ts_freq_p, ts_freq_q, NULL) < 0) + goto err_grid_init; + + producer->rec = rec; + producer->stream = stream; + producer->stitch_ts_offset = 0; + + os_log(LOG_INFO, "stream(%p) rec(%p)\n", stream, rec); + + return 0; + +err_grid_init: + media_clock_rec_close(rec); + +err_rec_open: + return -1; +} + +void clock_producer_stream_close(struct clock_grid *grid) +{ + struct clock_grid_producer_stream *producer = &grid->producer.u.stream; + + if (producer->stream) { + if (producer->rec) + media_clock_rec_close(producer->rec); + else + os_free(grid->ts); + + producer->stream = NULL; + producer->rec = NULL; + } +} diff --git a/avtp/media_clock.h b/avtp/media_clock.h new file mode 100644 index 0000000..73c524c --- /dev/null +++ b/avtp/media_clock.h @@ -0,0 +1,152 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Media clock interface handling + @details +*/ +#ifndef _MEDIA_CLOCK_H_ +#define _MEDIA_CLOCK_H_ + +#include "os/media_clock.h" +#include "clock_domain.h" + +typedef enum { + INIT, + MEASUREMENT, + READY, + RUNNING, + RUNNING_LOCKED, + ERR, +} media_clock_rec_state_t; + +struct media_clock_rec { + unsigned int id; + struct os_media_clock_rec os; + int flags; + media_clock_rec_state_t state; + u32 *array_addr; + unsigned int array_size; //number of timestamps + unsigned int ts_period; //theoric value (ns) + u32 *write_idx; //shared with driver + unsigned int clean_idx; + u32 delay; + u32 period_mean; + u32 period_min; + u32 period_max; + u32 period_first; + int offset_min; + int offset_max; + int max_offset_val; + u32 last_ts; + u32 ts[2]; + unsigned int ready; + unsigned int nb_meas; + unsigned int nb_clean_total; + unsigned int nb_ts_total; + unsigned int nb_pending; + + struct clock_rec_stats { + unsigned int running; + unsigned int running_locked; + unsigned int err_restart; + unsigned int err_uncertain; + unsigned int err_offset; + unsigned int err_period; + unsigned int err_rec_driver; + unsigned int err_crit; + struct stats period; + } stats; /**< pointer to time of last update, in number of 125us periods. */ +}; + + +#define TS_TX_BATCH (4 * NET_TX_BATCH) +#define MCG_TS_SIZE 1024 //TS_TX_BATCH +/* 100ms will result in a drift of at most 0.5µs between 2 clocks with a 5ppm clock ratio. + * Since the uncertainty on our clock measurements (and/or the stability of a single clock) + * should be much less than 1ppm (gPTP expects less than 0.1ppm), + * we should meet the 5% grid alignment required by AVTP (5% of 96kHz ~ 0.5µs). + */ +#define CLOCK_GRID_VALID_TIME_US 100000 + +#define CLOCK_GRID_VALID_PPM 100 + + +int clock_grid_init_mult(struct clock_grid *grid, struct clock_domain *domain, struct clock_grid *grid_parent, unsigned int freq_p, unsigned int freq_q); + +int clock_grid_consumer_get_ts(struct clock_grid_consumer *consumer, u32 *ts, unsigned int ts_n, unsigned int *flags, unsigned int alignment_ts); /* similar to media_clock_gen_get_ts */ + +int clock_grid_consumer_attach(struct clock_grid_consumer *consumer, struct clock_grid *grid, unsigned int offset, unsigned int alignment); +void clock_grid_consumer_detach(struct clock_grid_consumer *consumer); +void clock_grid_consumer_exit(struct clock_grid_consumer *consumer); + +struct timestamp { + unsigned int ts_nsec; + unsigned int flags; +}; + +#define MCG_MAX_FREQUENCY 48000 +#define MCG_MIN_FREQUENCY 300 + +#define MCR_MAX_PERIOD 20000000 // 20 ms : Maximum period/minimum frequency allowed for the intermediate clock +#define MCR_DELAY 5000000 // 5 ms : Added to the presentation time to overcome rx batching + processing time + +#define MCR_CLEAN_BATCH 32 // 2^x + +#define MCR_NB_MEAS 100 + +#define MCR_FLAGS_IN_USE (1 << 0) +#define MCR_FLAGS_RUNNING (1 << 1) + +#define MCG_FLAGS_RESET (1 << 0) +#define MCG_FLAGS_DO_ALIGN (1 << 1) +#define MCG_NO_WAKE_UP 0 +#define MCG_WAKE_UP_MIN 2 + +#define SYT_INTERVAL_LN2 3 + +#include "os/media_clock.h" + +struct ipc_avtp_clock_grid_consumer_stats { + void *consumer; + void *grid; + + struct clock_grid_consumer_stats stats; +}; + +struct ipc_avtp_clock_rec_stats { + void *clock_id; + struct clock_rec_stats stats; +}; + +int media_clock_rec_open(struct media_clock_rec *clock, unsigned int ts_freq_p, unsigned int ts_freq_q, void *context); +void media_clock_rec_close(struct media_clock_rec *clock); +struct media_clock_rec *media_clock_rec_init(int domain_id); +void media_clock_rec_exit(struct media_clock_rec *); +media_clock_rec_state_t media_clock_rec(struct media_clock_rec *clock, struct timestamp *ts, int num_ts); +void media_clock_rec_stats_print(struct ipc_avtp_clock_rec_stats *msg); +void media_clock_rec_stats_dump(struct media_clock_rec *clock, struct ipc_avtp_clock_rec_stats *msg); +int media_clock_rec_open_ptp(struct media_clock_rec *rec); +void media_clock_rec_close_ptp(struct media_clock_rec *rec); + +void clock_grid_consumer_stats_print(struct ipc_avtp_clock_grid_consumer_stats *msg); +void clock_grid_consumer_stats_dump(struct clock_grid_consumer *consumer, struct ipc_tx *tx); + +void clock_grid_mult_exit(struct clock_grid *grid); + +void clock_producer_stream_rx(struct clock_grid *grid, struct timestamp *ts, unsigned *ts_n, unsigned int stitch_ts); +int clock_producer_stream_open(struct clock_grid *grid, struct stream_listener *stream, struct media_clock_rec *rec, + unsigned int ts_freq_p, unsigned int ts_freq_q); +void clock_producer_stream_close(struct clock_grid *grid); + +static inline int media_clock_gen_get_ts(struct clock_grid_consumer *consumer, u32 *ts, unsigned int ts_n, unsigned int *flags, unsigned int alignment_ts) +{ + return clock_grid_consumer_get_ts(consumer, ts, ts_n, flags, alignment_ts); +} + +#endif /* _MEDIA_CLOCK_H_ */ diff --git a/avtp/mma.h b/avtp/mma.h new file mode 100644 index 0000000..3e3d8dc --- /dev/null +++ b/avtp/mma.h @@ -0,0 +1,18 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief MMA protocol handling functions + @details +*/ + +#ifndef _MMA_H_ +#define _MMA_H_ + + +#endif /* _MMA_H_ */ diff --git a/avtp/rtos/avtp.cmake b/avtp/rtos/avtp.cmake new file mode 100644 index 0000000..c980256 --- /dev/null +++ b/avtp/rtos/avtp.cmake @@ -0,0 +1 @@ +genavb_target_add_srcs(TARGET ${avb} SRCS main.c) diff --git a/avtp/rtos/main.c b/avtp/rtos/main.c new file mode 100644 index 0000000..19aa4b3 --- /dev/null +++ b/avtp/rtos/main.c @@ -0,0 +1,319 @@ +/* + * Copyright 2018-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief NXP AVTP RTOS specific code + @details Setups RTOS task for NXP AVDECC stack component. Implements main loop and event handling. + */ + +#include +#include + +#include "rtos_abstraction_layer.h" + +#include "common/ipc.h" +#include "common/log.h" +#include "common/types.h" + +#include "os/config.h" +#include "os/sys_types.h" +#include "os/log.h" +#include "os/clock.h" +#include "os/sys_types.h" +#include "os/net.h" +#include "os/ipc.h" +#include "os/timer.h" + +#include "avtp/avtp_entry.h" +#include "avtp/config.h" + +#define AVTP_TASK_NAME "AVTP Stack" +#define STATS_TASK_NAME "Stats Task" +#define AVTP_TASK_SUCCESS (1 << 0) +#define AVTP_TASK_ERROR (1 << 1) +#define STATS_TASK_SUCCESS (1 << 2) +#define STATS_TASK_ERROR (1 << 3) + +#define IPC_POOLING_PERIOD_NS (10ULL * NSECS_PER_MS) +#define STATS_PERIOD_NS (10ULL * NSECS_PER_SEC) + +struct avtp_ctx { + rtos_event_group_t event_group; + struct avtp_config *avtp_cfg; + rtos_mqueue_t event_queue; + uint8_t queue_buffer[AVTP_CFG_EVENT_QUEUE_LENGTH * sizeof(struct event)]; + rtos_thread_t task; + rtos_thread_t stats_task; + rtos_mqueue_t stats_event_queue; + uint8_t stats_queue_buffer[STATS_CFG_EVENT_QUEUE_LENGTH * sizeof(struct event)]; + struct ipc_rx stats_ipc_rx; + void *avtp; +}; + +const struct avtp_config avtp_default_config = { + .log_level = avtp_CFG_LOG, + + .port_max = CFG_EP_DEFAULT_NUM_PORTS, + + .logical_port_list = CFG_EP_LOGICAL_PORT_LIST, +}; + +/** + * + */ +__init static void process_config(struct avtp_config *avtp_cfg) +{ +} + + +/** + * + */ +static void stats_task(void *pvParameters) +{ + struct avtp_ctx *ctx = pvParameters; + + os_log(LOG_INIT, "stats task init\n"); + + if (rtos_mqueue_init(&ctx->stats_event_queue, STATS_CFG_EVENT_QUEUE_LENGTH, sizeof(struct event), ctx->stats_queue_buffer) < 0) { + os_log(LOG_ERR, "rtos_mqueue_init() failed\n"); + goto err_queue_create; + } + + if (ipc_rx_init(&ctx->stats_ipc_rx, IPC_AVTP_STATS, stats_ipc_rx, (unsigned long)&ctx->stats_event_queue) < 0) + goto err_ipc_rx; + + rtos_event_group_set(&ctx->event_group, STATS_TASK_SUCCESS); + + os_log(LOG_INIT, "started\n"); + + while (1) { + struct event e; + + if (rtos_mqueue_receive(&ctx->stats_event_queue, &e, RTOS_MS_TO_TICKS(10000)) < 0) + continue; + + switch (e.type) { + case EVENT_TYPE_IPC: + ipc_rx((struct ipc_rx *)e.data); + break; + default: + os_log(LOG_ERR, "rtos_mqueue_receive(): invalid event type(%u)\n", e.type); + break; + } + } + + /* Not reached */ + +err_ipc_rx: + rtos_mqueue_destroy(&ctx->stats_event_queue); + +err_queue_create: + + os_log(LOG_INIT, "stats task exited\n"); + + rtos_event_group_set(&ctx->event_group, STATS_TASK_ERROR); + + rtos_thread_abort(NULL); +} + + +/** + * + */ +static void avtp_task(void *pvParameters) +{ + struct avtp_ctx *ctx = pvParameters; + struct avtp_config *avtp_cfg = ctx->avtp_cfg; + struct avtp_ctx *avtp; + uint64_t current_time = 0, sched_time = 0, ipc_time = 0, stats_time = 0; + unsigned int i, events_count; + struct process_stats stats; + uint32_t bits; + + os_log(LOG_INIT, "avtp task init\n"); + + if (rtos_mqueue_init(&ctx->event_queue, AVTP_CFG_EVENT_QUEUE_LENGTH, sizeof(struct event), ctx->queue_buffer) < 0) { + os_log(LOG_ERR, "rtos_mqueue_init() failed\n"); + goto err_queue_create; + } + + /** + * Create stats task + */ + if (rtos_thread_create(&ctx->stats_task, STATS_CFG_PRIORITY, 0, STATS_CFG_STACK_DEPTH, STATS_TASK_NAME, stats_task, ctx) < 0) { + os_log(LOG_ERR, "rtos_thread_create(%s) failed\n", STATS_TASK_NAME); + goto err_task_create; + } + + bits = rtos_event_group_wait(&ctx->event_group, STATS_TASK_SUCCESS | STATS_TASK_ERROR, false, RTOS_WAIT_FOREVER); + if (bits & STATS_TASK_ERROR) + goto err_task_create; + + /** + * AVTP Config + */ + process_config(avtp_cfg); + + for (i = 0; i < CFG_MAX_NUM_PORT; i++) + avtp_cfg->clock_gptp_list[i] = logical_port_to_gptp_clock(avtp_cfg->logical_port_list[i], CFG_DEFAULT_GPTP_DOMAIN); + + /** + * AVTP Init + */ + avtp = avtp_init(avtp_cfg, (unsigned long)&ctx->event_queue); + if (!avtp) + goto err_avtp_init; + + ctx->avtp = avtp; + + rtos_event_group_set(&ctx->event_group, AVTP_TASK_SUCCESS); + + os_log(LOG_INIT, "started\n"); + + stats_init(&stats.events, 31, NULL, NULL); + stats_init(&stats.sched_intvl, 31, NULL, NULL); + stats_init(&stats.processing_time, 31, NULL, NULL); + + /** + * Main loop + */ + while (1) { + struct event e; + int rc; + + events_count = 0; + + rc = rtos_mqueue_receive(&ctx->event_queue, &e, RTOS_MS_TO_TICKS(2)); + + if (os_clock_gettime64(OS_CLOCK_SYSTEM_MONOTONIC, ¤t_time) == 0) { + stats_update(&stats.sched_intvl, current_time - sched_time); + sched_time = current_time; + } + + if (rc != 0) + goto timeout; + + do { + events_count++; + + switch (e.type) { + case EVENT_TYPE_NET_RX: + net_rx_multi((struct net_rx *)e.data); + break; + + case EVENT_TYPE_TIMER: + os_timer_process((struct os_timer *)e.data); + break; + + case EVENT_TYPE_MEDIA: + avtp_media_event(e.data); + break; + + default: + os_log(LOG_ERR, "rtos_mqueue_receive(): invalid event type(%u)\n", e.type); + break; + } + + } while (events_count < 8 && !rtos_mqueue_receive(&ctx->event_queue, &e, RTOS_NO_WAIT)); + +timeout: + stats_update(&stats.events, events_count); + + if (os_clock_gettime64(OS_CLOCK_SYSTEM_MONOTONIC, ¤t_time) == 0) { + if ((current_time - ipc_time) > IPC_POOLING_PERIOD_NS) { + avtp_ipc_rx(avtp); + avtp_stream_free(avtp, current_time); + ipc_time = current_time; + + if ((current_time - stats_time) > STATS_PERIOD_NS) { + avtp_stats_dump(avtp, &stats); + stats_time = current_time; + } + } + + stats_update(&stats.processing_time, current_time - sched_time); + } + } + + /* Not reached */ + +err_avtp_init: + rtos_thread_abort(&ctx->stats_task); + + ipc_rx_exit(&ctx->stats_ipc_rx); + + rtos_mqueue_destroy(&ctx->stats_event_queue); + +err_task_create: + rtos_mqueue_destroy(&ctx->event_queue); + +err_queue_create: + + os_log(LOG_INIT, "avtp task exited\n"); + + rtos_event_group_set(&ctx->event_group, AVTP_TASK_ERROR); + + rtos_thread_abort(NULL); +} + +/** + * + */ +__init void *avtp_task_init(struct avtp_config *avtp_cfg) +{ + struct avtp_ctx *ctx; + uint32_t bits; + + ctx = rtos_malloc(sizeof(struct avtp_ctx)); + if (!ctx) + goto err_ctx_alloc; + + if (rtos_event_group_init(&ctx->event_group) < 0) + goto err_event_group; + + ctx->avtp_cfg = avtp_cfg; + + if (rtos_thread_create(&ctx->task, AVTP_CFG_PRIORITY, 0, AVTP_CFG_STACK_DEPTH, AVTP_TASK_NAME, avtp_task, ctx) < 0) { + os_log(LOG_ERR, "rtos_thread_create(%s) failed\n", AVTP_TASK_NAME); + goto err_task_create; + } + + bits = rtos_event_group_wait(&ctx->event_group, AVTP_TASK_SUCCESS | AVTP_TASK_ERROR, false, RTOS_WAIT_FOREVER); + if (bits & AVTP_TASK_ERROR) + goto err_event_group; + + os_log(LOG_INIT, "avtp main completed\n"); + + return ctx; + +err_event_group: +err_task_create: + rtos_free(ctx); + +err_ctx_alloc: + return NULL; +} + +__exit void avtp_task_exit(void *handle) +{ + struct avtp_ctx *ctx = handle; + + rtos_thread_abort(&ctx->task); + + avtp_exit(ctx->avtp); + + rtos_thread_abort(&ctx->stats_task); + + ipc_rx_exit(&ctx->stats_ipc_rx); + + rtos_mqueue_destroy(&ctx->event_queue); + + rtos_mqueue_destroy(&ctx->stats_event_queue); + + rtos_free(ctx); +} diff --git a/avtp/stream.c b/avtp/stream.c new file mode 100644 index 0000000..584d29d --- /dev/null +++ b/avtp/stream.c @@ -0,0 +1,1161 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Stream handling functions + @details +*/ + +#include "os/stdlib.h" +#include "os/clock.h" +#include "os/assert.h" + +#include "common/log.h" +#include "common/avtp.h" +#include "common/net.h" +#include "common/61883_iidc.h" +#include "common/cvf.h" +#include "common/avdecc.h" + +#include "stream.h" + +#include "61883_iidc.h" +#include "cvf.h" +#include "acf.h" +#include "aaf.h" +#include "crf.h" +#include "media_clock.h" + +#define STREAM_DESTROYED_FREE_DELAY_NS 1000000 /* Time delay between destroying and freeing stream */ +#define STREAM_TALKER_MIN_PRESENTATION_TIME_OFFSET_NS 125000 /* Minimum presentation time offset : 125us */ +#define STREAM_TALKER_MAX_PRESENTATION_TIME_OFFSET_NS 50000000 /* Maximun presentation time offset : 50ms */ + +static const unsigned int sr_class_max_pending_packets[SR_CLASS_MAX + 1] = { + [SR_CLASS_A] = 8, // 1ms + [SR_CLASS_B] = 4, // 1ms + [SR_CLASS_C] = 1, + [SR_CLASS_D] = 1, + [SR_CLASS_E] = 1, + [SR_CLASS_NONE] = 1 +}; + +unsigned int avtp_stream_presentation_offset(struct stream_talker *stream) +{ + if (stream->subtype == AVTP_SUBTYPE_CRF) + return crf_stream_presentation_offset(stream); + else + return _avtp_stream_presentation_offset(stream->max_transit_time, stream->latency); +} + +static int stream_talker_format_check(struct stream_talker *stream, struct ipc_avtp_connect *ipc, + struct avdecc_format const *format) +{ + int rc = GENAVB_SUCCESS; + + if (format->u.s.v != AVTP_VERSION_0) { /* version: 0 describes an AVTP stream payload */ + os_log(LOG_ERR, "stream_id(%016"PRIx64") version(%u) not supported (should be 0 for AVTP)\n", + ntohll(stream->id), format->u.s.v); + goto err_format; + } + + /* FIXME subject to improvement to cover also video formats with variable bit rate*/ + if ((stream->common.flags & STREAM_FLAG_CUSTOM_TSPEC) && (format->u.s.subtype != AVTP_SUBTYPE_TSCF)) + goto err_format; + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + rc = talker_stream_61883_iidc_check(stream, format, ipc); + break; + + case AVTP_SUBTYPE_TSCF: + if (!(stream->common.flags & STREAM_FLAG_CUSTOM_TSPEC)) + goto err_format; + + rc = talker_stream_acf_tscf_check(stream, format, ipc); + break; + + case AVTP_SUBTYPE_AAF: + rc = talker_stream_aaf_check(stream, format, ipc); + break; + + case AVTP_SUBTYPE_CVF: + rc = talker_stream_cvf_check(stream, format, ipc); + break; + case AVTP_SUBTYPE_MMA_STREAM: + case AVTP_SUBTYPE_SVF: + case AVTP_SUBTYPE_RVF: + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") subtype(%u) not supported\n", ntohll(stream->id), format->u.s.subtype); + goto err_format; + } + + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + +static int alternative_talker_format_check(struct stream_talker *stream, struct ipc_avtp_connect *ipc, struct avdecc_format const *format) +{ + int rc = GENAVB_SUCCESS; + + if ((stream->common.flags & STREAM_FLAG_CUSTOM_TSPEC) && (ipc->subtype != AVTP_SUBTYPE_NTSCF)) + goto err_format; + + switch (ipc->subtype) { + case AVTP_SUBTYPE_NTSCF: + if (!(stream->common.flags & STREAM_FLAG_CUSTOM_TSPEC)) + goto err_format; + + rc = talker_acf_ntscf_check(stream, ipc); + break; + + case AVTP_SUBTYPE_CRF: + rc = talker_crf_check(stream, format, ipc); + break; + + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") subtype(%u) not supported\n", ntohll(stream->id), ipc->subtype); + goto err_format; + } + + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + + +static int stream_listener_format_check(struct stream_listener *stream, struct ipc_avtp_connect *ipc, struct avdecc_format const *format, u16 flags) +{ + int rc = GENAVB_SUCCESS; + + if (format->u.s.v != AVTP_VERSION_0) { /* version: 0 describes an AVTP stream payload */ + os_log(LOG_ERR, "stream_id(%016"PRIx64") version(%u) not supported (should be 0 for AVTP)\n", + ntohll(stream->id), format->u.s.v); + goto err_format; + } + + /* Likely a misconfiguration since only a talker application is expected to specify the streams parameters */ + if (stream->common.flags & STREAM_FLAG_CUSTOM_TSPEC) + goto err_format; + + switch (ipc->subtype) { + case AVTP_SUBTYPE_61883_IIDC: + rc = listener_stream_61883_iidc_check(stream, format, flags); + break; + + case AVTP_SUBTYPE_CVF: + rc = listener_stream_cvf_check(stream, format, flags); + break; + + case AVTP_SUBTYPE_TSCF: + rc = listener_stream_acf_tscf_check(stream, format, flags); + break; + + case AVTP_SUBTYPE_AAF: + rc = listener_stream_aaf_check(stream, format, flags); + break; + + case AVTP_SUBTYPE_MMA_STREAM: + case AVTP_SUBTYPE_SVF: + case AVTP_SUBTYPE_RVF: + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") subtype(%u) not supported\n", ntohll(stream->id), ipc->subtype); + goto err_format; + } + + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + +static int alternative_listener_format_check(struct stream_listener *stream, struct ipc_avtp_connect *ipc, struct avdecc_format const *format, u16 flags) +{ + int rc = GENAVB_SUCCESS; + + /* Likely a misconfiguration since only a talker application is expected to specify the streams parameters */ + if (stream->common.flags & STREAM_FLAG_CUSTOM_TSPEC) + goto err_format; + + switch (ipc->subtype) { + case AVTP_SUBTYPE_NTSCF: + rc = listener_acf_ntscf_check(stream, flags); + break; + + case AVTP_SUBTYPE_CRF: + rc = listener_crf_check(stream, format, flags); + break; + + default: + os_log(LOG_ERR, "stream_id(%016"PRIx64") subtype(%u) not supported\n", ntohll(stream->id), ipc->subtype); + goto err_format; + } + + return rc; + +err_format: + return -GENAVB_ERR_STREAM_PARAMS; +} + + +void avtp_latency_stats(struct stream_listener *stream, struct avtp_rx_desc *desc) +{ + stats_update(&stream->stats.avb_delay, stream->gptp_current - desc->desc.ts); + + stats_update(&stream->stats.avtp_delay, desc->avtp_timestamp - stream->gptp_current); +} + +void stream_talker_stats_print(struct ipc_avtp_talker_stats *msg) +{ + struct talker_stats *stats = &msg->stats; + + stats_compute(&stats->sched_intvl); + + os_log(LOG_INFO, "stream_id(%016"PRIx64")\n", ntohll(msg->stream_id)); + + os_log(LOG_INFO, "rx: %10u, clock: %10u, tx: %10u, rx err: %10u, clock err: %10u, gptp err: %10u\n", + stats->media_rx, stats->clock_rx, stats->tx, stats->media_err, stats->clock_err, stats->gptp_err); + os_log(LOG_INFO, "tx err: %10u, partial: %10u, media underrun: %10u clock invalid: %10u sched intvl: % 10d/% 10d/% 10d (ns)\n", + stats->tx_err, stats->partial, stats->media_underrun, stats->clock_invalid, + stats->sched_intvl.min, stats->sched_intvl.mean, stats->sched_intvl.max); +} + +static void stream_talker_stats_dump(struct stream_talker *stream, struct ipc_tx *tx) +{ + struct ipc_desc *desc; + struct ipc_avtp_talker_stats *msg; + + desc = ipc_alloc(tx, sizeof(*msg)); + if (!desc) + goto err_ipc_alloc; + + desc->type = IPC_AVTP_STREAM_TALKER_STATS; + desc->len = sizeof(*msg); + desc->flags = 0; + + msg = (struct ipc_avtp_talker_stats *)&desc->u; + + msg->stream_id = stream->id; + os_memcpy(&msg->stats, &stream->stats, sizeof(stream->stats)); + + if (ipc_tx(tx, desc) < 0) + goto err_ipc_tx; + + clock_grid_consumer_stats_dump(&stream->consumer, tx); + + stats_reset(&stream->stats.sched_intvl); + return; + +err_ipc_tx: + ipc_free(tx, desc); + +err_ipc_alloc: + return; +} + +void stream_listener_stats_print(struct ipc_avtp_listener_stats *msg) +{ + struct listener_stats *stats = &msg->stats; + + os_log(LOG_INFO, "stream_id(%016"PRIx64")\n", ntohll(msg->stream_id)); + + stats_compute(&stats->avb_delay); + stats_compute(&stats->avtp_delay); + stats_compute(&stats->batch); + + os_log(LOG_INFO, "rx: %10u, clock: %10u, tx: %10u, subtype err: %10u, tx err: %10u\n", + stats->rx, stats->clock_tx, stats->media_tx, stats->subtype_err, stats->media_tx_err); + + os_log(LOG_INFO, "lost: %10u, mr: %10u, tu: %10u, subformat err: %10u, dropped: %10u\n", + stats->pkt_lost, stats->mr, stats->tu, stats->format_err, stats->media_tx_dropped); + + os_log(LOG_INFO,"now-rx_ts %4d/%4d/%4d avtp_ts-now %4d/%4d/%4d (us) batch %2d/%2d/%2d/%2"PRIu64"\n", + stats->avb_delay.min/1000, stats->avb_delay.mean/1000, stats->avb_delay.max/1000, + stats->avtp_delay.min/1000, stats->avtp_delay.mean/1000, stats->avtp_delay.max/1000, + stats->batch.min, stats->batch.mean, stats->batch.max, stats->batch.variance); + + if (msg->clock_rec_enabled) + media_clock_rec_stats_print(&msg->clock_stats); +} + + +static void stream_listener_stats_dump(struct stream_listener *stream, struct ipc_tx *tx) +{ + struct ipc_desc *desc; + struct ipc_avtp_listener_stats *msg; + + desc = ipc_alloc(tx, sizeof(*msg)); + if (!desc) + goto err_ipc_alloc; + + desc->type = IPC_AVTP_STREAM_LISTENER_STATS; + desc->len = sizeof(*msg); + desc->flags = 0; + + msg = (struct ipc_avtp_listener_stats *)&desc->u; + + msg->stream_id = stream->id; + os_memcpy(&msg->stats, &stream->stats, sizeof(stream->stats)); + + if (stream->source) { + struct clock_grid_producer_stream *producer = &stream->source->grid.producer.u.stream; + + if (producer->rec) { + msg->clock_rec_enabled = 1; + media_clock_rec_stats_dump(producer->rec, &msg->clock_stats); + } + } else + msg->clock_rec_enabled = 0; + + stats_reset(&stream->stats.avb_delay); + stats_reset(&stream->stats.avtp_delay); + stats_reset(&stream->stats.batch); + + if (ipc_tx(tx, desc) < 0) + goto err_ipc_tx; + + return; + +err_ipc_tx: + ipc_free(tx, desc); + +err_ipc_alloc: + return; +} + +void stream_stats_dump(struct avtp_port *port, struct ipc_tx *tx) +{ + struct stream_talker *stream_talker; + struct stream_listener *stream_listener; + struct list_head *entry; + + for (entry = list_first(&port->talker); entry != &port->talker; entry = list_next(entry)) { + stream_talker = container_of(entry, struct stream_talker, common.list); + + stream_talker_stats_dump(stream_talker, tx); + } + + for (entry = list_first(&port->listener); entry != &port->listener; entry = list_next(entry)) { + stream_listener = container_of(entry, struct stream_listener, common.list); + + stream_listener_stats_dump(stream_listener, tx); + } +} + +/** Adds a stream to the port talker stream list + * + * \return none + * \param port pointer to port context + * \param stream pointer to talker stream + */ +static void stream_talker_add(struct avtp_port *port, struct stream_talker *stream) +{ + list_add_tail(&port->talker, &stream->common.list); +} + +/** Searches for a stream in the port talker stream list (based on stream id) + * + * \return pointer to the matching stream, NULL if the stream was not found + * \param port pointer to port context + * \param stream_id stream id to match + */ +struct stream_talker *stream_talker_find(struct avtp_port *port, void *stream_id) +{ + struct stream_talker *stream; + struct list_head *entry; + + for (entry = list_first(&port->talker); entry != &port->talker; entry = list_next(entry)) { + stream = container_of(entry, struct stream_talker, common.list); + + if (cmp_64(&stream->id, stream_id)) + return stream; + } + + return NULL; +} + +/** Calculates transmit batch for the stream + * + */ +static unsigned int stream_tx_batch(const struct stream_talker *stream) +{ + unsigned int tx_batch = 0; + unsigned int align_batch, min_batch, avtp_min_batch, max_batch, avtp_max_batch; + unsigned int samples_per_packet; + unsigned int packet_rate_p, packet_rate_q; + + switch (stream->subtype) { + case AVTP_SUBTYPE_AAF: + case AVTP_SUBTYPE_61883_IIDC: + case AVTP_SUBTYPE_CVF: + case AVTP_SUBTYPE_TSCF: + case AVTP_SUBTYPE_NTSCF: + samples_per_packet = stream->frames_per_packet; + + /* align wakeup period to whole packets */ + align_batch = stream->frames_per_interval; + + /* Set wakeup period so that generated packet number, per period, is less than half the maximum supported transmit burst */ + max_batch = ((NET_TX_BATCH / 2) / align_batch) * align_batch; + + packet_rate_p = stream->sample_rate; + packet_rate_q = samples_per_packet; + + min_batch = align_batch; + + avtp_min_batch = ((u64)CFG_AVTP_MIN_LATENCY * packet_rate_p + (u64)packet_rate_q * NSECS_PER_SEC - 1) / ((u64)packet_rate_q * NSECS_PER_SEC); + + avtp_max_batch = ((u64)CFG_AVTP_MAX_LATENCY * packet_rate_p) / ((u64)packet_rate_q * NSECS_PER_SEC); + avtp_max_batch = (avtp_max_batch / align_batch) * align_batch; + + while (min_batch < avtp_min_batch) + min_batch += align_batch; + + if (max_batch > avtp_max_batch) + max_batch = avtp_max_batch; + + /* For high sample rates/low samples per packet or low sample rates/high samples per packet */ + if (min_batch > max_batch) { + os_log(LOG_ERR, "invalid latency/batch target min_batch: %d, max_batch: %d, latency_min_batch: %d, latency_max_batch: %d\n", min_batch, max_batch, avtp_min_batch, avtp_max_batch); + goto err; + } + + tx_batch = ((u64)stream->latency * packet_rate_p) / ((u64)packet_rate_q * NSECS_PER_SEC); + tx_batch = (tx_batch / align_batch) * align_batch; + + if (tx_batch < min_batch) + tx_batch = min_batch; + else if (tx_batch > max_batch) + tx_batch = max_batch; + + break; + + case AVTP_SUBTYPE_CRF: + tx_batch = CRF_TX_BATCH; + break; + + default: + goto err; + break; + } + + os_assert(tx_batch <= NET_TX_BATCH); + + return tx_batch; +err: + return 0; +} + +int stream_clock_consumer_enable(struct stream_talker *stream) +{ + unsigned int ts_freq_p, ts_freq_q, packet_freq_p, packet_freq_q; + unsigned int wake_freq_p, wake_freq_q; + unsigned int ps; + + if (!(stream->common.flags & STREAM_FLAG_CLOCK_GENERATION)) + return 0; + + ts_freq_p = stream->sample_rate; + + if (stream->subtype == AVTP_SUBTYPE_CRF) + ts_freq_p *= 2; + + ts_freq_q = stream->samples_per_timestamp; + + if (!ts_freq_p || !ts_freq_q) { + os_log(LOG_ERR, "talker(%p) invalid ts_freq: %u/%u\n", stream, ts_freq_p, ts_freq_q); + return -1; + } + + packet_freq_p = stream->sample_rate; + packet_freq_q = stream->frames_per_packet; + + stream->time_per_packet = ((u64)NSECS_PER_SEC * packet_freq_q) / packet_freq_p; + + stream->latency = stream->tx_batch * stream->time_per_packet; + + wake_freq_p = packet_freq_p; + wake_freq_q = packet_freq_q * stream->tx_batch; + + if (!wake_freq_p || !wake_freq_q) { + os_log(LOG_ERR, "talker(%p) invalid wake_freq: %u/%u\n", stream, wake_freq_p, wake_freq_q); + return -1; + } + + os_log(LOG_INFO, "talker(%p) media_rx(%p) frames per packet %u, payload size %u, syt interval %u, syt freq %u, latency %u, batch %u\n", + stream, &stream->media, stream->frames_per_packet, stream->payload_size, + stream->samples_per_timestamp, ts_freq_p / ts_freq_q, stream->latency, stream->tx_batch); + + /* Align on media clock grid */ + /* IEEE 1722-2016 4.3.5 and 10.8 */ + /* n x Ps - Ps / 20 < Toffset < n x Ps + Ps / 20. n > 0 */ + /* In practice we just round up to a multiple of Ps = (NSECS_PER_SEC / sample_rate) */ + ps = (NSECS_PER_SEC + stream->sample_rate / 2) / stream->sample_rate; + + if (clock_domain_init_consumer(stream->domain, &stream->consumer, avtp_stream_presentation_offset(stream), + ts_freq_p, ts_freq_q, ps, sr_class_prio(stream->class)) < 0) + goto err_init; + + if (stream->subtype == AVTP_SUBTYPE_CRF) { + if (os_timer_create(&stream->subtype_data.crf.t, stream->domain->source->clock_id, 0, crf_os_timer_handler, stream->priv) < 0) + goto err_timer_create; + + if (os_timer_start(&stream->subtype_data.crf.t, 0, wake_freq_p, wake_freq_q, 0) < 0) + goto err_timer_start; + } else { + if (clock_domain_init_consumer_wakeup(stream->domain, &stream->consumer, wake_freq_p, wake_freq_q) < 0) + goto err_init_wakeup; + } + + stream->consumer_enabled = true; + + return 0; + +err_timer_start: + os_timer_destroy(&stream->subtype_data.crf.t); + +err_timer_create: +err_init_wakeup: + clock_domain_exit_consumer(&stream->consumer); + +err_init: + return -1; +} + +void stream_clock_consumer_disable(struct stream_talker *stream) +{ + if ((stream->common.flags & STREAM_FLAG_CLOCK_GENERATION) && (stream->consumer_enabled)) { + if (stream->subtype == AVTP_SUBTYPE_CRF) + os_timer_destroy(&stream->subtype_data.crf.t); + else + clock_domain_exit_consumer_wakeup(&stream->consumer); + + clock_domain_exit_consumer(&stream->consumer); + + stream->consumer_enabled = false; + } +} + +/** Creates a talker stream context + * + * Allocates memory for the stream context and initializes handles to media stack, media clock capture and network layers + * + * \return pointer to created stream or NULL if the stream couldn't be created/already exists. + * \param avtp pointer to avtp global context + * \param ipc ipc connect message (with all the stream parameters) + */ +struct stream_talker *stream_talker_create(struct avtp_ctx *avtp, struct avtp_port *port, struct ipc_avtp_connect *ipc) +{ + struct stream_talker *stream; + struct net_address addr; + unsigned int hdr_len = 0; + unsigned int flags; + int rc; + + stream = stream_talker_find(port, &ipc->stream_id); + if (stream) { + os_log(LOG_ERR, "Destroying previously existing stream with stream_id(%016"PRIx64")\n", get_ntohll(ipc->stream_id)); + stream_talker_destroy(stream, NULL); + } + + stream = os_malloc(sizeof(*stream)); + if (!stream) + goto err_allocation_failed; + + os_memset(stream, 0, sizeof(*stream)); + + if (!sr_class_enabled(ipc->stream_class)) + goto err_format; + + stream->common.flags |= STREAM_FLAG_SR | STREAM_FLAG_VLAN; + + stream->consumer_enabled = false; + + stream->domain = clock_domain_get(avtp, ipc->clock_domain); + if (!stream->domain) { + os_log(LOG_ERR, "stream_id(%016"PRIx64"), clock_domain_get(%d) failed\n", ntohll(stream->id), ipc->clock_domain); + goto err_clock_domain; + } + + stream->locked_count = stream->domain->locked_count; + + /* Legacy support, set the domain source internally */ + if (ipc->clock_domain < GENAVB_CLOCK_DOMAIN_0) { + if (clock_domain_set_source_legacy(stream->domain, avtp, ipc) < 0) + goto err_clock_domain; + } + + /* Stream params inherited from the application (i.e. not retrieved from AVDDECC format) */ + if (ipc->flags & GENAVB_STREAM_FLAGS_CUSTOM_TSPEC) + stream->common.flags |= STREAM_FLAG_CUSTOM_TSPEC; + + stream->class = ipc->stream_class; + stream->direction = AVTP_DIRECTION_TALKER; + stream->subtype = ipc->subtype; + stream->port = ipc->port; + stream->latency = ipc->talker.latency; + stream->common.avtp = avtp; + + copy_64(&stream->id, &ipc->stream_id); + stream->format = ipc->format; + stream->media_count = 0; + + stream->clock_gptp = port->clock_gptp; + + stream->priv = avtp->priv; + + os_memset(stream->header_template, 0, HEADER_TEMPLATE_SIZE); + + stream->header_len = net_add_eth_header(stream->header_template, ipc->dst_mac, ETHERTYPE_VLAN); + + /* Vlan id and priority are overriden by the network layer */ + stream->header_len += net_add_vlan_header(stream->header_template + stream->header_len, ETHERTYPE_AVTP, 0, 0, 0); + + stream->avtp_hdr = (struct avtp_data_hdr *)(stream->header_template + stream->header_len); + + /* + * Initialize stream parameters and check format - hdr_len is an output + */ + if (is_avtp_stream(ipc->subtype)) + rc = stream_talker_format_check(stream, ipc, &stream->format); + else if (is_avtp_alternative(ipc->subtype)) + rc = alternative_talker_format_check(stream, ipc, &stream->format); + else + rc = -1; + + if (rc < 0) { + os_log(LOG_ERR, "stream_id(%016"PRIx64"), stream format check failed: rc = %d\n", ntohll(stream->id), rc); + goto err_format; + } + + if (stream->common.flags & STREAM_FLAG_CUSTOM_TSPEC) { + if (avtp_fmt_sample_size(ipc->subtype, &stream->format)) + stream->frames_per_packet = ipc->talker.max_frame_size / avtp_fmt_sample_size(ipc->subtype, &stream->format); + else + stream->frames_per_packet = ipc->talker.max_frame_size; + + stream->payload_size = ipc->talker.max_frame_size; + + stream->frames_per_interval = ipc->talker.max_interval_frames; + + stream->sample_rate = ((u64)stream->payload_size * NSECS_PER_SEC * sr_class_interval_q(stream->class)) / sr_class_interval_p(stream->class); + + stream->samples_per_timestamp = samples_per_interval(stream->sample_rate, stream->class); + } else { + stream->frames_per_packet = __avdecc_fmt_samples_per_packet(&stream->format, stream->class, &stream->frames_per_interval); + + stream->payload_size = stream->frames_per_packet * avdecc_fmt_sample_stride(&ipc->format); + + stream->sample_rate = avdecc_fmt_sample_rate(&stream->format); + + stream->samples_per_timestamp = avdecc_fmt_samples_per_timestamp(&stream->format, stream->class); + } + + stream->init(stream, &hdr_len); + + stream->header_len += hdr_len; + + if (ipc->flags & IPC_AVTP_FLAGS_MAX_TRANSIT_TIME_VALID) { + if ((ipc->talker.max_transit_time < STREAM_TALKER_MIN_PRESENTATION_TIME_OFFSET_NS) || + (ipc->talker.max_transit_time > STREAM_TALKER_MAX_PRESENTATION_TIME_OFFSET_NS)) { + os_log(LOG_ERR, "stream_id(%016"PRIx64"), stream max_transit_time(%u) out of range [%u, %u]\n", + ntohll(stream->id), ipc->talker.max_transit_time, STREAM_TALKER_MIN_PRESENTATION_TIME_OFFSET_NS, STREAM_TALKER_MAX_PRESENTATION_TIME_OFFSET_NS); + goto err_transit_time; + } + + stream->max_transit_time = ipc->talker.max_transit_time; + } else { + stream->max_transit_time = sr_class_max_transit_time(stream->class); + } + + addr.ptype = PTYPE_AVTP; + addr.port = ipc->port; + addr.vlan_id = ipc->talker.vlan_id; + addr.priority = ipc->talker.priority; + addr.u.avtp.subtype = ipc->subtype; + addr.u.avtp.sr_class = stream->class; + copy_64(addr.u.avtp.stream_id, &stream->id); + + if (net_tx_init(&stream->tx, &addr) < 0) + goto err_tx_init; + + flags = 0; + + if (stream->common.flags & STREAM_FLAG_MEDIA_WAKEUP) + flags |= MEDIA_FLAG_WAKEUP; + + stats_init(&stream->stats.sched_intvl, 31, NULL, NULL); + if (os_clock_gettime32(stream->clock_gptp, &stream->gptp_current) < 0) + stream->stats.gptp_err++; + + stream->tx_batch = stream_tx_batch(stream); + if (!stream->tx_batch) + goto err_tx_batch; + + if (stream_clock_consumer_enable(stream) < 0) + goto err_clock_enable; + + if (!(stream->common.flags & STREAM_FLAG_NO_MEDIA)) + if (media_rx_init(&stream->media, &stream->id, avtp->priv, flags, stream->header_len, stream_presentation_offset(stream->max_transit_time, stream->latency)) < 0) + goto err_rx_init; + + stream_talker_add(port, stream); + + os_log(LOG_INFO, "talker_stream_id(%016"PRIx64") class(%d) format(%016"PRIx64") domain(%p): %d\n", + ntohll(stream->id), stream->class, get_ntohll(ipc->format.u.raw), stream->domain, stream->domain->id); + + return stream; + +err_rx_init: + stream_clock_consumer_disable(stream); + +err_clock_enable: +err_tx_batch: + net_tx_exit(&stream->tx); + +err_tx_init: +err_transit_time: +err_format: +err_clock_domain: + os_free(stream); + +err_allocation_failed: + return NULL; +} + +/** Destroys a talker stream context + * + * \return none + * \param stream pointer to talker stream to destroy + */ +void stream_talker_destroy(struct stream_talker *stream, struct ipc_tx *tx) +{ + os_log(LOG_INFO, "talker_stream_id(%016"PRIx64")\n", ntohll(stream->id)); + + if (tx) + stream_talker_stats_dump(stream, tx); + + stream_clock_consumer_disable(stream); + + if (!(stream->common.flags & STREAM_FLAG_NO_MEDIA)) + media_rx_exit(&stream->media); + + net_tx_exit(&stream->tx); + + list_del(&stream->common.list); + list_add_tail(&stream->common.avtp->stream_destroyed, &stream->common.list); +} + +/** Adds a stream to the port listener stream list + * + * \return none + * \param port pointer to port context + * \param stream pointer to listener stream + */ +static void stream_listener_add(struct avtp_port *port, struct stream_listener *stream) +{ + list_add_tail(&port->listener, &stream->common.list); +} + +/** Searches for a stream in the port listener stream list (based on stream id and class) + * + * \return pointer to the matching stream, NULL if the stream was not found + * \param port pointer to port context + * \param stream_id stream id to match + * \param class stream class to match + */ +struct stream_listener *stream_listener_find(struct avtp_port *port, void *stream_id) +{ + struct stream_listener *stream; + struct list_head *entry; + + for (entry = list_first(&port->listener); entry != &port->listener; entry = list_next(entry)) { + stream = container_of(entry, struct stream_listener, common.list); + + if (cmp_64(&stream->id, stream_id)) + return stream; + } + + return NULL; +} + + +static unsigned int stream_rx_batch(const struct stream_listener *stream, unsigned int *latency) +{ + unsigned int rx_batch; + + /* FIXME, take into account media queue batch size */ + + switch (stream->subtype) { + case AVTP_SUBTYPE_AAF: + case AVTP_SUBTYPE_61883_IIDC: + case AVTP_SUBTYPE_CVF: + rx_batch = sr_class_max_pending_packets[stream->class]; + *latency = (rx_batch * sr_class_interval_p(stream->class)) / sr_class_interval_q(stream->class); + break; + + case AVTP_SUBTYPE_TSCF: + case AVTP_SUBTYPE_NTSCF: + case AVTP_SUBTYPE_CRF: + rx_batch = 1; + *latency = 0; + break; + + default: + rx_batch = 0; + break; + } + + return rx_batch; +} + +/** Creates a listener stream context + * + * Allocates memory for the stream context and initializes handles to media stack, media clock recovery and network layers + * + * \return pointer to created stream or NULL if the stream couldn't be created/already exists. + * \param avtp pointer to avtp global context + * \param ipc ipc connect message (with all the stream parameters) + */ +struct stream_listener *stream_listener_create(struct avtp_ctx *avtp, struct avtp_port *port, struct ipc_avtp_connect *ipc) +{ + struct stream_listener *stream; + struct net_address addr; + unsigned int rx_batch, rx_latency; + int rc; + + if (!sr_class_enabled(ipc->stream_class)) { + os_log(LOG_ERR, "listener_stream_id(%016"PRIx64") class invalid: %d\n", get_ntohll(ipc->stream_id), ipc->stream_class); + goto err_class_invalid; + } + + stream = stream_listener_find(port, ipc->stream_id); + if (stream) { + os_log(LOG_ERR, "Destroying previously existing stream with stream_id(%016"PRIx64")\n", get_ntohll(ipc->stream_id)); + stream_listener_destroy(stream, NULL); + } + + stream = os_malloc(sizeof(*stream)); + if (!stream) + goto err_allocation_failed; + + os_memset(stream, 0, sizeof(*stream)); + + stream->class = ipc->stream_class; + stream->direction = AVTP_DIRECTION_LISTENER; + stream->common.avtp = avtp; + + copy_64(&stream->id, ipc->stream_id); + + stream->subtype = ipc->subtype; + + stream->format = ipc->format; + + stream->clock_gptp = port->clock_gptp; + + /* + * Initialize stream parameters and check format + */ + if (is_avtp_stream(ipc->subtype)) + rc = stream_listener_format_check(stream, ipc, &stream->format, ipc->flags); + else if (is_avtp_alternative(ipc->subtype)) + rc = alternative_listener_format_check(stream, ipc, &stream->format, ipc->flags); + else + rc = -1; + + if (rc < 0) { + os_log(LOG_ERR, "stream_id(%016"PRIx64"), stream format check failed: rc = %d\n", ntohll(stream->id), rc); + goto err_format; + } + + if ((rc = stream->init(stream)) < 0) + goto err_init; + + stream->max_transit_time = sr_class_max_transit_time(stream->class); + stream->max_timing_uncertainty = sr_class_max_timing_uncertainty(stream->class); + + addr.ptype = PTYPE_AVTP; + addr.port = ipc->port; + addr.u.avtp.subtype = ipc->subtype; + addr.u.avtp.sr_class = stream->class; + copy_64(addr.u.avtp.stream_id, &stream->id); + + rx_batch = stream_rx_batch(stream, &rx_latency); + if (!rx_batch) + goto err_rx_batch; + + if (is_avtp_stream(ipc->subtype)) + rc = net_rx_init_multi(&stream->rx, &addr, avtp_stream_net_rx, rx_batch, rx_latency, avtp->priv); + else + rc = net_rx_init_multi(&stream->rx, &addr, avtp_alternative_net_rx, rx_batch, rx_latency, avtp->priv); + + if (rc < 0) + goto err_net; + + if (!(stream->common.flags & STREAM_FLAG_NO_MEDIA)) + if (media_tx_init(&stream->media, &stream->id) < 0) + goto err_media; + + stream->domain = clock_domain_get(avtp, ipc->clock_domain); + if (!stream->domain) { + os_log(LOG_ERR, "stream_id(%016"PRIx64"), clock_domain_get(%d) failed\n", ntohll(stream->id), ipc->clock_domain); + goto err_clock_domain; + } + + /* Legacy support, set the domain source internally */ + if (ipc->clock_domain < GENAVB_CLOCK_DOMAIN_0) { + if (clock_domain_set_source_legacy(stream->domain, avtp, ipc) < 0) + goto err_clock_domain; + } + + /* Setup stream source (new and legacy clock API) */ + if ((ipc->flags & IPC_AVTP_FLAGS_MCR) + && (clock_domain_is_source_stream(stream->domain, &stream->id))) + if (__clock_domain_update_source(stream->domain, stream->domain->source, stream) < 0) { + os_log(LOG_ERR, "stream_id(%016"PRIx64"), __clock_domain_update_source failed\n", ntohll(stream->id)); + goto err_clock_source; + } + + /* + * FIXME, we should really fail at this stage. + * Need to fix AVDECC/entities + */ + if (stream->source) { + os_log(LOG_INFO, "stream is a clock source\n"); + + if (stream->source->grid.producer.u.stream.rec) + os_log(LOG_INFO, "MCR enabled\n"); + else + os_log(LOG_INFO, "MCR disabled\n"); + } + + os_memcpy(stream->dst_mac, ipc->dst_mac, 6); + stream->port = ipc->port; + + if (net_add_multi(&stream->rx, stream->port, stream->dst_mac) < 0) { + os_log(LOG_ERR, "listener_stream_id(%016"PRIx64") cannot register MC address\n", ntohll(stream->id)); + goto err_multi; + } + + stats_init(&stream->stats.avb_delay, 31, NULL, NULL); + stats_init(&stream->stats.avtp_delay, 31, NULL, NULL); + stats_init(&stream->stats.batch, 31, NULL, NULL); + + stream_listener_add(port, stream); + + os_log(LOG_INFO, "listener_stream_id(%016"PRIx64") class(%d) format(%016"PRIx64") domain(%p): %d\n", + ntohll(stream->id), stream->class, get_ntohll(&stream->format), stream->domain, stream->domain->id); + + return stream; + +err_multi: + if (stream->source) + clock_source_close(stream->source); + +err_clock_source: +err_clock_domain: + if (!(stream->common.flags & STREAM_FLAG_NO_MEDIA)) + media_tx_exit(&stream->media); + +err_media: + net_rx_exit(&stream->rx); + +err_net: + if (stream->exit) + stream->exit(stream); + +err_rx_batch: +err_init: +err_format: + os_free(stream); + +err_allocation_failed: +err_class_invalid: + return NULL; +} + +/** Destroys a listener stream context + * + * \return none + * \param stream pointer to listener stream to destroy + */ +void stream_listener_destroy(struct stream_listener *stream, struct ipc_tx *tx) +{ + os_log(LOG_INFO, "listener_stream_id(%016"PRIx64")\n", ntohll(stream->id)); + + if (stream->exit) + stream->exit(stream); + + net_del_multi(&stream->rx, stream->port, stream->dst_mac); + + net_rx_exit(&stream->rx); + + if (!(stream->common.flags & STREAM_FLAG_NO_MEDIA)) + media_tx_exit(&stream->media); + + if (stream->source) + clock_source_close(stream->source); + + list_del(&stream->common.list); + list_add_tail(&stream->common.avtp->stream_destroyed, &stream->common.list); + + if (tx) + stream_listener_stats_dump(stream, tx); +} + +void stream_free_all(struct avtp_ctx *avtp) +{ + struct stream_common *stream; + struct list_head *entry; + + entry = list_first(&avtp->stream_destroyed); + while (entry != &avtp->stream_destroyed) { + stream = container_of(entry, struct stream_common, list); + entry = list_next(entry); + + list_del(&stream->list); + os_free(stream); + } +} + +void avtp_stream_free(void *avtp_ctx, u64 current_time) +{ + struct avtp_ctx *avtp = (struct avtp_ctx*)avtp_ctx; + struct stream_common *stream; + struct list_head *entry; + + entry = list_first(&avtp->stream_destroyed); + while (entry != &avtp->stream_destroyed) { + stream = container_of(entry, struct stream_common, list); + entry = list_next(entry); + + if (!(stream->flags & STREAM_FLAG_DESTROYED)) { + stream->flags |= STREAM_FLAG_DESTROYED; + stream->destroy_time = current_time; + } else if ((current_time - stream->destroy_time) >= STREAM_DESTROYED_FREE_DELAY_NS) { + list_del(&stream->list); + + os_log(LOG_INFO, "stream(%p)\n", stream); + + os_free(stream); + } + } +} + +int stream_media_rx(struct stream_talker *stream, struct media_rx_desc **media_desc_array, u32 *ts, unsigned int *flags, unsigned int * alignment_ts) +{ + u32 i; + int rc; + struct media_rx_desc *media_desc; + unsigned int do_align = 0, underrun = 0, tx_batch; + + /* Get samples from media stack. If running behind (late > 0), try to read one extra packet to progressively recover. */ + if (stream->late) + tx_batch = stream->tx_batch + 1; + else + tx_batch = stream->tx_batch; + + if (unlikely((rc = media_rx(&stream->media, media_desc_array, tx_batch)) < 0)) { + stream->stats.media_err++; +// os_log(LOG_ERR, "talker(%p) media.rx failed\n", stream); + goto media_rx_fail; + } + + /* Keep track of late packets to trigger underrun condition + * Only trigger underrun if we have some received some data. + * "Late" is relative to number of "number of wakeups" x "tx_batch", + * we increment it each time we read less than stream->tx_batch packets from the stack, + * we decrement it each time we read more */ + if ((stream->media_count != 0) || (rc != 0)) + stream->late += stream->tx_batch - (unsigned int)rc; + + stream->stats.media_rx += rc; + + for (i = 0; i < rc; i++) { + media_desc = media_desc_array[i]; + if (media_desc->ts_n) { + do_align = 1; + stream->late = 0; + *alignment_ts = media_desc->avtp_ts[0].val; // Take the first timestamp of the first packet with timestamps + //FIXME if the timestamp is not for the first byte of the first packet, this can add an error of up to batch size + break; + } + } + + if ((stream->late > stream->tx_batch) && rc) { + stream->stats.media_underrun++; + stream->late = 0; + underrun = 1; + } + + if (unlikely((((stream->media_count == 0) && rc) || underrun || stream_domain_phase_change(stream)) && !do_align)) { + do_align = 1; + *alignment_ts = stream->gptp_current + avtp_stream_presentation_offset(stream); + } + + if (unlikely(do_align)) + *flags |= MCG_FLAGS_DO_ALIGN; + + return rc; + +media_rx_fail: + stream->late = 0; + + return rc; +} + + +int stream_tx_flow_control(struct stream_talker *stream, unsigned int *tx_batch) +{ + unsigned int tx_avail; + int rc = 0; + + /* check the amount of free space in the network transmit queue */ + tx_avail = net_tx_available(&stream->tx); + + if (tx_avail < *tx_batch) { + /* transmit is congested, enable flow control if not yet done */ + if(!stream->tx_event_enabled) { + /*disable media queue events so we stop dequeuing data from the media interface */ + media_rx_event_disable(&stream->media); + + /* register event notification from the network interface to signal + the avtp thread when there is free space again in the transmit queue */ + net_tx_event_enable(&stream->tx, stream->priv); + + stream->tx_event_enabled = 1; + + /* if the transmit queue is full, we do not transmit any data at this round, + else we just transmit what can be transmitted */ + if (!tx_avail) + rc = -1; + else + *tx_batch = tx_avail; + } else + /* we are already in flow controlled state, nothing should be transmitted */ + rc = -1; + } else { + /* network transmit queue is not congested (at least one batch of free slots in the queue), make sure flow control is now disabled */ + if (stream->tx_event_enabled) { + /* disable network queue events, i.e. do not listen anymore to the event from network interface */ + net_tx_event_disable(&stream->tx); + + /* enable media queue events and allow dequeuing from the media interface */ + media_rx_event_enable(&stream->media); + + /* back to normal operation, no restriction on transmit */ + stream->tx_event_enabled = 0; + } + } + + os_log(LOG_DEBUG, "stream(%p) tx_batch %u tx_avail %u flow_control_enabled %u rc %d\n", stream, *tx_batch, tx_avail, stream->tx_event_enabled, rc); + + return rc; +} diff --git a/avtp/stream.h b/avtp/stream.h new file mode 100644 index 0000000..52f7c3d --- /dev/null +++ b/avtp/stream.h @@ -0,0 +1,353 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Stream handling functions + @details +*/ + +#ifndef _STREAM_H_ +#define _STREAM_H_ + +#include "os/sys_types.h" +#include "common/list.h" + +#include "avtp.h" + +#include "genavb/media.h" +#include "os/media.h" +#include "media_clock.h" + +#define HEADER_TEMPLATE_SIZE 64 + +#define STREAM_FLAG_VLAN (1 << 0) /* Stream is vlan tagged */ +#define STREAM_FLAG_SR (1 << 1) /* Stream has a stream reservation */ +#define STREAM_FLAG_MEDIA_WAKEUP (1 << 2) /* Stream processing started by media interface (instead of clock generation) */ +#define STREAM_FLAG_CLOCK_GENERATION (1 << 3) /* Stream requires clock generation */ +#define STREAM_FLAG_NO_MEDIA (1 << 4) /* Stream doesn't exchange data with media interface */ +#define STREAM_FLAG_CUSTOM_TSPEC (1 << 5) /* Stream params inherited from the media interface */ +#define STREAM_FLAG_DESTROYED (1 << 6) /* Stream is destroyed and waits to be freed */ + +/** Common stream context + * + * Common fields to Listener and Talker streams + */ +struct stream_common { + struct avtp_ctx *avtp; + struct list_head list; + u64 destroy_time; + unsigned int flags; +}; + +/** Listener stream context. + * + * Each listener stream contains a network receive context. There is a one to one mapping between streams + * and receive queues. + */ +struct stream_listener { + struct stream_common common; /* Must be placed at the start of the structure */ + + struct clock_domain *domain; + struct clock_source *source; + + u64 id; /**< AVTP stream_id, stored as Big Endian */ + u8 dst_mac[6]; + u16 port; + + os_clock_id_t clock_gptp; + + avtp_direction_t direction; + + sr_class_t class; + + void (*net_rx)(struct stream_listener *, struct avtp_rx_desc **, unsigned int); + int (*init)(struct stream_listener *); + void (*exit)(struct stream_listener *); + + struct net_rx rx; + + struct media_tx media; + + struct avdecc_format format; + + unsigned int subtype; + u8 sequence_num; + unsigned int mr; + unsigned int pkt_received; + u32 gptp_current; /* gptp snapshot taken at the start of the stream batch processing */ + + u32 max_transit_time; + u32 max_timing_uncertainty; + + union { + struct { + unsigned int current_frame_offset; + } cvf; + + struct { + unsigned int syt_interval_ln2; + u8 syt_count; + } iec61883_6; + + struct { + u32 hdr[2]; + u32 hdr_mask[2]; + } aaf; + + struct crf_subtype_data { + /* Header data to match on receive (starting at pull field) */ + u32 hdr[2]; + u8 type; + unsigned int ts_last; + unsigned int ts_last_set; + unsigned int received_ts_last; + unsigned int state; + unsigned int period_nominal; + unsigned int period_err; + unsigned int period; + unsigned int timestamp; + struct timer timer; + unsigned int free_wheeling_to_locked_delay; + } crf; + } subtype_data; + + struct listener_stats { + unsigned int rx; + unsigned int media_tx; + unsigned int clock_tx; + unsigned int pkt_lost; + unsigned int mr; + unsigned int tu; + unsigned int subtype_err; + unsigned int format_err; + unsigned int media_tx_err; + unsigned int media_tx_dropped; + unsigned int gptp_err; + + struct stats avb_delay; + struct stats avtp_delay; + struct stats batch; + } stats; +}; + +/** Talker stream context + * + * Each talker stream contains a network transmit context. There is a one to one mapping between streams + * and transmit queues. + */ +struct stream_talker { + struct stream_common common; /* Must be placed at the start of the structure */ + + struct clock_domain *domain; + unsigned int locked_count; + + u64 id; + + avtp_direction_t direction; + + sr_class_t class; + unsigned int subtype; + u16 port; + + os_clock_id_t clock_gptp; + + void (*net_tx)(struct stream_talker *); + void (*init)(struct stream_talker *, unsigned int *); + + struct net_tx tx; + + struct media_rx media; + + struct avdecc_format format; + unsigned int payload_size; + unsigned int frames_per_interval; + unsigned int frames_per_packet; + unsigned int media_count; + unsigned int sample_rate; + unsigned int samples_per_timestamp; + + struct clock_grid_consumer consumer; // Uses a grid with a MULT producer, itself using a source with a HW producer + bool consumer_enabled; + + unsigned int ts_n; + unsigned int tx_batch; + unsigned int late; + unsigned int frame_with_ts; + unsigned int time_per_packet; + unsigned int ts_last; + u32 ts_launch; + u32 gptp_current; /* gptp snapshot taken at the start of the stream batch processing */ + + unsigned int ts_media_prev; + + unsigned int header_len; + + unsigned int tx_event_enabled; + unsigned long priv; + + union { + struct { + unsigned int syt_interval_ln2; + struct iec_61883_hdr *iec_hdr; + } iec61883; + + struct { + unsigned int frames_per_timestamp; + unsigned int sparse; + unsigned int tx_count; + } aaf; + + struct { + u32 ts_msb; + u32 ts_period; + struct os_timer t; + } crf; + + struct { + struct cvf_h264_hdr *h264_hdr; + unsigned int prev_incomplete_nal; + unsigned int is_nalu_ts_valid; + u32 h264_timestamp; + u8 nalu_header; + } cvf_h264; + } subtype_data; + + unsigned int latency; + unsigned int max_transit_time; + + struct avtp_data_hdr *avtp_hdr; + u8 header_template[HEADER_TEMPLATE_SIZE]; + + struct talker_stats { + unsigned int tx; + unsigned int tx_err; + unsigned int media_rx; + unsigned int media_err; + unsigned int media_underrun; + unsigned int clock_rx; + unsigned int clock_err; + unsigned int partial; + unsigned int clock_invalid; + unsigned int gptp_err; + + struct stats sched_intvl; + } stats; +}; + +struct ipc_avtp_listener_stats { + avb_u64 stream_id; + struct listener_stats stats; + unsigned int clock_rec_enabled; + + struct ipc_avtp_clock_rec_stats clock_stats; +}; + +struct ipc_avtp_talker_stats { + avb_u64 stream_id; + struct talker_stats stats; +}; + +#define stream_destroy(stream, ipc_tx) \ +{ \ + if ((stream)->direction == AVTP_DIRECTION_LISTENER) \ + stream_listener_destroy((struct stream_listener *)stream, ipc_tx); \ + else \ + stream_talker_destroy((struct stream_talker *)stream, ipc_tx); \ +} + +struct stream_listener *stream_listener_find(struct avtp_port *port, void *stream_id); +struct stream_talker *stream_talker_find(struct avtp_port *port, void *stream_id); + +void avtp_latency_stats(struct stream_listener *stream, struct avtp_rx_desc *desc); + +struct stream_listener *stream_listener_create(struct avtp_ctx *avtp, struct avtp_port *port, struct ipc_avtp_connect *params); +void stream_listener_destroy(struct stream_listener *stream, struct ipc_tx *tx); + +struct stream_talker *stream_talker_create(struct avtp_ctx *avtp, struct avtp_port *port, struct ipc_avtp_connect *params); +void stream_talker_destroy(struct stream_talker *stream, struct ipc_tx *tx); +int stream_media_rx(struct stream_talker *stream, struct media_rx_desc **media_desc_array, u32 *ts, unsigned int *flags, unsigned int * alignment_ts); + +int stream_tx_flow_control(struct stream_talker *stream, unsigned int *tx_batch); + +void stream_stats_dump(struct avtp_port *port, struct ipc_tx *tx); +void stream_talker_stats_print(struct ipc_avtp_talker_stats *msg); +void stream_listener_stats_print(struct ipc_avtp_listener_stats *msg); + +void stream_free_all(struct avtp_ctx *avtp); + +int stream_clock_consumer_enable(struct stream_talker *stream); +void stream_clock_consumer_disable(struct stream_talker *stream); + +unsigned int avtp_stream_presentation_offset(struct stream_talker *stream); + +static inline void stream_net_tx_handler(struct stream_talker *stream) +{ + u32 current_time; + + if (os_clock_gettime32(stream->clock_gptp, ¤t_time) < 0) { + stream->stats.gptp_err++; + } else { + stats_update(&stream->stats.sched_intvl, current_time - stream->gptp_current); + stream->gptp_current = current_time; + } + + stream->net_tx(stream); +} + +static inline int stream_net_tx(struct stream_talker *stream, struct media_rx_desc **desc, unsigned int n) +{ + int rc; + + /* Send packets */ + rc = net_tx_multi(&stream->tx, (struct net_tx_desc **)desc, n); + if (rc < (int)n) { + if (rc > 0) { + stream->stats.tx += rc; + stream->stats.tx_err += n - rc; + } else + stream->stats.tx_err += n; + + return -1; + } + + stream->stats.tx += rc; + + return 0; +} + +static inline int stream_media_tx(struct stream_listener *stream, struct media_desc **desc, unsigned int n) +{ + int rc; + + rc = media_tx(&stream->media, desc, n); + if (rc < (int)n) { + if (rc < 0) { + stream->stats.media_tx_err++; + stream->stats.media_tx_dropped += n; + } + else + stream->stats.media_tx_dropped += n - rc; + } + + if (rc > 0) + stream->stats.media_tx += rc; + + return rc; +} + +static inline unsigned int stream_domain_phase_change(struct stream_talker *stream) +{ + if (stream->locked_count != stream->domain->locked_count) { + stream->locked_count = stream->domain->locked_count; + return 1; + } + + return 0; +} + +#endif /* _STREAM_H_ */ diff --git a/common/61883_iidc.c b/common/61883_iidc.c new file mode 100644 index 0000000..f7d2caa --- /dev/null +++ b/common/61883_iidc.c @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Common 61883 IIDC functions + @details +*/ + +#include "common/61883_iidc.h" + +const avb_u32 avtp_61883_6_sampling_freq[IEC_61883_6_FDF_SFC_MAX + 1] = { + [IEC_61883_6_FDF_SFC_32000] = 32000, + [IEC_61883_6_FDF_SFC_44100] = 44100, + [IEC_61883_6_FDF_SFC_48000] = 48000, + [IEC_61883_6_FDF_SFC_88200] = 88200, + [IEC_61883_6_FDF_SFC_96000] = 96000, + [IEC_61883_6_FDF_SFC_176400] = 176400, + [IEC_61883_6_FDF_SFC_192000] = 192000, + [IEC_61883_6_FDF_SFC_RSVD] = 0, +}; diff --git a/common/61883_iidc.h b/common/61883_iidc.h new file mode 100644 index 0000000..6196dd1 --- /dev/null +++ b/common/61883_iidc.h @@ -0,0 +1,21 @@ +/* + * Copyright 2014-2015 Freescale Semiconductor, Inc. + * Copyright 2017-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file 61883_iidc.h + @brief Common 61883_IIDC definitions + @details +*/ + +#ifndef _PROTO_61883_H_ +#define _PROTO_61883_H_ + +#include "genavb/61883_iidc.h" + +extern const avb_u32 avtp_61883_6_sampling_freq[IEC_61883_6_FDF_SFC_MAX + 1]; + +#endif diff --git a/common/aaf.c b/common/aaf.c new file mode 100644 index 0000000..ad355ce --- /dev/null +++ b/common/aaf.c @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Common AAF functions + @details +*/ + +#include "common/aaf.h" + +const avb_u32 avtp_aaf_sampling_freq[AAF_NSR_MAX + 1] = { + [AAF_NSR_USER_SPECIFIED] = 48000, /* FIXME */ + [AAF_NSR_8000] = 8000, + [AAF_NSR_16000] = 16000, + [AAF_NSR_32000] = 32000, + [AAF_NSR_44100] = 44100, + [AAF_NSR_48000] = 48000, + [AAF_NSR_88200] = 88200, + [AAF_NSR_96000] = 96000, + [AAF_NSR_176400] = 176400, + [AAF_NSR_192000] = 192000, + [AAF_NSR_24000] = 24000, + [AAF_NSR_RESERVED1] = 0, + [AAF_NSR_RESERVED2] = 0, + [AAF_NSR_RESERVED3] = 0, + [AAF_NSR_RESERVED4] = 0, + [AAF_NSR_RESERVED5] = 0 +}; diff --git a/common/aaf.h b/common/aaf.h new file mode 100644 index 0000000..e3dfa8b --- /dev/null +++ b/common/aaf.h @@ -0,0 +1,19 @@ +/* + * Copyright 2016-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file aaf.h + @brief Common AAF definitions + @details +*/ +#ifndef _PROTO_AAF_H_ +#define _PROTO_AAF_H_ + +#include "genavb/aaf.h" + +extern const avb_u32 avtp_aaf_sampling_freq[AAF_NSR_MAX + 1]; + +#endif diff --git a/common/acmp.h b/common/acmp.h new file mode 100644 index 0000000..f590fff --- /dev/null +++ b/common/acmp.h @@ -0,0 +1,44 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file acmp.h + @brief ACMP protocol common definitions + @details PDU and protocol definitions for all ACMP applications +*/ + +#ifndef _PROTO_ACMP_H_ +#define _PROTO_ACMP_H_ + +#include "genavb/acmp.h" +#include "common/types.h" +#include "common/avtp.h" + +struct __attribute__ ((packed)) acmp_pdu { + u64 stream_id; + u64 controller_entity_id; + u64 talker_entity_id; + u64 listener_entity_id; + u16 talker_unique_id; + u16 listener_unique_id; + u8 stream_dest_mac[6]; + u16 connection_count; + u16 sequence_id; + u16 flags; + u16 stream_vlan_id; + u16 rsvd; +}; + +#define OFFSET_TO_ACMP (sizeof(struct eth_hdr) + sizeof(struct avtp_ctrl_hdr)) +#define ACMP_NET_DATA_SIZE (OFFSET_TO_ACMP + sizeof(struct acmp_pdu)) +#define ACMP_IS_LISTENER_COMMAND(msg_type) ((msg_type == ACMP_CONNECT_RX_COMMAND) || (msg_type == ACMP_GET_RX_STATE_COMMAND) || (msg_type == ACMP_DISCONNECT_RX_COMMAND)) +#define ACMP_IS_TALKER_COMMAND(msg_type) ((msg_type == ACMP_CONNECT_TX_COMMAND) || (msg_type == ACMP_GET_TX_STATE_COMMAND) || (msg_type == ACMP_DISCONNECT_TX_COMMAND) || (msg_type == ACMP_GET_TX_CONNECTION_COMMAND)) +#define ACMP_IS_COMMAND(msg_type) (!(msg_type & 0x1)) + +#define ACMP_PDU_LEN 44 + +#endif /* _PROTO_ACMP_H_ */ diff --git a/common/adp.h b/common/adp.h new file mode 100644 index 0000000..b32c99f --- /dev/null +++ b/common/adp.h @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file adp.h + @brief ADP protocol common definitions + @details PDU and protocol definitions for all ADP applications +*/ + +#ifndef _PROTO_ADP_H_ +#define _PROTO_ADP_H_ + +#include "common/types.h" +#include "common/avtp.h" +#include "genavb/adp.h" + +struct __attribute__ ((packed)) adp_pdu { + u64 entity_id; + u64 entity_model_id; + u32 entity_capabilities; + u16 talker_stream_sources; + u16 talker_capabilities; + u16 listener_stream_sinks; + u16 listener_capabilities; + u32 controller_capabilities; + u32 available_index; + u64 gptp_grandmaster_id; + u8 gptp_domain_number; + u8 rsvd0; + u16 rsvd1; + u16 identity_control_index; + u16 interface_index; + u64 association_id; + u32 rsvd2; +}; + + +#define ADP_PDU_LEN 56 +#define OFFSET_TO_ADP (sizeof(struct eth_hdr) + sizeof(struct avtp_ctrl_hdr)) +#define ADP_NET_DATA_SIZE (OFFSET_TO_ADP + sizeof(struct adp_pdu)) + +#endif /* _PROTO_ADP_H_ */ diff --git a/common/aecp.h b/common/aecp.h new file mode 100644 index 0000000..f6e362c --- /dev/null +++ b/common/aecp.h @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2015 Freescale Semiconductor, Inc. + * Copyright 2017-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file aecp.h + @brief AECP protocol common definitions + @details PDU and protocol definitions for all AECP applications +*/ + +#ifndef _PROTO_AECP_H_ +#define _PROTO_AECP_H_ + +#include "common/types.h" +#include "common/avtp.h" +#include "genavb/aecp.h" + +#define OFFSET_TO_AECP (sizeof(struct eth_hdr) + sizeof(struct avtp_ctrl_hdr)) +#define OFFSET_TO_AECP_SPECIFIC (sizeof(struct eth_hdr) + sizeof(struct avtp_ctrl_hdr) + sizeof(struct aecp_aem_pdu)) + +/* 250 ms timeout for all AECP commands (1722.1-2013 section 9.2.1.2.5) */ +#define AECP_COMMANDS_TIMEOUT 250 +#define AECP_IN_PROGRESS_TIMEOUT 120 + + +#endif /* _PROTO_AECP_H_ */ diff --git a/common/avdecc.c b/common/avdecc.c new file mode 100644 index 0000000..eb8c81f --- /dev/null +++ b/common/avdecc.c @@ -0,0 +1,398 @@ +/* + * Copyright 2014-2015 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file avdecc.c + @brief AVDECC protocol common functions implementations +*/ + +#include "genavb/avdecc.h" +#include "genavb/sr_class.h" +#include "avtp.h" +#include "61883_iidc.h" +#include "aaf.h" +#include "cvf.h" + +unsigned int avdecc_fmt_hdr_size(const struct avdecc_format *format) +{ + unsigned int hdr_size = 0; + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_4: + case IEC_61883_CIP_FMT_6: + hdr_size = sizeof(struct iec_61883_hdr); + break; + + default: + /* FIXME */ + break; + } + + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + if (format->u.s.subtype_u.cvf.format == CVF_FORMAT_RFC) { + switch (format->u.s.subtype_u.cvf.subtype) { + case CVF_FORMAT_SUBTYPE_MJPEG: + hdr_size = sizeof (struct cvf_mjpeg_hdr); + break; + + case CVF_FORMAT_SUBTYPE_H264: + hdr_size = sizeof (struct cvf_h264_hdr); + break; + + case CVF_FORMAT_SUBTYPE_JPEG2000: + default: + break; + } + } + + break; + + case AVTP_SUBTYPE_AAF: + break; + + case AVTP_SUBTYPE_CRF: + break; + + case AVTP_SUBTYPE_TSCF: + break; +#endif + default: + break; + } + + return hdr_size; +} + + +unsigned int avdecc_fmt_sample_stride(const struct avdecc_format *format) +{ + unsigned int sample_stride = 0; + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + if (format->u.s.subtype_u.iec61883.sf == IEC_61883_SF_IIDC) + break; + + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_4: + sample_stride = IEC_61883_4_SP_SIZE; + break; + + case IEC_61883_CIP_FMT_6: + sample_stride = (format->u.s.subtype_u.iec61883.format_u.iec61883_6.dbs == 0?256:format->u.s.subtype_u.iec61883.format_u.iec61883_6.dbs) << 2; + break; + + case IEC_61883_CIP_FMT_8: + default: + break; + } + + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + if (format->u.s.subtype_u.cvf.format == CVF_FORMAT_RFC) + sample_stride = 1; + + break; + + case AVTP_SUBTYPE_AAF: + sample_stride = avdecc_fmt_sample_size(format); + break; + + case AVTP_SUBTYPE_CRF: + sample_stride = avdecc_fmt_sample_size(format); + break; + + case AVTP_SUBTYPE_TSCF: + sample_stride = avdecc_fmt_sample_size(format); + break; +#endif + default: + break; + } + + if (!sample_stride) + sample_stride = AVDECC_FMT_ERROR; + + return sample_stride; +} + + +unsigned int samples_per_interval(unsigned int rate, sr_class_t sr_class) +{ + /* samples_per_intvl = rate * sr_class_interval = rate * sr_class_interval_p / (sr_class_interval_q * 10^9) */ + return ((rate / 100) * (sr_class_interval_p(sr_class) / 1000) + 10000 * sr_class_interval_q(sr_class) - 1) / (10000 * sr_class_interval_q(sr_class)); +} + +unsigned int __avdecc_fmt_samples_per_packet(const struct avdecc_format *format, sr_class_t sr_class, unsigned int *max_interval_frames) +{ + unsigned int samples_per_intvl, samples_per_packet; + unsigned int rate, stride, max_samples_per_packet; + unsigned int interval_packets = 1; + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_AAF: + if (avdecc_format_is_aaf_pcm(format)) + samples_per_packet = AVDECC_FMT_AAF_PCM_SAMPLES_PER_FRAME(format); + else if (avdecc_format_is_aaf_aes3(format)) + samples_per_packet = format->u.s.subtype_u.aaf.format_u.aes3.frames_per_frame; + else + goto err_fmt; + + if (!samples_per_packet) + goto err_fmt; + + rate = avdecc_fmt_sample_rate(format); + if (rate == AVDECC_FMT_ERROR) + goto err_fmt; + + samples_per_intvl = samples_per_interval(rate, sr_class); + + interval_packets = (samples_per_intvl + samples_per_packet - 1) / samples_per_packet; + + break; + + case AVTP_SUBTYPE_CRF: + samples_per_packet = format->u.s.subtype_u.crf.timestamps_per_pdu; + if (!samples_per_packet) + goto err_fmt; + + break; + + default: + rate = avdecc_fmt_sample_rate(format); + stride = avdecc_fmt_sample_stride(format); + + if ((stride == AVDECC_FMT_ERROR) || (rate == AVDECC_FMT_ERROR)) + goto err_fmt; + + max_samples_per_packet = (AVTP_DATA_MTU - avdecc_fmt_hdr_size(format)) / stride; + + if (avdecc_format_is_61883_6(format)) { + switch (rate) { + case 32000: + samples_per_intvl = samples_per_interval(rate, sr_class); + break; + + case 44100: + case 88200: + case 176400: + samples_per_intvl = samples_per_interval(44100, sr_class); + samples_per_intvl *= rate / 44100; + break; + + case 48000: + case 96000: + case 192000: + samples_per_intvl = samples_per_interval(48000, sr_class); + samples_per_intvl *= rate / 48000; + break; + + default: + goto err_fmt; + break; + } + } else { + samples_per_intvl = samples_per_interval(rate, sr_class); + } + + if (samples_per_intvl > max_samples_per_packet) { + /* More than one packet required, try to minimize the number of packets per interval */ + interval_packets = (samples_per_intvl + max_samples_per_packet - 1) / max_samples_per_packet; + + samples_per_packet = ((samples_per_intvl + interval_packets - 1) / interval_packets); + } else { + samples_per_packet = samples_per_intvl; + } + + break; + } + + if (max_interval_frames) + *max_interval_frames = interval_packets; + + return samples_per_packet; + +err_fmt: + if (max_interval_frames) + *max_interval_frames = 0; + + return AVDECC_FMT_ERROR; +} + +unsigned int avdecc_fmt_samples_per_packet(const struct avdecc_format *format, sr_class_t sr_class) +{ + return __avdecc_fmt_samples_per_packet(format, sr_class, NULL); +} + +unsigned int avdecc_fmt_samples_per_timestamp(const struct avdecc_format *format, sr_class_t sr_class) +{ + unsigned int samples_per_timestamp = 0; + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + if (format->u.s.subtype_u.iec61883.sf == IEC_61883_SF_IIDC) + break; + + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_6: + { + /* SYT_INTERVAL */ + unsigned int samples_per_packet = avdecc_fmt_samples_per_packet(format, sr_class); + + samples_per_timestamp = 8; + while (samples_per_timestamp < samples_per_packet) + samples_per_timestamp <<= 1; + + break; + } + + case IEC_61883_CIP_FMT_4: + samples_per_timestamp = 1; + break; + + default: + break; + } + + break; + + case AVTP_SUBTYPE_AAF: + /* For sparse mode this is wrong */ + samples_per_timestamp = avdecc_fmt_samples_per_packet(format, sr_class); + break; + + case AVTP_SUBTYPE_CRF: + samples_per_timestamp = 1; + break; + + case AVTP_SUBTYPE_CVF: /*TODO check this ??? for now make it similar to mpeg2-ts*/ + samples_per_timestamp = avdecc_fmt_samples_per_packet(format, sr_class); + break; + + default: + break; + } + + return samples_per_timestamp; +} + +unsigned int __avdecc_fmt_payload_size(const struct avdecc_format *format, sr_class_t sr_class, unsigned int *max_frame_size, unsigned int *max_interval_frames) +{ + unsigned int payload_size; + + payload_size = __avdecc_fmt_samples_per_packet(format, sr_class, max_interval_frames) * avdecc_fmt_sample_stride(format); + + if (max_frame_size) + *max_frame_size = payload_size; + + return payload_size; +} + + +unsigned int avdecc_fmt_sample_rate(const struct avdecc_format *format) +{ + unsigned int sample_rate = 0; + + switch (format->u.s.subtype) { + case AVTP_SUBTYPE_61883_IIDC: + if (format->u.s.subtype_u.iec61883.sf == IEC_61883_SF_IIDC) + break; + + switch (format->u.s.subtype_u.iec61883.fmt) { + case IEC_61883_CIP_FMT_6: // For AVDECC formats, fdf != IEC_61883_6_FDF_NODATA should be a valid assumption + sample_rate = avtp_61883_6_sampling_freq[format->u.s.subtype_u.iec61883.format_u.iec61883_6.fdf_u.fdf.sfc]; + break; + + case IEC_61883_CIP_FMT_4: + /* FIXME this should be deduced from the contents of the MPEG2TS file + for now supports up to 16000 * 188 * 8 ~ 24Mbit/s MPEG2TS rate */ + sample_rate = 16000; + break; + + case IEC_61883_CIP_FMT_8: + default: + break; + } + + break; + +#ifdef CFG_AVTP_1722A + case AVTP_SUBTYPE_CVF: + if (format->u.s.subtype_u.cvf.format == CVF_FORMAT_RFC) { + switch (format->u.s.subtype_u.cvf.subtype) { + case CVF_FORMAT_SUBTYPE_MJPEG: + sample_rate = 3750000; // FIXME for now, hard-coded to 30 Mbps + break; + + case CVF_FORMAT_SUBTYPE_H264: + sample_rate = 3000000; /* FIXME for now hardcoded for 24 Mbps*/ + break; + case CVF_FORMAT_SUBTYPE_JPEG2000: + /* FIXME */ + default: + break; + } + } + + break; + + case AVTP_SUBTYPE_AAF: + sample_rate = avtp_aaf_sampling_freq[format->u.s.subtype_u.aaf.nsr]; + break; + + case AVTP_SUBTYPE_CRF: { + unsigned int p, q; + + switch (format->u.s.subtype_u.crf.pull) { + case CRF_PULL_1_1: + default: + p = 1, q = 1; + break; + + case CRF_PULL_1000_1001: + p = 1000, q = 1001; + break; + + case CRF_PULL_1001_1000: + p = 1001, q = 1000; + break; + + case CRF_PULL_24_25: + p = 24, q = 25; + break; + + case CRF_PULL_25_24: + p = 25, q = 24; + break; + + case CRF_PULL_1_8: + p = 1, q = 8; + break; + } + + /* 1722rev1-2016, Table 28 recommends 300Hz */ + sample_rate = ((u64)AVDECC_FMT_CRF_BASE_FREQUENCY(format) * p) / (AVDECC_FMT_CRF_TIMESTAMP_INTERVAL(format) * q); + + break; + } +#endif + default: + break; + } + + if (!sample_rate) + sample_rate = AVDECC_FMT_ERROR; + + return sample_rate; +} diff --git a/common/avdecc.h b/common/avdecc.h new file mode 100644 index 0000000..d5c5a7f --- /dev/null +++ b/common/avdecc.h @@ -0,0 +1,28 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file avdecc.h + @brief AVDECC common definitions and functions +*/ + +#ifndef _PROTO_AVDECC_H_ +#define _PROTO_AVDECC_H_ + +#include "genavb/avdecc.h" +#include "genavb/sr_class.h" + +unsigned int avdecc_fmt_sample_stride(const struct avdecc_format *format); + +static inline unsigned int avdecc_fmt_payload_size(const struct avdecc_format *format, sr_class_t sr_class) +{ + unsigned int max_frame_size, max_interval_frames; + + return __avdecc_fmt_payload_size(format, sr_class, &max_frame_size, &max_interval_frames); +} + +#endif /* _PROTO_AVDECC_H_ */ diff --git a/common/avtp.h b/common/avtp.h new file mode 100644 index 0000000..9228241 --- /dev/null +++ b/common/avtp.h @@ -0,0 +1,119 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file avtp.h + @brief AVTP protocol common definitions + @details PDU and protocol definitions for all AVTP applications +*/ + +#ifndef _PROTO_AVTP_H_ +#define _PROTO_AVTP_H_ + +#include "common/types.h" +#include "common/avdecc.h" +#include "config.h" +#include "genavb/ether.h" +#include "genavb/avtp.h" +#include "genavb/sr_class.h" +#include "genavb/streaming.h" + +struct avtp_ctrl_hdr { +#ifdef CFG_AVTP_1722A_BROKEN /* FIXME support of AVTP control according to 1722a needs more work */ +#ifdef __BIG_ENDIAN__ + u32 subtype:8; + + u32 sv:1; + u32 version:3; + + u32 format_specific_data:9; + u32 control_data_length:11; + +#else + u32 subtype:8; + + u32 format_specific_data_1:4; /* FIXME this field is broken, since it crosses a byte boundary */ + u32 version:3; + u32 sv:1; + + u32 control_data_length_1:3; /* FIXME this field is broken, since it crosses a byte boundary */ + u32 format_specific_data_2:5; /* FIXME this field is broken, since it crosses a byte boundary */ + + u32 control_data_length_2:8; /* FIXME this field is broken, since it crosses a byte boundary */ +#endif + +#else + u8 subtype; + +#ifdef __BIG_ENDIAN__ + u8 sv:1; + u8 version:3; + u8 control_data:4; + + u16 control_data_len_status; +#else + u8 control_data:4; + u8 version:3; + u8 sv:1; + + u16 control_data_len_status; +#endif + +#endif /* !CFG_AVTP_1722A_BROKEN */ + // FIXME no stream_id? +}; + +#define AVTP_GET_STATUS(hdr) \ + ((ntohs(hdr->control_data_len_status) >> 11) & 0x1F) + +#define AVTP_GET_CTRL_DATA_LEN(hdr) \ + (ntohs(hdr->control_data_len_status) & 0x7FF) + +#define AVTP_SET_CTRL_DATA_STATUS(hdr, status, cdl) \ + (hdr->control_data_len_status = htons(((status & 0x1F) << 11) | (cdl & 0x7FF))) + +/* + Minimal avtp presentation offset, in avtp stack, to avoid network late transmit/underflow (under + worst case cpu load). + If avtp stack does periodic processing, with a "latency" period, when cpu load approaches 100%, it will + take up to "latency" time to process avtp data. + Data that starts being processed at t0, may only finish processing at t0 + latency. + This means avtp packets should have a transmit timestamp that is at least "latency" in the future, + otherwise their transmit time may already be in the past when they reach the network transmit buffer +*/ +static inline unsigned int _avtp_stream_presentation_offset(unsigned int max_transit_time, unsigned int latency) +{ + return max_transit_time + latency; +} + +/* + Minimal avtp presentation offset, in media stack, to avoid network late transmit/underflow + If avtp stack does periodic processing, with a "latency" period, it may take up to "latency" + time for it to process new data. +*/ +static inline unsigned int stream_presentation_offset(unsigned int max_transit_time, unsigned int latency) +{ + return _avtp_stream_presentation_offset(max_transit_time, latency) + latency; +} + +static inline unsigned int avtp_fmt_sample_size(unsigned int subtype, const struct avdecc_format *format) +{ + unsigned int sample_size; + + switch (subtype) { + case AVTP_SUBTYPE_NTSCF: /* not avdecc defined format */ + sample_size = 1; + break; + default: + sample_size = avdecc_fmt_sample_size(format); + break; + } + + return sample_size; +} + +#endif /* _PROTO_AVTP_H_ */ diff --git a/common/clock.c b/common/clock.c new file mode 100644 index 0000000..d5f09d2 --- /dev/null +++ b/common/clock.c @@ -0,0 +1,30 @@ +/* + * Copyright 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Generic Clock functions implementation + @details +*/ + +#include "common/clock.h" + +int clock_set_time64(os_clock_id_t clk_id, u64 ns) +{ + u64 curr; + s64 off; + int rc; + + rc = os_clock_gettime64(clk_id, &curr); + if (rc) + goto exit; + + off = ns - curr; + + rc = os_clock_setoffset(clk_id, off); +exit: + return rc; +} diff --git a/common/clock.h b/common/clock.h new file mode 100644 index 0000000..4ef02a7 --- /dev/null +++ b/common/clock.h @@ -0,0 +1,21 @@ +/* + * Copyright 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file clock.h + @brief Generic clock functions + @details +*/ + +#ifndef _COMMON_CLOCK_H_ +#define _COMMON_CLOCK_H_ + +#include "os/clock.h" + + +int clock_set_time64(os_clock_id_t clk_id, u64 ns); + +#endif /* _COMMON_CLOCK_H_ */ diff --git a/common/common.cmake b/common/common.cmake new file mode 100644 index 0000000..f3533ad --- /dev/null +++ b/common/common.cmake @@ -0,0 +1,45 @@ + +genavb_add_library(NAME common + SRCS + filter.c + log.c + managed_objects.c + stats.c + timer.c + random.c + ) + +genavb_target_add_srcs(TARGET gptp + SRCS + clock.c + ) + +genavb_target_add_srcs(TARGET avtp + SRCS + 61883_iidc.c + aaf.c + ) + +genavb_target_add_srcs(TARGET avdecc + SRCS + avdecc.c + aaf.c + hash.c +) + +genavb_target_add_srcs(TARGET srp + SRCS + srp.c + ) + +genavb_target_add_srcs(TARGET genavb + SRCS + 61883_iidc.c + aaf.c + avdecc.c + log.c + srp.c + ) + +genavb_link_libraries(TARGET ${avb} LIB common) +genavb_link_libraries(TARGET ${tsn} LIB common) diff --git a/common/config.h b/common/config.h new file mode 100644 index 0000000..8c08442 --- /dev/null +++ b/common/config.h @@ -0,0 +1,31 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief AVB global static configuration + @details Contains all compile time configuration options for the entire AVB stack +*/ + +#ifndef _CFG_H_ +#define _CFG_H_ + +#include "genavb/config.h" +#include "os/config.h" + +#define CFG_LOG LOG_INFO + +#define common_CFG_LOG CFG_LOG + +#define AVTP_CFG_NUM_DOMAINS 4 + + +#define CFG_AVTP_MIN_LATENCY 500000 /* minimum wakeup latency */ +#define CFG_AVTP_DEFAULT_LATENCY 1000000 /* default wakeup latency */ +#define CFG_AVTP_MAX_LATENCY 20000000 /* maximum wakeup latency */ + +#endif /* _CFG_H_ */ diff --git a/common/crf.h b/common/crf.h new file mode 100644 index 0000000..97b2b1b --- /dev/null +++ b/common/crf.h @@ -0,0 +1,18 @@ +/* + * Copyright 2016-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file crf.h + @brief CRF protocol common definitions +*/ + +#ifndef _PROTO_CRF_H_ +#define _PROTO_CRF_H_ + +#include "genavb/crf.h" + + +#endif diff --git a/common/cvf.h b/common/cvf.h new file mode 100644 index 0000000..d123f71 --- /dev/null +++ b/common/cvf.h @@ -0,0 +1,103 @@ +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * Copyright 2017-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file cvf.h + @brief CVF protocol common definitions + @details Protocol and Header definitions for all CVF applications +*/ + +#ifndef _PROTO_CVF_H_ +#define _PROTO_CVF_H_ + +#include "common/types.h" +#include "genavb/cvf.h" + +struct __attribute__ ((packed)) cvf_mjpeg_hdr { + u8 type_specific; + u8 fragment_offset_msb; + u16 fragment_offset_lsb; + u8 type; + u8 Q; + u8 width; + u8 height; +}; + +struct __attribute__ ((packed)) cvf_h264_hdr { + u32 h264_timestamp; +}; + +struct __attribute__ ((packed)) cvf_h264_nalu_header { +#ifdef __BIG_ENDIAN__ + u8 f:1; + u8 nri:2; + u8 type:5; +#else + u8 type:5; + u8 nri:2; + u8 f:1; +#endif +}; + +#define cvf_h264_nalu_fu_indicator cvf_h264_nalu_header + +struct __attribute__ ((packed)) cvf_h264_nalu_fu_header { +#ifdef __BIG_ENDIAN__ + u8 s:1; + u8 e:1; + u8 r:1; + u8 type:5; +#else + u8 type:5; + u8 r:1; + u8 e:1; + u8 s:1; +#endif +}; + +#define FU_HEADER_SIZE (sizeof(struct cvf_h264_nalu_fu_header) + sizeof(struct cvf_h264_nalu_fu_indicator)) + +/* NALU types as defined in the RFC6184 section 5.4, table 3 + * NAL Unit types is also specified in T-REC-H.264 Section 7.4.1 Table 7.1 */ +enum cvf_h264_nalu_type { + CVF_H264_NALU_TYPE_RESERVED0 = 0, + CVF_H264_NALU_TYPE_NAL_UNIT1, + CVF_H264_NALU_TYPE_NAL_UNIT2, + CVF_H264_NALU_TYPE_NAL_UNIT3, + CVF_H264_NALU_TYPE_NAL_UNIT4, + CVF_H264_NALU_TYPE_NAL_UNIT5, + CVF_H264_NALU_TYPE_NAL_UNIT6, + CVF_H264_NALU_TYPE_NAL_UNIT7, + CVF_H264_NALU_TYPE_NAL_UNIT8, + CVF_H264_NALU_TYPE_NAL_UNIT9, + CVF_H264_NALU_TYPE_NAL_UNIT10, + CVF_H264_NALU_TYPE_NAL_UNIT11, + CVF_H264_NALU_TYPE_NAL_UNIT12, + CVF_H264_NALU_TYPE_NAL_UNIT13, + CVF_H264_NALU_TYPE_NAL_UNIT14, + CVF_H264_NALU_TYPE_NAL_UNIT15, + CVF_H264_NALU_TYPE_NAL_UNIT16, + CVF_H264_NALU_TYPE_RESERVED17, + CVF_H264_NALU_TYPE_RESERVED18, + CVF_H264_NALU_TYPE_NAL_UNIT19, + CVF_H264_NALU_TYPE_NAL_UNIT20, + CVF_H264_NALU_TYPE_NAL_UNIT21, + CVF_H264_NALU_TYPE_RESERVED22, + CVF_H264_NALU_TYPE_RESERVED23, + CVF_H264_NALU_TYPE_STAP_A, + CVF_H264_NALU_TYPE_STAP_B, + CVF_H264_NALU_TYPE_MTAP16, + CVF_H264_NALU_TYPE_MTAP24, + CVF_H264_NALU_TYPE_FU_A, + CVF_H264_NALU_TYPE_FU_B, + CVF_H264_NALU_TYPE_RESERVED30, + CVF_H264_NALU_TYPE_RESERVED31 +}; + +#define CVF_H264_STAP_A_NALU_SIZE_HDR_SIZE 2 + +#endif /* PROTO_CVF_H */ diff --git a/common/ether.h b/common/ether.h new file mode 100644 index 0000000..368a380 --- /dev/null +++ b/common/ether.h @@ -0,0 +1,19 @@ +/* + * Copyright 2014-2015 Freescale Semiconductor, Inc. + * Copyright 2017-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file ether.h + @brief ETHERNET protocol common definitions +*/ + +#ifndef _ETHER_H_ +#define _ETHER_H_ + +#include "genavb/ether.h" + + +#endif /* _ETHER_H_ */ diff --git a/common/filter.c b/common/filter.c new file mode 100644 index 0000000..93dc7ee --- /dev/null +++ b/common/filter.c @@ -0,0 +1,261 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Generic filtering functions + @details +*/ + +#include "filter.h" +#include "common/log.h" +#include "os/stdlib.h" +#include "os/string.h" + +#define FILTER_EXP_DECAY_M_MAX (1 << 6) + +/** Exponential decay coefficient lookup table + * This avoid extreme values (coef=0 <-> M=+INF or coef=1 <-> M=0) + * and spare the exponential computing from M to coef. + * IEEE 802.1AS 11.1.2 "the weight of a past propagation delay + * measurement is 1/e after M measurements" so the coef of the new value is: + * Coef = 1-exp(-1/M) + */ +static const double filter_exp_decay_coef[6] = { + 0.6321205588, //1-exp(-1/1) + 0.3934693403, //1-exp(-1/2) + 0.2211992169, //1-exp(-1/4) + 0.1175030974, //1-exp(-1/8) + 0.0605869372, //1-exp(-1/16) + 0.0307667655, //1-exp(-1/32) +}; + + +/** Identity filter reset function. + * @params: pointer to filter parameters + * @val: sampling value to filter + * + */ +static double filter_identity_filter(union filter_params *params, double val) +{ + return val; +} + + +/** Identity filter initialization function. + * @params: pointer to filter + * + */ +int filter_identity_init(struct filter *f) +{ + f->reset = NULL; + f->close = NULL; + f->filter = filter_identity_filter; + + return 0; +} + + +/** Mean filter reset function. + * @params: pointer to filter parameters + * + */ +static void filter_mean_reset(union filter_params *params) +{ + params->mean.count = 0; + params->mean.mean = 0; + params->mean.position = 0; + os_memset(params->mean.array, 0, params->mean.array_size*sizeof(double)); +} + + +/** Mean filter close function. + * @params: pointer to filter parameters + * + */ +static void filter_mean_close(union filter_params *params) +{ + os_free(params->mean.array); +} + + +/** Mean filter processing function. + * @params: pointer to filter parameters + * @val: sampling value to filter + */ +static double filter_mean_filter(union filter_params *params, double val) +{ + params->mean.mean = params->mean.mean*params->mean.count - params->mean.array[params->mean.position] + val; + if (params->mean.count < params->mean.array_size) + params->mean.count++; + params->mean.mean /= params->mean.count; + + params->mean.array[params->mean.position] = val; + params->mean.position++; + if (params->mean.position == params->mean.array_size) + params->mean.position = 0; + + return params->mean.mean; +} + +/** Mean filter initialization function. + * @params: pointer to filter parameters + * @size: max numbers of samples the mean value is computed over + * + */ +int filter_mean_init(struct filter *f, u32 size) +{ + int rc = 0; + + f->params.mean.array_size = size; + f->params.mean.array = os_malloc(size*sizeof(double)); + if (!f->params.mean.array) { + rc = -1; + goto err_malloc; + } + + f->reset = filter_mean_reset; + f->close = filter_mean_close; + f->filter = filter_mean_filter; + + f->reset(&f->params); + +err_malloc: + return rc; +} + + +/** Exponential decay filter reset function. + * @params: pointer to filter parameters + * + */ +static void filter_exp_decay_reset(union filter_params *params) +{ + params->exp_decay.mean = 0; + params->exp_decay.count = 1; +} + + +/** Exponential decay filter processing function. + * @params: pointer to filter parameters + * @val: sampling value to filter + * + */ +static double filter_exp_decay_filter(union filter_params *params, double val) +{ + if (params->exp_decay.count <= params->exp_decay.m_factor) + params->exp_decay.mean = ((params->exp_decay.count - 1) * params->exp_decay.mean + val) / params->exp_decay.count; + else + params->exp_decay.mean = (params->exp_decay.mean * (1 - params->exp_decay.coef) + params->exp_decay.coef * val); + + params->exp_decay.count++; + + return params->exp_decay.mean; +} + + +/** Exponential decay filter initialization function. + * @params: pointer to filter + * @coef: weigth of the sampling value added to the filter + * + */ +int filter_exp_decay_init(struct filter *f, double coef) +{ + int i; + + if ((coef > 1.0) || (coef <= 0.0)) { + os_log(LOG_ERR, "Invalid coefficient value (%f), defaulting to 1.\n", coef); + f->params.exp_decay.coef = 1.0; + } else { + f->params.exp_decay.coef = coef; + } + + f->params.exp_decay.m_factor = FILTER_EXP_DECAY_M_MAX; + for (i = 0; i < 6; i++) + if (coef > filter_exp_decay_coef[i]) { + f->params.exp_decay.m_factor = 1 << i; + break; + } + + os_log(LOG_INFO, "Filter parameters -> M = %d Coef = %f\n", f->params.exp_decay.m_factor, coef); + + f->reset = filter_exp_decay_reset; + f->close = NULL; + f->filter = filter_exp_decay_filter; + + f->reset(&f->params); + + return 0; +} + + +static void filter_fir_reset(union filter_params *params) +{ + params->fir.count = 0; + params->fir.position = 0; + os_memset(params->fir.array, 0, params->fir.array_size*sizeof(double)); +} + +static void filter_fir_close(union filter_params *params) +{ + os_free(params->fir.array); +} + +static double filter_fir_filter(union filter_params *params, double val) +{ + double result = 0; + u32 i, pos; + + /* Update the array of samples with the new value */ + params->fir.array[params->fir.position] = val; + + /* Not enough samples yet, return unfiltered value */ + if (params->fir.count < params->fir.array_size) { + params->fir.count++; + result = val; + } else { + pos = params->fir.position; + for (i = 0; i < params->fir.array_size; i ++) { + result += params->fir.taps_array[i] * params->fir.array[pos]; + pos = pos == 0? params->fir.array_size - 1:pos - 1; + } + + result /= params->fir.taps_sum; + } + + + params->fir.position++; + if (params->fir.position == params->fir.array_size) + params->fir.position = 0; + + return result; +} + +int filter_fir_init(struct filter *f, u32 size, double *filter_taps) +{ + u32 i; + + f->params.fir.array_size = size; + f->params.fir.taps_array = filter_taps; + f->params.fir.taps_sum = 0; + for (i = 0; i < size; i++) { + f->params.fir.taps_sum += filter_taps[i]; + } + + f->params.fir.array = os_malloc(size*sizeof(double)); + + if (!f->params.fir.array) + return -1; + + f->reset = filter_fir_reset; + f->close = filter_fir_close; + f->filter = filter_fir_filter; + + f->reset(&f->params); + + return 0; +} diff --git a/common/filter.h b/common/filter.h new file mode 100644 index 0000000..ae63ef3 --- /dev/null +++ b/common/filter.h @@ -0,0 +1,90 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Generic filtering functions + @details +*/ + +#ifndef _COMMON_FILTER_H_ +#define _COMMON_FILTER_H_ + +#include "common/types.h" + +union filter_params { + struct { + double *array; + double mean; + u32 array_size; + u32 position; + u32 count; + } mean; + + struct { + double mean; + double coef; + int m_factor; + int count; + } exp_decay; + + struct { + double *array; + u32 array_size; + u32 position; + u32 count; + double *taps_array; + double taps_sum; + } fir; +}; + + +struct filter { + void (*reset)(union filter_params *params); + void (*close)(union filter_params *params); + double (*filter)(union filter_params *params, double val); + + union filter_params params; +}; + + +/** Call filter reset function. + * @f: pointer to the filter to reset + * + */ +static inline void filter_reset(struct filter *f) +{ + if (f->reset) + f->reset(&f->params); +} + +/** Call filter close function. + * @f: pointer to the filter to close + * + */ +static inline void filter_close(struct filter *f) +{ + if (f->close) + f->close(&f->params); +} + +/** Call filter processing function. + * @f: pointer to the filter to process + * @val: sampling value to filter + * + */ +static inline double filter(struct filter *f, double val) +{ + return f->filter(&f->params, val); +} + +int filter_identity_init(struct filter *f); +int filter_mean_init(struct filter *f, u32 size); +int filter_exp_decay_init(struct filter *f, double coef); +int filter_fir_init(struct filter *f, u32 size, double *filter_taps); + +#endif /* _COMMON_FILTER_H_ */ diff --git a/common/hash.c b/common/hash.c new file mode 100644 index 0000000..fda77f1 --- /dev/null +++ b/common/hash.c @@ -0,0 +1,32 @@ +/* + * Copyright 2017, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Generic hashing functions + @details +*/ + +#include "hash.h" + +/** Compute a simple 8-bit rotating hash on the given input key. + * \return 8-bit hash of the input key + * \param key pointer to character array to hash + * \param len length of the key array, in bytes + * \param init initial value of the hash (may be used e.g. to compute a single hash value from separate variables) + * + * This function computes an 8-bit hash of the input key, using a simple rotating hash. + */ +u8 rotating_hash_u8(u8 *key, unsigned int len, u8 init) +{ + u8 hash = init; + unsigned int i; + + for (i = 0; i < len; i++) + hash = (hash << 3) ^ (hash >> 5) ^ key[i]; + + return hash; +} diff --git a/common/hash.h b/common/hash.h new file mode 100644 index 0000000..6ab4884 --- /dev/null +++ b/common/hash.h @@ -0,0 +1,19 @@ +/* + * Copyright 2017, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Generic hashing functions + @details +*/ +#ifndef _HASH_H_ +#define _HASH_H_ + +#include "common/types.h" + +u8 rotating_hash_u8(u8 *key, unsigned int len, u8 init); + +#endif diff --git a/common/ipc.h b/common/ipc.h new file mode 100644 index 0000000..a91fe01 --- /dev/null +++ b/common/ipc.h @@ -0,0 +1,438 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief IPC Service implementation + @details +*/ + +#ifndef _COMMON_IPC_H_ +#define _COMMON_IPC_H_ + +#include "common/types.h" +#include "genavb/genavb.h" + +/* IPC return codes */ +#define IPC_TX_ERR_NO_READER 1 /**< An IPC message was sent but no reader was listening */ +#define IPC_TX_ERR_QUEUE_FULL 2 /**< An IPC message was sent but the IPC queue was full */ +#define IPC_TX_ERR_UNKNOWN 3 /**< An unknown error happened while trying to send an IPC */ + +/* IPC identifiers + * Identifiers are always in groups of three. + * The first member of the group is intendend for commands (from the external application to a stack component), + * the second for indications/async responses (from a stack component to an external application) and + * the third one for sync responses (from a stack component to an external application). + * command IPCs can be of the type "many to one" + * response and indication IPCs can be of the type "one to many" + */ +typedef enum { + /* AVDECC */ + IPC_MEDIA_STACK_AVDECC = 0, + IPC_AVDECC_MEDIA_STACK, + IPC_UNUSED_0, + + /* AVDECC controller */ + IPC_CONTROLLER_AVDECC, + IPC_AVDECC_CONTROLLER, + IPC_AVDECC_CONTROLLER_SYNC, + + /* AVDECC controlled */ + IPC_CONTROLLED_AVDECC, + IPC_AVDECC_CONTROLLED, + IPC_UNUSED_1, + + /* MSRP Endpoint 0 */ + IPC_MEDIA_STACK_MSRP, + IPC_MSRP_MEDIA_STACK, + IPC_MSRP_MEDIA_STACK_SYNC, + + /* MSRP Bridge */ + IPC_MEDIA_STACK_MSRP_BRIDGE, + IPC_MSRP_BRIDGE_MEDIA_STACK, + IPC_MSRP_BRIDGE_MEDIA_STACK_SYNC, + + /* MVRP Endpoint 0 */ + IPC_MEDIA_STACK_MVRP, + IPC_MVRP_MEDIA_STACK, + IPC_MVRP_MEDIA_STACK_SYNC, + + /* MVRP Bridge */ + IPC_MEDIA_STACK_MVRP_BRIDGE, + IPC_MVRP_BRIDGE_MEDIA_STACK, + IPC_MVRP_BRIDGE_MEDIA_STACK_SYNC, + + /* Clock domain */ + IPC_MEDIA_STACK_CLOCK_DOMAIN, + IPC_CLOCK_DOMAIN_MEDIA_STACK, + IPC_CLOCK_DOMAIN_MEDIA_STACK_SYNC, + + /* MAAP */ + IPC_MEDIA_STACK_MAAP, + IPC_MAAP_MEDIA_STACK, + IPC_MAAP_MEDIA_STACK_SYNC, + + /* gPTP Endpoint 0 */ + IPC_MEDIA_STACK_GPTP, + IPC_GPTP_MEDIA_STACK, + IPC_GPTP_MEDIA_STACK_SYNC, + + /* gPTP Bridge */ + IPC_MEDIA_STACK_GPTP_BRIDGE, + IPC_GPTP_BRIDGE_MEDIA_STACK, + IPC_GPTP_BRIDGE_MEDIA_STACK_SYNC, + + /* AVTP */ + IPC_MEDIA_STACK_AVTP, + IPC_AVTP_MEDIA_STACK, + IPC_AVTP_MEDIA_STACK_SYNC, + + IPC_AVTP_STATS, + IPC_UNUSED_2, + IPC_UNUSED_3, + + /* MAC Service Endpoint 0 */ + IPC_MEDIA_STACK_MAC_SERVICE, + IPC_MAC_SERVICE_MEDIA_STACK, + IPC_MAC_SERVICE_MEDIA_STACK_SYNC, + + /* MAC Service Bridge */ + IPC_MEDIA_STACK_MAC_SERVICE_BRIDGE, + IPC_MAC_SERVICE_BRIDGE_MEDIA_STACK, + IPC_MAC_SERVICE_BRIDGE_MEDIA_STACK_SYNC, + + /* MSRP Endpoint 1 */ + IPC_MEDIA_STACK_MSRP_1, + IPC_MSRP_1_MEDIA_STACK, + IPC_MSRP_1_MEDIA_STACK_SYNC, + + /* MVRP Endpoint 1 */ + IPC_MEDIA_STACK_MVRP_1, + IPC_MVRP_1_MEDIA_STACK, + IPC_MVRP_1_MEDIA_STACK_SYNC, + + /* MAC Service Endpoint 1 */ + IPC_MEDIA_STACK_MAC_SERVICE_1, + IPC_MAC_SERVICE_1_MEDIA_STACK, + IPC_MAC_SERVICE_1_MEDIA_STACK_SYNC, + + /* gPTP Endpoint 1 */ + IPC_MEDIA_STACK_GPTP_1, + IPC_GPTP_1_MEDIA_STACK, + IPC_GPTP_1_MEDIA_STACK_SYNC, + + /* HSR */ + IPC_HSR_STACK, + IPC_UNUSED_4, + IPC_UNUSED_5, + + /* SRP Endpoint - bridge communication */ + IPC_SRP_ENDPOINT_BRIDGE, + IPC_SRP_BRIDGE_ENDPOINT, + IPC_UNUSED_6, + + IPC_ID_MAX, + + IPC_ID_NONE +} ipc_id_t; + +#define IPC_MEDIA_STACK_MSRP_0 IPC_MEDIA_STACK_MSRP +#define IPC_MSRP_0_MEDIA_STACK IPC_MSRP_MEDIA_STACK +#define IPC_MSRP_0_MEDIA_STACK_SYNC IPC_MSRP_MEDIA_STACK_SYNC + +#define IPC_MEDIA_STACK_MVRP_0 IPC_MEDIA_STACK_MVRP +#define IPC_MVRP_0_MEDIA_STACK IPC_MVRP_MEDIA_STACK +#define IPC_MVRP_0_MEDIA_STACK_SYNC IPC_MVRP_MEDIA_STACK_SYNC + +#define IPC_MEDIA_STACK_MAC_SERVICE_0 IPC_MEDIA_STACK_MAC_SERVICE +#define IPC_MAC_SERVICE_0_MEDIA_STACK IPC_MAC_SERVICE_MEDIA_STACK +#define IPC_MAC_SERVICE_0_MEDIA_STACK_SYNC IPC_MAC_SERVICE_MEDIA_STACK_SYNC + +#define IPC_MEDIA_STACK_GPTP_0 IPC_MEDIA_STACK_GPTP +#define IPC_GPTP_0_MEDIA_STACK IPC_GPTP_MEDIA_STACK +#define IPC_GPTP_0_MEDIA_STACK_SYNC IPC_GPTP_MEDIA_STACK_SYNC + +/* IPC types */ +enum { + IPC_TYPE_SINGLE_READER_WRITER = 0, /* Single writer, reader */ + IPC_TYPE_MANY_READERS, /* Many readers, single writer. Meant for indications and responses. */ + IPC_TYPE_MANY_WRITERS, /* Many writers, single reader. Meant for commands. */ +}; + +#define IPC_MEDIA_STACK_AVDECC_TYPE IPC_TYPE_SINGLE_READER_WRITER +#define IPC_AVDECC_MEDIA_STACK_TYPE IPC_TYPE_SINGLE_READER_WRITER + +#define IPC_CONTROLLER_AVDECC_TYPE IPC_TYPE_SINGLE_READER_WRITER +#define IPC_AVDECC_CONTROLLER_TYPE IPC_TYPE_SINGLE_READER_WRITER +#define IPC_AVDECC_CONTROLLER_SYNC_TYPE IPC_TYPE_SINGLE_READER_WRITER + +#define IPC_CONTROLLED_AVDECC_TYPE IPC_TYPE_SINGLE_READER_WRITER +#define IPC_AVDECC_CONTROLLED_TYPE IPC_TYPE_SINGLE_READER_WRITER + +#define IPC_MEDIA_STACK_MSRP_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MSRP_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MSRP_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_MSRP_1_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MSRP_1_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MSRP_1_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_MSRP_BRIDGE_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MSRP_BRIDGE_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MSRP_BRIDGE_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_MVRP_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MVRP_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MVRP_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_MVRP_1_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MVRP_1_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MVRP_1_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_MVRP_BRIDGE_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MVRP_BRIDGE_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MVRP_BRIDGE_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_CLOCK_DOMAIN_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_CLOCK_DOMAIN_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_CLOCK_DOMAIN_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_MAAP_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MAAP_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MAAP_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_GPTP_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_GPTP_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_GPTP_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_GPTP_1_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_GPTP_1_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_GPTP_1_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_GPTP_BRIDGE_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_GPTP_BRIDGE_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_GPTP_BRIDGE_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_AVTP_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_AVTP_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_AVTP_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_AVTP_STATS_TYPE IPC_TYPE_SINGLE_READER_WRITER + +#define IPC_MEDIA_STACK_MAC_SERVICE_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MAC_SERVICE_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MAC_SERVICE_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_MAC_SERVICE_1_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MAC_SERVICE_1_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MAC_SERVICE_1_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_MEDIA_STACK_MAC_SERVICE_BRIDGE_TYPE IPC_TYPE_MANY_WRITERS +#define IPC_MAC_SERVICE_BRIDGE_MEDIA_STACK_TYPE IPC_TYPE_MANY_READERS +#define IPC_MAC_SERVICE_BRIDGE_MEDIA_STACK_SYNC_TYPE IPC_TYPE_MANY_READERS + +#define IPC_SRP_BRIDGE_ENDPOINT_TYPE IPC_TYPE_SINGLE_READER_WRITER +#define IPC_SRP_ENDPOINT_BRIDGE_TYPE IPC_TYPE_SINGLE_READER_WRITER + +enum { + /* Hearbeat message from app to stack */ + IPC_HEARTBEAT = GENAVB_MSG_TYPE_MAX, + + /* AVTP IPC message types*/ + IPC_AVTP_CONNECT, + IPC_AVTP_LISTENER_CONNECT_RESPONSE, + IPC_AVTP_TALKER_CONNECT_RESPONSE, + IPC_AVTP_DISCONNECT, + IPC_AVTP_DISCONNECT_RESPONSE, + + IPC_AVTP_PROCESS_STATS, + IPC_AVTP_STREAM_TALKER_STATS, + IPC_AVTP_STREAM_LISTENER_STATS, + IPC_AVTP_CLOCK_DOMAIN_STATS, + IPC_AVTP_CLOCK_GRID_STATS, + IPC_AVTP_CLOCK_GRID_CONSUMER_STATS, + + IPC_MAC_SERVICE_GET_STATUS, + IPC_MAC_SERVICE_STATUS, + + /* IPC to transfer network buffers */ + IPC_NETWORK_BUFFER, +}; + +enum { + IPC_HSR_OPERATION_MODE_SET, +}; + +/* MSRP messages */ +#define ipc_msrp_listener_register genavb_msg_listener_register +#define ipc_msrp_listener_deregister genavb_msg_listener_deregister +#define ipc_msrp_listener_response genavb_msg_listener_response +#define ipc_msrp_listener_status genavb_msg_listener_status +#define ipc_msrp_listener_declaration_status genavb_msg_listener_declaration_status + +#define ipc_msrp_talker_register genavb_msg_talker_register +#define ipc_msrp_talker_deregister genavb_msg_talker_deregister +#define ipc_msrp_talker_response genavb_msg_talker_response +#define ipc_msrp_talker_status genavb_msg_talker_status +#define ipc_msrp_talker_declaration_status genavb_msg_talker_declaration_status + +/* MVRP messages */ +#define ipc_mvrp_vlan_register genavb_msg_vlan_register +#define ipc_mvrp_vlan_deregister genavb_msg_vlan_deregister +#define ipc_mvrp_vlan_response genavb_msg_vlan_response + +/* Clock Domain messages */ +#define ipc_clock_domain_set_source genavb_msg_clock_domain_set_source +#define ipc_clock_domain_response genavb_msg_clock_domain_response +#define ipc_clock_domain_get_status genavb_msg_clock_domain_get_status +#define ipc_clock_domain_status genavb_msg_clock_domain_status + +/* AVTP messages */ +#define ipc_avtp_connect genavb_stream_params + +/* MAAP messages */ +#define ipc_maap_create genavb_msg_maap_create +#define ipc_maap_create_response genavb_msg_maap_create_response +#define ipc_maap_delete genavb_msg_maap_delete +#define ipc_maap_delete_response genavb_msg_maap_delete_response +#define ipc_maap_status genavb_maap_status + +/* MEDIA STACK messages */ +#define ipc_media_stack_bind genavb_msg_media_stack_bind + +struct ipc_avtp_disconnect { + avb_u8 stream_id[8]; /**< Stream ID (in network order) */ + avb_u16 port; /**< Network port */ + sr_class_t stream_class; /**< Stream class */ + avtp_direction_t direction; /**< Stream direction */ +}; + +struct ipc_avtp_listener_connect_response { + u64 stream_id; /* In Big Endian */ + u16 status; /**< 0 for success, non-zero value for failure. */ +}; + +struct ipc_avtp_talker_connect_response { + u64 stream_id; /* In Big Endian */ + u16 status; /**< 0 for success, non-zero value for failure. */ + u32 latency; /* Transmit batch in nanoseconds */ + u32 batch; /* Transmit batch in packets units */ + u32 max_payload_size; /* Transmit maximum packet payload */ +}; + +struct ipc_avtp_disconnect_response { + u64 stream_id; /* In Big Endian */ + u16 status; /**< 0 for success, non-zero value for failure. */ +}; + +#define IPC_AVTP_FLAGS_MCR GENAVB_STREAM_FLAGS_MCR +#define IPC_AVTP_FLAGS_MAX_TRANSIT_TIME_VALID GENAVB_STREAM_FLAGS_MAX_TRANSIT_TIME_VALID + +struct ipc_mac_service_get_status { + u16 port_id; +}; + +struct ipc_mac_service_status { + u16 port_id; + u8 operational; + u8 point_to_point; + u32 rate; +}; + +/* Generic messages */ +#define ipc_error_response genavb_msg_error_response + +struct ipc_heartbeat { + u32 status; +}; + +/* IPC flags */ +#define IPC_FLAGS_AVB_MSG_SYNC (1 << 0) /* Used in the public API to specify the response (if any) should be sent in the corresponding "sync" channel */ + +/* IPC generic descriptor */ +#define ipc_acmp_command genavb_acmp_command +#define ipc_acmp_response genavb_acmp_response +#define ipc_aecp_msg genavb_aecp_msg +#define ipc_adp_msg genavb_adp_msg + +#define IPC_DST_ALL 0xffff + +struct ipc_desc { + u32 type; /* ipc message type */ + u32 len; /* length of the following IPC data */ + u32 flags; /* Flags altering the IPC behavior */ + u16 src; /* source of the ipc message, within an ipc channel */ + u16 dst; /* destination of the ipc message, within an ipc channel */ + union { + struct ipc_msrp_listener_register msrp_listener_register; + struct ipc_msrp_listener_deregister msrp_listener_deregister; + struct ipc_msrp_listener_response msrp_listener_response; + struct ipc_msrp_listener_status msrp_listener_status; + struct ipc_msrp_listener_declaration_status msrp_listener_declaration_status; + + struct ipc_msrp_talker_register msrp_talker_register; + struct ipc_msrp_talker_deregister msrp_talker_deregister; + struct ipc_msrp_talker_response msrp_talker_response; + struct ipc_msrp_talker_status msrp_talker_status; + struct ipc_msrp_talker_declaration_status msrp_talker_declaration_status; + + struct ipc_mvrp_vlan_register mvrp_vlan_register; + struct ipc_mvrp_vlan_deregister mvrp_vlan_deregister; + struct ipc_mvrp_vlan_response mvrp_vlan_response; + + struct ipc_clock_domain_set_source clock_domain_set_source; + struct ipc_clock_domain_response clock_domain_response; + struct ipc_clock_domain_get_status clock_domain_get_status; + struct ipc_clock_domain_status clock_domain_status; + + struct genavb_msg_gm_get_status gm_get_status; + struct genavb_msg_gm_status gm_status; + + struct genavb_msg_gptp_port_params gptp_port_params; + + struct genavb_msg_managed_get managed_get; + struct genavb_msg_managed_get_response managed_get_response; + struct genavb_msg_managed_set managed_set; + struct genavb_msg_managed_set_response managed_set_response; + + struct genavb_msg_media_stack_connect media_stack_connect; + struct genavb_msg_media_stack_disconnect media_stack_disconnect; + struct genavb_msg_media_stack_bind media_stack_bind; + struct genavb_msg_media_stack_unbind media_stack_unbind; + + struct ipc_avtp_connect avtp_connect; + struct ipc_avtp_listener_connect_response avtp_listener_connect_response; + struct ipc_avtp_talker_connect_response avtp_talker_connect_response; + + struct ipc_avtp_disconnect avtp_disconnect; + struct ipc_avtp_disconnect_response avtp_disconnect_response; + + struct ipc_acmp_command acmp_command; + struct ipc_acmp_response acmp_response; + struct ipc_aecp_msg aecp_msg; + struct ipc_adp_msg adp_msg; + + struct ipc_mac_service_get_status mac_service_get_status; + struct ipc_mac_service_status mac_service_status; + + struct ipc_maap_create maap_create; + struct ipc_maap_create_response maap_create_response; + struct ipc_maap_delete maap_delete; + struct ipc_maap_delete_response maap_delete_response; + struct ipc_maap_status maap_status; + + struct ipc_error_response error; + + struct ipc_heartbeat hearbeat; + + u8 data[0]; + } u; +}; + +#include "os/ipc.h" + +#endif /* _COMMON_IPC_H_ */ diff --git a/common/list.h b/common/list.h new file mode 100644 index 0000000..6f045b0 --- /dev/null +++ b/common/list.h @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Double linked list implementation + @details Double linked list data types and helper functions. +*/ + +#ifndef _LIST_H_ +#define _LIST_H_ + +#include "common/types.h" + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +static inline void list_head_init(struct list_head *head) +{ + head->prev = head; + head->next = head; +} + +static inline void list_add(struct list_head *head, struct list_head *entry) +{ + entry->next = head->next; + entry->next->prev = entry; + + head->next = entry; + entry->prev = head; +} + +static inline void list_add_tail(struct list_head *head, struct list_head *entry) +{ + entry->prev = head->prev; + entry->prev->next = entry; + + head->prev = entry; + entry->next = head; +} + +static inline void list_del(struct list_head *entry) +{ + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + entry->prev = NULL; + entry->next = NULL; +} + +#define list_empty(head) ((head)->next == (head)) + +#define list_first(head) ((head)->next) +#define list_last(head) ((head)->prev) +#define list_next(entry) ((entry)->next) + +#endif /* _LIST_H_ */ diff --git a/common/log.c b/common/log.c new file mode 100644 index 0000000..c30de60 --- /dev/null +++ b/common/log.c @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief logging services + @details print logging messages to the standard output +*/ + +#include "common/log.h" + +#include "os/config.h" +#include "avtp/config.h" +#include "avdecc/config.h" +#include "srp/config.h" +#include "maap/config.h" +#include "gptp/config.h" +#include "api/config.h" +#include "management/config.h" + +#include "os/clock.h" + +const char *log_lvl_string[] = { + [LOG_CRIT] = "CRIT", + [LOG_ERR] = "ERR", + [LOG_INIT] = "INIT", + [LOG_INFO] = "INFO", + [LOG_DEBUG] = "DBG" +}; + + +/* default log level per component */ +log_level_t log_component_lvl[max_COMPONENT_ID] = { +#if defined(avtp_CFG_LOG) + [avtp_COMPONENT_ID] = avtp_CFG_LOG, +#endif +#if defined(avdecc_CFG_LOG) + [avdecc_COMPONENT_ID] = avdecc_CFG_LOG, +#endif +#if defined(srp_CFG_LOG) + [srp_COMPONENT_ID] = srp_CFG_LOG, +#endif +#if defined(maap_CFG_LOG) + [maap_COMPONENT_ID] = maap_CFG_LOG, +#endif +#if defined(gptp_CFG_LOG) + [gptp_COMPONENT_ID] = gptp_CFG_LOG, +#endif +#if defined(common_CFG_LOG) + [common_COMPONENT_ID] = common_CFG_LOG, +#endif +#if defined(os_CFG_LOG) + [os_COMPONENT_ID] = os_CFG_LOG, +#endif +#if defined(api_CFG_LOG) + [api_COMPONENT_ID] = api_CFG_LOG, +#endif +#if defined(management_CFG_LOG) + [management_COMPONENT_ID] = management_CFG_LOG, +#endif +}; + +u64 log_time_s; +u64 log_time_ns; + +int log_level_set(unsigned int id, log_level_t level) +{ + if (id >= max_COMPONENT_ID) + return -1; + + log_component_lvl[id] = level; + + return 0; +} + +void log_update_time(os_clock_id_t clk_id) +{ + u64 log_time, log_time_s_local, log_time_ns_local; + + if (os_clock_gettime64(clk_id, &log_time) < 0) + return; + + log_time_s_local = log_time / NSECS_PER_SEC; + log_time_ns_local = log_time - log_time_s_local * NSECS_PER_SEC; + + log_time_s = log_time_s_local; + log_time_ns = log_time_ns_local; +} diff --git a/common/log.h b/common/log.h new file mode 100644 index 0000000..39d9380 --- /dev/null +++ b/common/log.h @@ -0,0 +1,99 @@ +/* + * Copyright 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief logging services + @details print logging messages to the standard output +*/ + +#ifndef _COMMON_LOG_H_ +#define _COMMON_LOG_H_ + +#include "genavb/log.h" + +#include "common/config.h" + +#include "os/clock.h" + +/** Logging options +* +*/ +#define LOG_OPT_RAW 0x100 /* raw messages only (no time reference nor function name) */ + +#define LOG_LEVEL_MASK 0xFF + +/** Logging levels definition + * + */ +typedef enum { + LOG_CRIT = GENAVB_LOG_LEVEL_CRIT, + LOG_CRIT_RAW = GENAVB_LOG_LEVEL_CRIT|LOG_OPT_RAW, + LOG_ERR = GENAVB_LOG_LEVEL_ERR, + LOG_ERR_RAW = GENAVB_LOG_LEVEL_ERR|LOG_OPT_RAW, + LOG_INIT = GENAVB_LOG_LEVEL_INIT, + LOG_INIT_RAW = GENAVB_LOG_LEVEL_INIT|LOG_OPT_RAW, + LOG_INFO = GENAVB_LOG_LEVEL_INFO, + LOG_INFO_RAW = GENAVB_LOG_LEVEL_INFO|LOG_OPT_RAW, + LOG_DEBUG = GENAVB_LOG_LEVEL_DEBUG, + LOG_DEBUG_RAW = GENAVB_LOG_LEVEL_DEBUG|LOG_OPT_RAW +} log_level_t; + + +/** Logging AVB component IDs + * + */ +typedef enum { + avtp_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_AVTP, + avdecc_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_AVDECC, + srp_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_SRP, + maap_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_MAAP, + common_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_COMMON, + os_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_OS, + gptp_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_GPTP, + api_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_API, + management_COMPONENT_ID = GENAVB_LOG_COMPONENT_ID_MGMT, + max_COMPONENT_ID +} log_component_id_t; + +/** Set the logging level for a component + * + * \return negative if error, 0 otherwise + * \param id component id + * \param level logging level to apply (see log_level_t definition) + */ +int log_level_set(unsigned int id, log_level_t level); + +/** Update the time that will be displayed in log messages. + * May be called at any time to provide the desired log timestamping accuracy. + * \return none + */ +void log_update_time(os_clock_id_t clk_id); + +extern log_level_t log_component_lvl[]; +extern const char *log_lvl_string[]; +extern u64 log_time_s; +extern u64 log_time_ns; + +/** Macro to convert enum into string inside a switch statement. + * + */ +#define case2str(x) case x: return #x + +#include "os/log.h" + +#define os_log(level, ...) do { \ + if (((level) & LOG_LEVEL_MASK) <= log_component_lvl[_COMPONENT_ID_]) { \ + if ((level) & LOG_OPT_RAW) { \ + _os_log_raw(__VA_ARGS__); \ + } else { \ + _os_log(log_lvl_string[(level) & LOG_LEVEL_MASK], __func__, _COMPONENT_STR_, __VA_ARGS__); \ + } \ + } \ +} while(0) + +#endif /* _COMMON_LOG_H_ */ diff --git a/common/maap.h b/common/maap.h new file mode 100644 index 0000000..e89963c --- /dev/null +++ b/common/maap.h @@ -0,0 +1,19 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2016, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief MAAP definitions + @details +*/ + +#ifndef _MAAP_H_ +#define _MAAP_H_ + +#define MAAP_BASE_MAC {0x91, 0xE0, 0xF0, 0x00, 0xFE, 0x00} + +#endif /* _MAAP_H_ */ diff --git a/common/managed_objects.c b/common/managed_objects.c new file mode 100644 index 0000000..7fa15be --- /dev/null +++ b/common/managed_objects.c @@ -0,0 +1,593 @@ +/* + * Copyright 2018, 2020-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Managed objects handling functions + @details +*/ + +#include "managed_objects.h" + +#include "os/string.h" + +#include "common/types.h" +#include "common/log.h" +#include "common/ptp.h" + + +static uint8_t *child_iterate(struct node *n, enum node_operation operation, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end, uintptr_t base); + +const unsigned int leaf_object_size[] = { + [LEAF_SCALED_NS] = sizeof(struct ptp_scaled_ns), + [LEAF_USCALED_NS] = sizeof(struct ptp_u_scaled_ns), + [LEAF_PORT_IDENTITY] = sizeof(struct ptp_port_identity), + [LEAF_UINT64] = sizeof(uint64_t), + [LEAF_INT64] = sizeof(int64_t), + [LEAF_CLOCK_IDENTITY] = sizeof(struct ptp_clock_identity), + [LEAF_MAC_ADDRESS] = sizeof(uint8_t)*6, + [LEAF_DOUBLE] = sizeof(double), + [LEAF_UINT32] = sizeof(uint32_t), + [LEAF_INT32]= sizeof(int32_t), + [LEAF_UINT16] = sizeof(uint16_t), + [LEAF_INT16] = sizeof(int16_t), + [LEAF_UINT8] = sizeof(uint8_t), + [LEAF_INT8] = sizeof(int8_t), + [LEAF_BOOL] = sizeof(uint8_t), +}; + +void node_array_link(struct node *parent, struct node *child) +{ + parent->child[parent->max_child] = child; + parent->max_child++; +} + +static void node_init(struct node *node, const char *name, enum node_type type) +{ + node->type = type; + node->max_child = 0; + node->name = name; +} + +void module_init(struct module *module, const char *name) +{ + node_init(&module->n, name, NODE_MODULE); +} + +struct node *container_init(struct container *container, const char *name) +{ + node_init(&container->n, name, NODE_CONTAINER); + + return &container->n; +} + +struct node *list_init(struct list *list, const char *name, uintptr_t (*handler)(void *, uintptr_t), void *handler_data, uint16_t num_keys, uint16_t *keys_id) +{ + int i; + + node_init(&list->n, name, NODE_LIST); + + list->dynamic_handler = handler; + list->dynamic_handler_data = handler_data; + + list->max_key = min(num_keys, LIST_KEY_MAX); + for (i = 0; i < list->max_key; i++) + list->key_id[i] = keys_id[i]; + + return &list->n; +} + +struct node *list_entry_init(struct list_entry *entry, const char *name) +{ + node_init(&entry->n, name, NODE_LIST_ENTRY); + + return &entry->n; +} + +struct node *leaf_init(struct leaf *leaf, const char *name, enum leaf_type type, enum leaf_flags flags, void *storage, + void (*handler)(void *, struct leaf *, enum node_operation, uint8_t *, uintptr_t), void *handler_data) +{ + node_init(&leaf->n, name, NODE_LEAF); + + leaf->type = type; + leaf->flags = flags; + leaf->val = storage; + leaf->handler = handler; + leaf->handler_data = handler_data; + + return &leaf->n; +} + +static struct node_header *get_header(uint8_t *buf, uint8_t *end) +{ + struct node_header *hdr; + + if ((end - buf) < sizeof(struct node_header)) + return NULL; + + hdr = (struct node_header *)buf; + + os_log(LOG_DEBUG, "id: %u, length: %u\n", hdr->id, hdr->length); + + return hdr; +} + +static struct node_header_status *put_header_status_start(uint8_t *buf, uint8_t *end, uint16_t id) +{ + struct node_header_status *hdr; + + if ((end - buf) < sizeof(struct node_header_status)) + return NULL; + + hdr = (struct node_header_status *)buf; + hdr->id = id; + hdr->length = sizeof(uint16_t); + hdr->status = 0; + + return hdr; +} + +static uint8_t *put_header_status_end(struct node_header_status *hdr, uint16_t status, uint16_t length) +{ + hdr->length += length; + hdr->status = status; + + return (uint8_t *)(hdr + 1); +} + +static uint8_t *put_header_status(uint8_t *buf, uint8_t *end, uint16_t id, uint16_t status) +{ + struct node_header_status *hdr; + + hdr = put_header_status_start(buf, end, id); + if (!hdr) + return NULL; + + return put_header_status_end(hdr, status, 0); +} + +static void leaf_bool_get(struct leaf *l, uint8_t *out, uintptr_t base) +{ + out[0] = *((bool *)(base + l->val)); +} + +static void _leaf_get(struct leaf *l, uint8_t *out, unsigned int size, uintptr_t base) +{ + os_memcpy(out, (base + l->val), size); +} + +static uint8_t *leaf_get(struct node *n, unsigned int id, uint8_t *out, uint8_t *out_end, uintptr_t base, uint16_t *status) +{ + struct leaf *l = container_of(n, struct leaf, n); + struct node_header_status *hdr; + unsigned int object_size; + unsigned int leaf_data_length = 0; + + *status = NODE_STATUS_OK; + + if ((!l->val && !base) || !(l->flags & LEAF_R)) { + /* Leaf can not be read */ + *status = NODE_STATUS_ERR_LENGTH; + out = put_header_status(out, out_end, id, *status); + goto out; + } + + if ((l->type < LEAF_TYPE_MIN) || (l->type > LEAF_TYPE_MAX)) { + /* unknown leaf type, stop parsing */ + *status = NODE_STATUS_ERR_TYPE; + out = put_header_status(out, out_end, id, *status); + goto out; + } + + hdr = put_header_status_start(out, out_end, id); + if (!hdr) { + /* no room for node response, stop parsing */ + *status = NODE_STATUS_ERR_LENGTH; + goto out; + } + + object_size = leaf_object_size[l->type]; + + out = (uint8_t *)(hdr + 1); + + /* FIXME should be refactored below with code from leaf_compare */ + if ((out_end - out) < object_size) { + *status = NODE_STATUS_ERR_LENGTH; + goto end; + } + + if (l->handler) { + l->handler(l->handler_data, l, NODE_GET, out, base); + } else { + if (l->type == LEAF_BOOL) + leaf_bool_get(l, out, base); + else + _leaf_get(l, out, object_size, base); + } + + out += object_size; + leaf_data_length = object_size; + +end: + put_header_status_end(hdr, *status, leaf_data_length); + +out: + return out; +} + +static void leaf_bool_set(struct leaf *l, uint8_t *in, uintptr_t base) +{ + if (in[0]) + ((bool *)(base + l->val))[0] = 1; + else + ((bool *)(base + l->val))[0] = 0; +} + +static void _leaf_set(struct leaf *l, uint8_t *in, unsigned int size, uintptr_t base) +{ + os_memcpy((base + l->val), in, size); +} + +static void *leaf_set(struct node *n, unsigned int id, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end, uintptr_t base) +{ + struct leaf *l = container_of(n, struct leaf, n); + struct node_header_status *hdr; + unsigned int object_size; + uint16_t status; + + if ((!l->val && !base) || !(l->flags & LEAF_W)) { + out = put_header_status(out, out_end, id, NODE_STATUS_ERR_WRITE); + goto out; + } + + if ((l->type < LEAF_TYPE_MIN) || (l->type > LEAF_TYPE_MAX)) { + /* unknown leaf type, stop parsing */ + out = put_header_status(out, out_end, id, NODE_STATUS_ERR_TYPE); + goto out; + } + + hdr = put_header_status_start(out, out_end, id); + if (!hdr) { + /* no room for node response, stop parsing */ + goto out; + } + + object_size = leaf_object_size[l->type]; + + if ((in_end - in) < object_size) { + status = NODE_STATUS_ERR_LENGTH; + goto end; + } + + status = NODE_STATUS_OK; + + if (l->handler) { + l->handler(l->handler_data, l, NODE_SET, in, base); + } else { + if (l->type == LEAF_BOOL) + leaf_bool_set(l, in, base); + else + _leaf_set(l, in, object_size, base); + } + +end: + out = put_header_status_end(hdr, status, 0); + +out: + return out; +} + +static void *leaf_list_get(struct node *n, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end) +{ + return out; +} + +static void *leaf_list_set(struct node *n, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end) +{ + return out; +} + +static int leaf_compare(struct leaf *leaf, uintptr_t base, void *key) +{ + uint8_t leaf_value[12]; + + if (leaf->handler) + leaf->handler(NULL, leaf, NODE_GET, leaf_value, base); + else { + if (leaf->type == LEAF_BOOL) + leaf_bool_get(leaf, leaf_value, base); + else + _leaf_get(leaf, leaf_value, leaf_object_size[leaf->type], base); + } + + return os_memcmp(key, leaf_value, leaf_object_size[leaf->type]); +} + +static bool list_entry_match(struct list *l, struct node *child, uintptr_t base, struct entry_key *keys) +{ + struct leaf *leaf; + int i; + + for (i = 0; i < l->max_key; i++) { + /* key always matches if wildcard */ + if (!keys[i].length) + continue; + + /* else retrieve entry's leaf and check if key matches */ + leaf = container_of(child->child[keys[i].id], struct leaf, n); + + if (leaf_compare(leaf, base, keys[i].val) != 0) + return false; + } + + return true; +} + +static int list_entry_extract_keys(struct node *n, struct list *l, struct entry_key *keys, uint8_t **in, uint8_t *in_end, uint8_t **out, uint8_t *out_end) +{ + struct node_header *key_child_hdr; + struct node *child, *_child; + struct leaf *_leaf; + int i; + + for (i = 0; i < l->max_key; i++) { + key_child_hdr = get_header(*in, in_end); + if (!key_child_hdr) { + /* no room for child header, stop parsing */ + goto err; + } + + *in = (uint8_t *)(key_child_hdr + 1); + + if (key_child_hdr->id != l->key_id[i]) { + /* Invalid key id */ + *out = put_header_status(*out, out_end, key_child_hdr->id, NODE_STATUS_ERR_KEY_ID); + goto err; + } + + if ((in_end - *in) < key_child_hdr->length) { + /* Invalid key length */ + *out = put_header_status(*out, out_end, key_child_hdr->id, NODE_STATUS_ERR_LENGTH); + goto err; + } + + /* sanity checks on key */ + if (l->dynamic_handler) { + child = n->child[0]; + _child = child->child[key_child_hdr->id]; + + if (_child->type != NODE_LEAF) { + *out = put_header_status(*out, out_end, key_child_hdr->id, NODE_STATUS_ERR_TYPE); + goto err; + } + + _leaf = container_of(_child, struct leaf, n); + + /* key length can be 0 for wildcard */ + if (key_child_hdr->length != 0 && key_child_hdr->length != leaf_object_size[_leaf->type]) { + *out = put_header_status(*out, out_end, key_child_hdr->id, NODE_STATUS_ERR_KEY_LENGTH); + goto err; + } + } else { + if (key_child_hdr->length != sizeof(uint16_t)) { + *out = put_header_status(*out, out_end, key_child_hdr->id, NODE_STATUS_ERR_KEY_LENGTH); + goto err; + } + + /* for static array, the key value is the index in the array */ + if (*(uint16_t *)*in >= n->max_child) { + *out = put_header_status(*out, out_end, key_child_hdr->id, NODE_STATUS_ERR_MAX_CHILD); + goto err; + } + } + + /* save key informations */ + keys[i].id = key_child_hdr->id; + keys[i].length = key_child_hdr->length; + keys[i].val = (void *)*in; + + *in += key_child_hdr->length; + } + + return 0; + +err: + return -1; +} + +static uint8_t *list_entry_iterate(struct node *n, enum node_operation operation, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end) +{ + struct node *child; + uintptr_t dynamic_entry_base = 0; + struct entry_key keys[LIST_KEY_MAX] = {0}; + struct list *l = container_of(n, struct list, n); + uint16_t status; + int i; + + /* + * extract list's keys + */ + if (list_entry_extract_keys(n, l, keys, &in, in_end, &out, out_end) < 0) + goto end; + + if (l->dynamic_handler) { + /* + * dynamic list, always at index 0 in the child array + */ + child = n->child[0]; + + /* start from head of the list */ + dynamic_entry_base = 0; + + /* iterate through the dynamic list to look for entries matching the look-up keys */ + while ((dynamic_entry_base = l->dynamic_handler(l->dynamic_handler_data, dynamic_entry_base))) { + if (!list_entry_match(l, child, dynamic_entry_base, keys)) + continue; + + /* entry matched, output keys */ + for (i = 0; i < l->max_key; i++) { + out = leaf_get(child->child[keys[i].id], keys[i].id, out, out_end, dynamic_entry_base, &status); + if (status != NODE_STATUS_OK) + goto end; + } + + out = child_iterate(child, operation, in, in_end, out, out_end, dynamic_entry_base); + } + } else { + /* + * static list, with key value as index in the child array + */ + child = n->child[*(uint16_t *)keys[0].val]; + + out = leaf_get(child->child[keys[0].id], keys[0].id, out, out_end, 0, &status); + if (status != NODE_STATUS_OK) + goto end; + + out = child_iterate(child, operation, in, in_end, out, out_end, 0); + } + +end: + return out; +} + +static uint8_t *list_iterate(struct node *n, unsigned int id, enum node_operation operation, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end) +{ + struct node_header_status *hdr; + uint8_t *out_base; + unsigned int child_total_length; + + hdr = put_header_status_start(out, out_end, id); + if (!hdr) { + /* no room for node response, stop parsing */ + goto end; + } + + out_base = (uint8_t *)(hdr + 1); + + out = list_entry_iterate(n, operation, in, in_end, out_base, out_end); + + child_total_length = out - out_base; + + put_header_status_end(hdr, NODE_STATUS_OK, child_total_length); + +end: + return out; +} + +static uint8_t *node_iterate(struct node *n, unsigned int id, enum node_operation operation, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end) +{ + struct node_header_status *hdr; + uint8_t *out_base; + unsigned int child_total_length; + + hdr = put_header_status_start(out, out_end, id); + if (!hdr) { + /* no room for node response, stop parsing */ + goto end; + } + + out_base = (uint8_t *)(hdr + 1); + + out = child_iterate(n, operation, in, in_end, out_base, out_end, 0); + + child_total_length = out - out_base; + + put_header_status_end(hdr, NODE_STATUS_OK, child_total_length); + +end: + return out; +} + +/* The parent validates the child header + * - enough room for the header, if not stop parsing + * - enough room for the length specified in the header, if not add child error to the response, stop parsing + * - known id, if not add child error to the response, skip child + */ +static uint8_t *child_iterate(struct node *n, enum node_operation operation, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end, uintptr_t base) +{ + struct node_header *child_hdr; + struct node *child; + uint16_t status; + + /* Process childs in a loop */ + while (in < in_end) { + child_hdr = get_header(in, in_end); + if (!child_hdr) { + /* no room for child header, stop parsing */ + break; + } + + in = (uint8_t *)(child_hdr + 1); + + if ((in + child_hdr->length) > in_end) { + /* invalid child data length, stop parsing */ + out = put_header_status(out, out_end, child_hdr->id, NODE_STATUS_ERR_LENGTH); + break; + } + + if (child_hdr->id >= n->max_child) { + /* unknown child id, skip */ + out = put_header_status(out, out_end, child_hdr->id, NODE_STATUS_ERR_ID); + if (!out) { + /* no room for response, stop parsing */ + break; + } + + in += child_hdr->length; + continue; + } + + child = n->child[child_hdr->id]; + + switch (child->type) { + case NODE_CONTAINER: + out = node_iterate(child, child_hdr->id, operation, in, in + child_hdr->length, out, out_end); + + break; + + case NODE_LIST: + out = list_iterate(child, child_hdr->id, operation, in, in + child_hdr->length, out, out_end); + + break; + + case NODE_LEAF: + if (operation == NODE_GET) + out = leaf_get(child, child_hdr->id, out, out_end, base, &status); + else if (operation == NODE_SET) + out = leaf_set(child, child_hdr->id, in, in + child_hdr->length, out, out_end, base); + + break; + + case NODE_LEAF_LIST: + if (operation == NODE_GET) + out = leaf_list_get(child, in, in + child_hdr->length, out, out_end); + else if (operation == NODE_SET) + out = leaf_list_set(child, in, in + child_hdr->length, out, out_end); + + break; + + default: + /* unknown child type, skip */ + out = put_header_status(out, out_end, child_hdr->id, NODE_STATUS_ERR_TYPE); + if (!out) { + /* no room for response, stop parsing */ + goto done; + } + + break; + } + + in += child_hdr->length; + } + +done: + return out; +} + +uint8_t *module_iterate(struct module *m, enum node_operation operation, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end) +{ + return child_iterate(&m->n, operation, in, in_end, out, out_end, 0); +} diff --git a/common/managed_objects.h b/common/managed_objects.h new file mode 100644 index 0000000..d6918e1 --- /dev/null +++ b/common/managed_objects.h @@ -0,0 +1,204 @@ +/* + * Copyright 2018, 2020-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Managed objects handling functions + @details +*/ + +#ifndef _MANAGED_OBJECTS_H_ +#define _MANAGED_OBJECTS_H_ + +#include "os/sys_types.h" + +enum node_type { + NODE_MODULE, + NODE_CONTAINER, + NODE_LIST, + NODE_LIST_ENTRY, + NODE_LEAF, + NODE_LEAF_LIST, +}; + +enum leaf_type { + LEAF_TYPE_MIN, + LEAF_BOOL = LEAF_TYPE_MIN, + LEAF_UINT8, + LEAF_UINT16, + LEAF_UINT32, + LEAF_UINT64, + LEAF_INT8, + LEAF_INT16, + LEAF_INT32, + LEAF_INT64, + LEAF_DOUBLE, + LEAF_SCALED_NS, + LEAF_USCALED_NS, + LEAF_CLOCK_IDENTITY, + LEAF_PORT_IDENTITY, + LEAF_MAC_ADDRESS, + LEAF_TYPE_MAX = LEAF_MAC_ADDRESS, +}; + +enum leaf_flags { + LEAF_R = 0x1, + LEAF_W = 0x2, + LEAF_RW = 0x3 +}; + +enum node_operation { + NODE_GET = 0, + NODE_SET +}; + +enum node_status { + NODE_STATUS_OK = 0, + NODE_STATUS_ERR_LENGTH = 1, + NODE_STATUS_ERR_ID = 2, + NODE_STATUS_ERR_TYPE = 3, + NODE_STATUS_ERR_READ = 4, + NODE_STATUS_ERR_WRITE = 5, + NODE_STATUS_ERR_MAX_CHILD = 6, + NODE_STATUS_ERR_KEY_ID = 7, + NODE_STATUS_ERR_KEY_LENGTH = 8, +}; + +#define LIST_KEY_MAX 4 + +struct entry_key { + uint16_t id; + uint16_t length; + void *val; +}; + +struct node { + enum node_type type; + const char *name; + unsigned int max_child; + struct node *child[]; /* Storage must be provided just below when declaring the actual node instances */ +}; + +struct module { + struct node n; +}; + +struct container { + struct node n; +}; + +struct list { + uint16_t key_id[LIST_KEY_MAX]; + unsigned int max_key; + uintptr_t (*dynamic_handler)(void *, uintptr_t); + void *dynamic_handler_data; + struct node n; +}; + +struct list_entry { + struct node n; +}; + +struct leaf { + struct node n; + enum leaf_flags flags; /* R, W, RW */ + enum leaf_type type; + uint8_t *val; + void (*handler)(void *, struct leaf *, enum node_operation, uint8_t *, uintptr_t); + void *handler_data; +}; + +struct leaf_list { + struct node n; + enum leaf_type type; + void *val; +}; + +struct __attribute__((packed)) node_header { + uint16_t id; + uint16_t length; +}; + +struct __attribute__((packed)) node_header_status { + uint16_t id; + uint16_t length; + uint16_t status; +}; + +void node_array_link(struct node *parent, struct node *child); +void module_init(struct module *module, const char *name); +struct node *container_init(struct container *container, const char *name); +struct node *list_init(struct list *list, const char *name, uintptr_t (*handler)(void *, uintptr_t), void *handler_data, uint16_t num_keys, uint16_t *keys_id); +struct node *list_entry_init(struct list_entry *entry, const char *name); +struct node *leaf_init(struct leaf *leaf, const char *name, enum leaf_type type, enum leaf_flags flags, void *storage, + void (*handler)(void *, struct leaf *, enum node_operation, uint8_t *, uintptr_t), void *handler_data); + + +/* Managed object data tree definition helper macros */ +#define MODULE(_name, _child_n, _childs) \ +struct _name {\ + struct module node;\ + struct node *child[_child_n]; /* child node storage array, must be below the node declaration */\ + _childs\ +}; + +#define CONTAINER(_name, _child_n, _childs) \ +struct {\ + struct container node;\ + struct node *child[_child_n]; /* child node storage array, must be below the node declaration */\ + _childs\ +} _name + +#define LIST(_name, _child_n, _childs) \ +struct {\ + struct list node;\ + struct node *child[_child_n]; /* child node storage array, must be below the node declaration */\ + _childs\ +} _name + +#define LIST_ENTRY(_name, _n, _child_n, _childs) \ +struct {\ + struct list_entry node;\ + struct node *child[_child_n]; /* child node storage array, must be below the node declaration */\ + _childs\ +} _name[_n] + +#define LEAF(_name) \ +struct {\ + struct leaf node;\ +} _name + +/* Managed object data tree initialization helper macros */ +#define MODULE_INIT(_module, _name) module_init(&(_module)->node, _name); + +#define CONTAINER_INIT(_parent, _container) { \ + struct node *n = container_init(&((_parent)->_container.node), #_container);\ + node_array_link(&(_parent)->node.n, n);\ +} + +#define LIST_INIT(_parent, _list, num_keys, keys_id) { \ + struct node *n = list_init(&((_parent)->_list.node), #_list, NULL, NULL, num_keys, keys_id);\ + node_array_link(&(_parent)->node.n, n);\ +} + +#define LIST_DYNAMIC_INIT(_parent, _list, _handler, _handler_data, num_keys, keys_id) { \ + struct node *n = list_init(&((_parent)->_list.node), #_list, _handler, _handler_data, num_keys, keys_id);\ + node_array_link(&(_parent)->node.n, n);\ +} + +#define LIST_ENTRY_INIT(_parent, _entry) {\ + struct node *n = list_entry_init(&((_parent)->_entry.node), #_entry);\ + node_array_link(&(_parent)->node.n, n);\ +} + +#define LEAF_INIT(_parent, _leaf, _type, _flags, _storage, _set_handler, _set_data) {\ + struct node *n = leaf_init(&((_parent)->_leaf.node), #_leaf, _type, _flags, _storage, _set_handler, _set_data);\ + node_array_link(&(_parent)->node.n, n);\ +} + +uint8_t *module_iterate(struct module *m, enum node_operation operation, uint8_t *in, uint8_t *in_end, uint8_t *out, uint8_t *out_end); + +#endif /* _MANAGED_OBJECTS_H_ */ diff --git a/common/net.h b/common/net.h new file mode 100644 index 0000000..84a8731 --- /dev/null +++ b/common/net.h @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Networking services + @details Networking services implementation +*/ + +#ifndef _NET_H_ +#define _NET_H_ + +#include "os/string.h" +#include "os/net.h" + +#include "common/types.h" +#include "common/net_types.h" +#include "common/ether.h" + +#define NTOH_MAC_VALUE(x) ntohll(get_48(x)) + +#define NET_DATA_START(desc) ((void *)((char *)(desc) + (desc)->l2_offset)) + +static inline u16 net_add_eth_header(void *buf, const u8 *mac_dst, u16 ethertype) +{ + struct eth_hdr *eth = (struct eth_hdr *)buf; + + eth->type = htons(ethertype); + + /*The source mac address will be added by the kernel network layer itself */ + os_memcpy(eth->dst, mac_dst, 6); + + return sizeof(struct eth_hdr); +} + +static inline u16 net_add_vlan_header(void *buf, u16 ethertype, u16 vid, u8 pcp, u8 cfi) +{ + struct vlanhdr *vlan = (struct vlanhdr *)buf; + + vlan->type = htons(ethertype); + vlan->label = VLAN_LABEL(vid, pcp, cfi); + + return sizeof(struct vlanhdr); +} + +static inline void net_eui64_from_mac(u8 *eui, u8 *mac_addr, u8 mod) +{ + eui[0] = mac_addr[0]; + eui[1] = mac_addr[1]; + eui[2] = mac_addr[2]; + eui[3] = 0xff; + eui[4] = 0xfe; + eui[5] = mac_addr[3]; + eui[6] = mac_addr[4]; + eui[7] = mac_addr[5] + mod; +} + +#endif /* _NET_H_ */ diff --git a/common/net_types.h b/common/net_types.h new file mode 100644 index 0000000..643e2f1 --- /dev/null +++ b/common/net_types.h @@ -0,0 +1,19 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Networking data types + @details +*/ + +#ifndef _NET_TYPES_H_ +#define _NET_TYPES_H_ + +#include "genavb/net_types.h" + +#endif /* _NET_TYPES_H_ */ diff --git a/common/os/pi_common.c b/common/os/pi_common.c new file mode 100644 index 0000000..9893665 --- /dev/null +++ b/common/os/pi_common.c @@ -0,0 +1,77 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * DOC: PI controller + * + * Implements a PI controller of the form: + * u(t) = Kp * err(t) + Ki * sum(err(t')) + * + * err(t) - error value (the difference between process variable (measured) and setpoint(expected)) + * u(t) - control variable + * Kp - proportional gain + * Ki - integral gain + * + * with several restrictions: + * err(t) and u(t) are 32bit integers + * Kp and Ki are of the form 1/(2^kp), 1/(2^ki) + */ + +/** + * pi_reset() - Resets the PI controller algorithm + * @p - pointer to pi structure + * @u - estimated control variable value + * + * The function resets the PI so that the control variable (u(t)) becomes equal + * to a user provided value. This assumes the user has knowledge of the system + * being modeled and can make a good estimate of the control variable value. + */ +void pi_reset(struct pi *p, int u) +{ + p->err = 0; + p->integral = ((int64_t)u) << p->ki; + + p->u = p->integral >> p->ki; +} + +/** + * pi_init() - Initializes the PI controller algorithm + * @p - pointer to pi structure + * @ki - integral term (the true Ki is 1/(2^ki)) + * @kp - proportional term (the true Kp is 1/(2^kp)) + * + * Called once to initialize the Kp/Ki terms of the PI + */ +void pi_init(struct pi *p, unsigned int ki, unsigned int kp) +{ + if (ki > 31) + ki = 31; + + if (kp > 31) + kp = 31; + + p->ki = ki; + p->kp = kp; +} + +/** + * pi_update() - Updates the PI controller algorithm + * @p - pointer to pi structure + * @err - error sample + * + * Updates the PI with a new err(t) sample + * Returns the new value of the control variable u(t) + */ +int pi_update(struct pi *p, int err) +{ + p->err = err; + p->integral += err; + + p->u = (p->err >> p->kp) + (p->integral >> p->ki); + + return p->u; +} diff --git a/common/os/pi_common.h b/common/os/pi_common.h new file mode 100644 index 0000000..3aad4ed --- /dev/null +++ b/common/os/pi_common.h @@ -0,0 +1,25 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PI_COMMON_H_ +#define _PI_COMMON_H_ + +struct pi { + unsigned int ki; + unsigned int kp; + int err; + int64_t integral; + int u; +}; + +void pi_reset(struct pi *p, int u); + +void pi_init(struct pi *p, unsigned int ki, unsigned int kp); + +int pi_update(struct pi *p, int err); + +#endif /* _PI_COMMON_H_ */ diff --git a/common/os/queue_common.c b/common/os/queue_common.c new file mode 100644 index 0000000..9230570 --- /dev/null +++ b/common/os/queue_common.c @@ -0,0 +1,19 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +void queue_flush(struct queue *q, void *data) +{ + unsigned long entry; + + /* Frees all the buffers in the queue */ + /* It's only safe to dequeue from a rx queue because the user is closing it (i.e, no longer dequeing) */ + while (!queue_empty(q)) { + entry = queue_dequeue(q); + + q->entry_free(data, entry); + } +} diff --git a/common/os/queue_common.h b/common/os/queue_common.h new file mode 100644 index 0000000..84937ea --- /dev/null +++ b/common/os/queue_common.h @@ -0,0 +1,229 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _QUEUE_COMMON_H_ +#define _QUEUE_COMMON_H_ + +/** + * DOC: Queue implementation + * + * Basic queue implementation supporting lockless single reader/single writer. + * To support multiple readers or multiple writers the user of the queue + * needs to add it's own reader/write side locking. + * The queue entries are the size of a pointer and usually store pointers to buffers. + */ + +#ifndef QUEUE_ENTRIES_MAX +#error "OS specific code must define queue size" +#endif + +/** + * struct queue - Queue structure + * @size - queue size (in number of entries) + * @flags - queue flags + * @read - atomic queue read pointer + * @write - atomic queue write pointer + * @entry - queue entry storage + * + * It's possible to use a bigger queue size at run time by allocating a bigger memory area + * for the queue structure. + * + */ +struct queue { + unsigned int size; + unsigned int max_size; + rtos_atomic_t read; + rtos_atomic_t write; + void (*entry_free)(void *data, unsigned long entry); + unsigned long entry[QUEUE_ENTRIES_MAX]; /* Placed last so that queue user can allocate bigger size */ +}; + +void queue_flush(struct queue *q, void *data); + +static inline void queue_init(struct queue *q, void (*entry_free)(void *data, unsigned long entry), unsigned int extra) +{ + q->max_size = QUEUE_ENTRIES_MAX + extra; + q->size = q->max_size; + q->entry_free = entry_free; + rtos_atomic_set(&q->read, 0); + rtos_atomic_set(&q->write, 0); +} + +static inline int queue_set_size(struct queue *q, unsigned int size) +{ + if ((q->size < 1U) || (q->size > q->max_size)) + return -1; + + q->size = size; + + return 0; +} + +/** + * queue_pending() - + * @q - pointer to queue structure + * + * Return: number of pending entries (that can be dequeued) + * + */ +static inline unsigned int queue_pending(struct queue *q) +{ + u32 read = rtos_atomic_read(&q->read); + u32 write = rtos_atomic_read(&q->write); + + if (write >= read) + return write - read; + else + return (write + q->max_size) - read; +} + +/** + * queue_available() - + * @q - pointer to queue structure + * + * Return: number of available free entries (that can be queued) + * + */ +static inline unsigned int queue_available(struct queue *q) +{ + int available = q->size - 1U - queue_pending(q); + + if (likely(available >= 0)) + return available; + + return 0; +} + +static inline int queue_full(struct queue *q) +{ + return !queue_available(q); +} + +static inline int queue_empty(struct queue *q) +{ + return !queue_pending(q); +} + +static inline void __queue_incr(struct queue *q, u32 *index) +{ + (*index)++; + if (*index >= q->max_size) + *index = 0; +} + +/** + * queue_enqueue_init() - optimized queueing init + * @q - pointer to queue structure + * @write - caller provided storage to track optimized queueing operation + * + * queue_enqueue_init()/queue_enqueue_next()/queue_enqueue_done() provide + * an optimized interface to queue several entries in a single go. + * The functions must be called in that order. queue_enqueue_init()/queue_enqueue_done() + * must be called once, queue_enqueue_next() may be called several times. + * The @write variable must not be modified by the caller between the + * queue_enqueue_init() and queue_enqueue_done() calls (it is used + * internally by the enqueue functions) + * At the beginning of the sequence the caller should verify how many entries are + * available to avoid a queue overflow. + * + */ +static inline void queue_enqueue_init(struct queue *q, u32 *write) +{ + *write = rtos_atomic_read(&q->write); +} + +static inline void queue_enqueue_next(struct queue *q, u32 *write, unsigned long entry) +{ + q->entry[*write] = entry; + + __queue_incr(q, write); +} + +static inline void queue_enqueue_done(struct queue *q, u32 write) +{ + smp_wmb(); + rtos_atomic_set(&q->write, write); +} + +/** + * queue_dequeue_init() - optimized dequeueing init + * @q - pointer to queue structure + * @read - caller provided storage to track optimized dequeueing operation + * + * queue_dequeue_init()/queue_dequeue_next()/queue_dequeue_done() provide + * an optimized interface to dequeue several entries in a single go. + * The functions must be called in that order. queue_dequeue_init()/queue_dequeue_done() + * must be called once, queue_dequeue_next() may be called several times. + * The @read variable must not be modified by the caller between the + * queue_dequeue_init() and queue_dequeue_done() calls (it is used + * internally by the dequeue functions) + * At the beginning of the sequence the caller should verify how many entries are + * pending to avoid a queue underflow. + * + */ +static inline void queue_dequeue_init(struct queue *q, u32 *read) +{ + *read = rtos_atomic_read(&q->read); +} + +static inline unsigned long queue_dequeue_next(struct queue *q, u32 *read) +{ + unsigned long entry = q->entry[*read]; + + __queue_incr(q, read); + + return entry; +} + +static inline void queue_dequeue_done(struct queue *q, u32 read) +{ + smp_wmb(); + rtos_atomic_set(&q->read, read); +} + +static inline unsigned long queue_peek(struct queue *q) +{ + if (queue_empty(q)) + return -1; /* 0 is a valid entry value */ + else + return q->entry[rtos_atomic_read(&q->read)]; +} + +static inline int queue_enqueue(struct queue *q, unsigned long entry) +{ + u32 write; + + if (queue_full(q)) + return -1; + + queue_enqueue_init(q, &write); + + queue_enqueue_next(q, &write, entry); + + queue_enqueue_done(q, write); + + return 0; +} + +static inline unsigned long queue_dequeue(struct queue *q) +{ + u32 read; + unsigned long entry; + + if (queue_empty(q)) + return -1; /* 0 is a valid entry value */ + + queue_dequeue_init(q, &read); + + entry = queue_dequeue_next(q, &read); + + queue_dequeue_done(q, read); + + return entry; +} + +#endif /* _QUEUE_COMMON_H_ */ diff --git a/common/ptp.h b/common/ptp.h new file mode 100644 index 0000000..cab441c --- /dev/null +++ b/common/ptp.h @@ -0,0 +1,2165 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file ptp.h + @brief PTP protocol common definitions + @details PDU and protocol definitions for all PTP applications +*/ + +#ifndef _PTP_H_ +#define _PTP_H_ + +#include "genavb/ptp.h" +#include "common/types.h" +#include "common/filter.h" +#include "common/timer.h" + +#include "gptp/config.h" + +#define PTP_MAXFREQ_PPB 200000 /* Maximum allowed clock frequency offset per 802.1AS, in parts per billion (100ppm from TAI, so 200ppm between 2 clocks) */ + + +/* +* PTP Version (802.1AS-2020 10.6.2.2.3, 10.6.2.2.4) +*/ +#define PTP_MINOR_VERSION 1 +#define PTP_VERSION 2 + +/* +* PTP domain (802.1AS-2020 -8.1) +*/ +#define PTP_DOMAIN_0 0 +#define PTP_DOMAIN_NUMBER_MAX 127 +#define PTP_DOMAIN_MINOR_SDOID 0x00 +#define PTP_DOMAIN_MAJOR_SDOID 0x1 + +/* +* CMLDS +*/ +#define PTP_CMLDS_MINOR_SDOID 0x00 +#define PTP_CMLDS_MAJOR_SDOID 0x2 + +struct __attribute ((packed)) ptp_domain { + u8 domain_number; + union { + u16 sdo_id; + struct __attribute__ ((packed)) { +#ifdef __BIG_ENDIAN__ + u16 minor_sdo_id:8; + u16 major_sdo_id:4; + u16 rsvd:4; +#else + u16 rsvd:4; + u16 major_sdo_id:4; + u16 minor_sdo_id:8; +#endif + }s; + }u; +}; + + +/* +* GM Clock Quality (802.1AS - 10.5.3.2.3) +*/ +struct __attribute__ ((packed)) ptp_clock_quality { + u8 clock_class; + u8 clock_accuracy; + u16 offset_scaled_log_variance; +}; + +/* +* System identity (802.1AS - 10.3.2) +* +* The systemIdentity attribute is defined for convenience when comparing two time-aware systems to +* determine which is a better candidate for root and if the time-aware system is grandmaster-capable (i.e., the +* value of priority1 is less than 255, see 8.6.2.1) +*/ +struct __attribute__ ((packed)) ptp_system_identity { + union { + u8 system_identity[14]; + struct __attribute__ ((packed)) { + u8 priority_1; + struct ptp_clock_quality clock_quality; + u8 priority_2; + struct ptp_clock_identity clock_identity; + }s; + }u; +}; + + +/* +* Port time-synchronization spanning tree priority vectors (802.1AS - 10.3.4) +* +* Time-aware systems send best master selection information to each other in announce messages. +* The priority vector is the basis for a concise specification in the BMCA's determination of the time +* synchronization spanning tree and grandmaster +* +* Big-endian +*/ +struct __attribute__ ((packed)) ptp_priority_vector { + union { + u8 priority[28]; + struct __attribute__ ((packed)) { + struct ptp_system_identity root_system_identity; + u16 steps_removed; + struct ptp_port_identity source_port_identity; + u16 port_number; + }s; + }u; +}; + + + +/* +* Precise Timestamp (802.1AS - ) +*/ +struct __attribute__ ((packed)) ptp_timestamp { + u16 seconds_msb; + u32 seconds_lsb; + u32 nanoseconds; +}; + +struct __attribute__ ((packed)) ptp_extended_timestamp { + u16 seconds_msb; + u32 seconds_lsb; + u32 fractional_nanoseconds_msb; + u16 fractional_nanoseconds_lsb; +}; + +/* 802.1AS - 6.3.1 */ +struct __attribute__ ((packed)) ptp_scaled_ns { + union { + u8 scaled_nanoseconds[12]; + struct __attribute__ ((packed)) { + /* Keep all values has unsigned and do unsigned math */ + u16 nanoseconds_msb; /* Should remain 0 for about 500 years (2**64/NSECS_PER_SEC/SECS_PER_DAY/DAYS_PER_YEAR = 584), so will be ignored in all computations */ + u64 nanoseconds; + u16 fractional_nanoseconds; + } s; + } u; +}; + +/* 802.1AS - 6.3.2 */ +struct __attribute__ ((packed)) ptp_u_scaled_ns { + union { + u8 u_scaled_nanoseconds[12]; + struct __attribute__ ((packed)) { + u16 nanoseconds_msb; /* Should remain 0 for about 500 years (2**64/NSECS_PER_SEC/SECS_PER_DAY/DAYS_PER_YEAR = 584), so will be ignored in all computations */ + u64 nanoseconds; + u16 fractional_nanoseconds; + } s; + } u; +}; + + +/* 802.1AS - 6.3.3 */ +struct __attribute__ ((packed)) ptp_time_interval { + s64 scaled_nanoseconds; +}; + +typedef double ptp_double; + + +/* +* TLV types (1588-2008 - Table 34) +*/ + +struct __attribute__ ((packed)) ptp_tlv_header { + u16 tlv_type; + u16 length_field; +}; + +/* TLV types */ +#define PTP_TLV_TYPE_MANAGEMENT 0x0001 +#define PTP_TLV_TYPE_MANAGEMENT_ERROR_STATUS 0x0002 +#define PTP_TLV_TYPE_ORGANIZATION_EXTENSION 0x0003 +#define PTP_TLV_TYPE_REQUEST_UNICAST_TRANSMISSION 0x0004 +#define PTP_TLV_TYPE_GRANT_UNICAST_TRANSMISSION 0x0005 +#define PTP_TLV_TYPE_CANCEL_UNICAST_TRANSMISSION 0x0006 +#define PTP_TLV_TYPE_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION 0x0007 +#define PTP_TLV_TYPE_PATH_TRACE 0x0008 +#define PTP_TLV_TYPE_ALTERNATE_TIME_OFFSET_INDICATOR 0x0009 +#define PTP_TLV_TYPE_AUTHENTICATION 0x2000 +#define PTP_TLV_TYPE_AUTHENTICATION_CHALLENGE 0x2001 +#define PTP_TLV_TYPE_SECURITY_ASSOCIATION_UPDATE 0x2002 +#define PTP_TLV_TYPE_CUM_FREQ_SCALE_FACTOR_OFFSET 0x2003 +#define PTP_TLV_TYPE_ORGANIZATION_EXTENSION_DO_NOT_PROPAGATE 0x8000 + +/* IEEE 802.1AS-2020 - sections 10.6.4.3.5, 10.6.4.4.5 and 10.6.4.5.5 */ +#define PTP_TLV_SUBTYPE_INTERVAL_REQUEST 2 +#define PTP_TLV_SUBTYPE_GPTP_CAPABLE_MESSAGE 4 +#define PTP_TLV_SUBTYPE_GPTP_CAPABLE_INTERVAL 5 + + +/* +* Path Trace TLV (802.1AS - Table 10.8, 10.5.3.2.8) +*/ + +/* FIXME, there shouldn't be such limitation to the number of path_sequence entries */ +#define MAX_PTLV_ENTRIES 16 + +struct __attribute__ ((packed)) ptp_path_tlv { + struct ptp_tlv_header header; + struct ptp_clock_identity path_sequence[MAX_PTLV_ENTRIES]; +}; + +/* +* Follow Up TLV +*/ +struct __attribute__ ((packed)) ptp_follow_up_tlv { + u16 tlv_type; + u16 length_field; + u8 organization_id[3]; + u8 organization_sub_type[3]; + s32 cumulative_scaled_rate_offset; + u16 gm_time_base_indicator; + struct ptp_scaled_ns last_gm_phase_change; + s32 scaled_last_gm_freq_change; +}; + + + +/* +* Message Interval request TLV (802.1AS-2020 - Table 10.14) +*/ +struct __attribute__ ((packed)) ptp_interval_tlv { + s8 link_delay_interval; + s8 time_sync_interval; + s8 announce_interval; + u8 flags; + u16 reserved; +}; + +/* Table 10-14 - Definitions of bits of flags field of message interval request TLV */ +#define ITLV_FLAGS_COMPUTE_RATIO_MASK (0x01) +#define ITLV_FLAGS_COMPUTE_DELAY_MASK (0x02) + + +/* +* gPTP Capable TLV (802.1AS-2020 - Table 10.19) +*/ +struct __attribute__ ((packed)) ptp_gptp_capable_tlv { + s8 log_gptp_capable_message_interval; + u8 flags; + u32 reserved; +}; + + +/* +* Announce Message (802.1AS - Table 10.7) +*/ +struct __attribute__ ((packed)) ptp_announce_pdu { + struct ptp_hdr header; + u8 reserved1[10]; + u16 current_utc_offset; + u8 reserved2; + u8 grandmaster_priority1; + struct ptp_clock_quality grandmaster_clock_quality; + u8 grandmaster_priority2; + struct ptp_clock_identity grandmaster_identity; + u16 steps_removed; + u8 time_source; + struct ptp_path_tlv ptlv; +}; + + +/* +* Signaling Message (802.1AS - 10.5.4 Table 10.9) +*/ +struct __attribute__ ((packed)) ptp_signaling_pdu { + struct ptp_hdr header; + struct ptp_port_identity target_port_identity; /* default to 0xFF */ + u16 tlv_type; + u16 length_field; + u8 organization_id[3]; + u8 organization_sub_type[3]; + union { + struct ptp_interval_tlv itlv; + struct ptp_gptp_capable_tlv ctlv; + } u; +}; + +/* +* Sync Message (802.1AS -) +*/ +struct __attribute__ ((packed)) ptp_sync_pdu { + struct ptp_hdr header; + struct ptp_timestamp timestamp; +}; + + +/* +* follow-up Message (802.1AS -) +*/ +struct __attribute__ ((packed)) ptp_follow_up_pdu { + struct ptp_hdr header; + struct ptp_timestamp precise_origin_timestamp; + struct ptp_follow_up_tlv tlv; +}; + +/* +* PDelay request Message (802.1AS -) +*/ +struct __attribute__ ((packed)) ptp_pdelay_req_pdu { + struct ptp_hdr header; + u8 reserved[20]; +}; + +/* +* PDelay response Message (802.1AS -) +*/ +struct __attribute__ ((packed)) ptp_pdelay_resp_pdu { + struct ptp_hdr header; + struct ptp_timestamp request_receipt_timestamp; + struct ptp_port_identity requesting_port_identity; +}; + + +/* +* Pdelay response follow-up Message (802.1AS -) +*/ +struct __attribute__ ((packed)) ptp_pdelay_resp_follow_up_pdu { + struct ptp_hdr header; + struct ptp_timestamp response_origin_timestamp; + struct ptp_port_identity requesting_port_identity; +}; + + +/* +* Port Roles (802.1AS - Table 10.1 and Table 14.5) */ +typedef enum { + DISABLED_PORT = 3, /** any port of the time-aware system for which portEnabled, ptpPortEnabled, and asCapable are not all TRUE */ + MASTER_PORT = 6, /** any port, P, of the time aware system that is closer than any other port of the gptp communication path connected to P */ + PASSIVE_PORT = 7, /** any port of the time-aware system whose port role is not MasterPort, SlavePort or DisabledPort */ + SLAVE_PORT = 9 /** the one port of the time-aware system that is closest to the root time-aware system. Does not transmit announce or sync messages */ +}ptp_port_role_t; + + +/* +* Time Sources (802.1AS - 8.6.2.7) +*/ +typedef enum { + TIME_SOURCE_ATOMIC_CLOCK = 0x10, + TIME_SOURCE_GPS = 0x20, + TIME_SOURCE_TERRESTRIAL_RADIO = 0x30, + TIME_SOURCE_PTP = 0x40, + TIME_SOURCE_NTP = 0x50, + TIME_SOURCE_HAND_SET = 0x60, + TIME_SOURCE_OTHER = 0x90, + TIME_SOURCE_INTERNAL_OSCILLATOR = 0xA0 +}ptp_time_source_t; + + +/* +* Spanning Tree Information (802.1AS - 10.3.9.2) +*/ +typedef enum { + SPANNING_TREE_RECEIVED = 0, + SPANNING_TREE_MINE, + SPANNING_TREE_AGED, + SPANNING_TREE_DISABLED +}ptp_spanning_tree_info_t; + + +/* +* Values for message_type field (802.1AS - Table 11-3) +* +* Media dependent, full duplex point to point +*/ +#define PTP_MSG_TYPE_SYNC 0x0 +#define PTP_MSG_TYPE_DELAY_REQ 0x1 +#define PTP_MSG_TYPE_PDELAY_REQ 0x2 +#define PTP_MSG_TYPE_PDELAY_RESP 0x3 +#define PTP_MSG_TYPE_FOLLOW_UP 0x8 +#define PTP_MSG_TYPE_DELAY_RESP 0x9 +#define PTP_MSG_TYPE_PDELAY_RESP_FUP 0xA + +/* +* Values for message_type field (802.1AS - Table 10-5) +* +* Media independent layer +*/ + +#define PTP_MSG_TYPE_ANNOUNCE 0xB +#define PTP_MSG_TYPE_SIGNALING 0xC +#define PTP_MSG_TYPE_MANAGEMENT 0xD + + +/* +* Flags bits definitions (802.1AS Cor1 2013 - Table 10.6) +*/ + +//FIXME for now using 802.1AS-2011 octet 1 definition for backward compatibility +#if 0 +/* octet 1 */ +#define PTP_FLAG_LEAP_61 (1<<1) +#define PTP_FLAG_LEAP_59 (1<<2) +#define PTP_FLAG_CURRENT_UTC_OFF_VALID (1<<3) +#define PTP_FLAG_PTP_TIMESCALE (1<<4) +#define PTP_FLAG_TIME_TRACEABLE (1<<5) +#define PTP_FLAG_FREQUENCY_TRACEABLE (1<<6) +#else +/* octet 1 */ +#define PTP_FLAG_LEAP_61 (1<<0) +#define PTP_FLAG_LEAP_59 (1<<1) +#define PTP_FLAG_CURRENT_UTC_OFF_VALID (1<<2) +#define PTP_FLAG_PTP_TIMESCALE (1<<3) +#define PTP_FLAG_TIME_TRACEABLE (1<<4) +#define PTP_FLAG_FREQUENCY_TRACEABLE (1<<5) +#endif +/* octect 0 */ +#define PTP_FLAG_ALTERNATE (1<<0) /* defined in 1588 only */ +#define PTP_FLAG_TWO_STEP (1<<1) /* defined in 1588 only */ +#define PTP_FLAG_UNICAST (1<<2) +#define PTP_FLAG_PROFILE1 (1<<5) /* defined in 1588 only */ +#define PTP_FLAG_PROFILE2 (1<<6) /* defined in 1588 only */ +#define PTP_FLAG_SECURITY (1<<7) /* defined in 1588 only */ + + +/* +* Values for control field (802.1AS - Table 11-7) +*/ +#define PTP_CONTROL_SYNC 0x0 +#define PTP_CONTROL_FOLLOW_UP_2011 0x2 +#define PTP_CONTROL_PDELAY_REQ_2011 0x5 +#define PTP_CONTROL_PDELAY_RESP_2011 0x5 +#define PTP_CONTROL_PDELAY_RESP_FUP_2011 0x5 +#define PTP_CONTROL_ANNOUNCE_2011 0x5 + +/* +* Values for control field (802.1AS -2020 - 10.6.2.2.13 +*/ +#define PTP_CONTROL_FOLLOW_UP 0x0 +#define PTP_CONTROL_PDELAY_REQ 0x0 +#define PTP_CONTROL_PDELAY_RESP 0x0 +#define PTP_CONTROL_PDELAY_RESP_FUP 0x0 +#define PTP_CONTROL_ANNOUNCE 0x0 + +/* +* Values for log message interval field (802.1AS - 11.4.2.8) +*/ +#define PTP_LOG_MSG_PDELAY_RESP 0x7F +#define PTP_LOG_MSG_PDELAY_RESP_FUP 0x7F +#define PTP_LOG_MSG_SIGNALING 0x7F + + +/* +* Delay mechanism (802.1AS-2020 - Table 14.8) +*/ +typedef enum { + P2P = 2, /* The port uses instance-specific peer-top-peer delay mechanism*/ + COMMON_P2P = 3, /* The port uses CMLDS */ + SPECIAL = 4 /*The port uses transport with native time transfer mechanism. No peer-to-peer delay meachism*/ +} ptp_delay_mechanism_t; + +/* +* Per PTP-Port or Per Link-Port global variables. Used by the +* PdelayReq and PdelayResp state machines. +*/ +struct ptp_port_params { + /* + * 802.1AS-2020 - Table 10.1 + */ + u8 begin; + ptp_double neighbor_rate_ratio; + struct ptp_u_scaled_ns mean_link_delay; + struct ptp_scaled_ns delay_asymmetry; + bool compute_neighbor_rate_ratio; + bool current_compute_neighbor_rate_ratio; + bool initial_compute_neighbor_rate_ratio; + bool compute_mean_link_delay; + bool current_compute_mean_link_delay; + bool initial_compute_mean_link_delay; + bool port_oper; /* 802.1.AS-2020 - 10.2.5.12 a Boolean that is set if the time-aware system's MAC is operational (Tx/Rx frames, Spanning Tree, Relay) */ + u16 this_port; /* FIXME port number not used since duplicated by port->port_id */ + bool as_capable_across_domains; +}; + +/* +* Per port global variables +*/ +struct ptp_instance_port_params { + /* + * 802.1AS-2020 - Table 10.1 + * Global variables used by time synchronization state machines. + */ + bool ptp_port_enabled; /* 802.1AS - 10.2.4.12 a Boolean that is set if the time-synchronization and bmca functions of the port are enabled */ + bool as_capable; /* TRUE = pdelay-req-resp from peer, FALSE = ptp link not yet up */ + struct ptp_u_scaled_ns sync_receipt_timeout_time_interval; + s8 current_log_sync_interval; + s8 initial_log_sync_interval; + struct ptp_u_scaled_ns sync_interval; + + u16 this_port; /* port number not used since duplicated by port->port_id */ + bool sync_locked; + bool neighbor_gptp_capable; + bool sync_slow_down; + struct ptp_u_scaled_ns old_sync_interval; + bool gptp_capable_message_slow_down; + struct ptp_u_scaled_ns gptp_capable_message_interval; + struct ptp_u_scaled_ns old_gptp_capable_message_interval; + s8 initial_log_gptp_capable_message_interval; + s8 current_log_gptp_capable_message_interval; + + /*Avnu AutoCDSFunctionalSpec-1_4 - 6.2.1.5 / 6.2.1.6 */ + s8 oper_log_pdelay_req_interval; /* A device moves to this value on all slave ports once the measured values have stabilized*/ + s8 oper_log_sync_interval; /*operLogSyncInterval is the Sync interval that a device moves to and signals on a slave port once it has achieved synchronization*/ + + int rate_ratio; + + /* 802.1AS-2020 - Table 10.3 + * Global variables used by the best master clock selection, external port + * configuration, and announce interval setting state machines. + */ + struct ptp_u_scaled_ns announce_receipt_timeout_time_interval; + bool announce_slow_down; + struct ptp_u_scaled_ns old_announce_interval; + ptp_spanning_tree_info_t info_is; + struct ptp_priority_vector master_priority; + s8 current_log_announce_interval; + s8 initial_log_announce_interval; + struct ptp_u_scaled_ns announce_interval; + u16 message_steps_removed; + bool new_info; + struct ptp_priority_vector port_priority; + u16 port_steps_removed; + struct ptp_announce_pdu *rcvd_announce_ptr; + bool rcvd_msg; + bool updt_info; + u8 ann_leap61; + u8 ann_leap59; + u8 ann_current_utc_offset_valid; + bool ann_ptp_timescale; + u8 ann_time_traceable; + u8 ann_frequency_traceable; + u16 ann_current_utc_offset; + ptp_time_source_t ann_time_source; + struct ptp_clock_identity received_path_trace[MAX_PTLV_ENTRIES]; + + /* 802.1AS-2020 - 14.8.5 */ + ptp_delay_mechanism_t delay_mechanism; +}; + + +/* 802.1AS-2020 - section 10.7.3 */ +#define SYNC_RECEIPT_TIMEOUT 3 +#define ANNOUNCE_RECEIPT_TIMEOUT 3 +#define GPTP_CAPABLE_RECEIPT_TIMEOUT 9 + +/* +* Per instance global variables +*/ +struct ptp_instance_params { + /* 802.1AS-2020 - Table 10.1 + * Global variables used by time synchronization state machines. + */ + u8 begin; + struct ptp_u_scaled_ns clock_master_sync_interval; + struct ptp_extended_timestamp clock_slave_time; + struct ptp_extended_timestamp sync_receipt_time; + struct ptp_u_scaled_ns sync_receipt_local_time; + ptp_double clock_source_freq_offset; + struct ptp_scaled_ns clock_source_phase_offset; + u16 clock_source_time_base_indicator; + u16 clock_source_time_base_indicator_old; + struct ptp_scaled_ns clock_source_last_gm_phase_change; + ptp_double clock_source_last_gm_freq_change; + struct ptp_u_scaled_ns current_time; + bool gm_present; + ptp_double gm_rate_ratio; + u16 gm_time_base_indicator; + struct ptp_scaled_ns last_gm_phase_change; + ptp_double last_gm_freq_change; + struct ptp_time_interval local_clock_tick_interval; + struct ptp_u_scaled_ns local_time; + ptp_port_role_t selected_role[CFG_GPTP_MAX_NUM_PORT + 1]; /* 10.2.3.20 */ + struct ptp_extended_timestamp master_time; + struct ptp_clock_identity this_clock; + s8 parent_log_sync_interval; + bool instance_enable; + struct ptp_u_scaled_ns sync_receipt_timeout_time; + + /* 802.1AS-2020 - Table 10.3 + * Global variables used by the best master clock selection, external port + * configuration, and announce interval setting state machines. + */ + u16 reselect; + u16 selected; + u16 master_steps_removed; + u8 leap61; + u8 leap59; + u8 current_utc_offset_valid; + bool ptp_timescale; + u8 time_traceable; + u8 frequency_traceable; + s16 current_utc_offset; + ptp_time_source_t time_source; + u8 sys_leap61; + u8 sys_leap59; + u8 sys_current_utc_offset_valid; + bool sys_ptp_timescale; + u8 sys_time_traceable; + u8 sys_frequency_traceable; + s16 sys_current_utc_offset; + ptp_time_source_t sys_time_source; + struct ptp_priority_vector system_priority; + struct ptp_priority_vector gm_priority; + struct ptp_priority_vector last_gm_priority; + struct ptp_clock_identity path_trace[MAX_PTLV_ENTRIES]; + bool external_port_configuration_enabled; + u16 last_announce_report; + + /* Non standard */ + u8 do_clock_adjust; + u16 num_ptlv; +}; + + +/* +* 802.1AS Entities definitions +*/ + + +/* 10.2.2.3.1 +When sent from the PortSync or ClockMaster entity, it provides the SiteSync entity with master clock timing +information, timestamp of receipt of a time-synchronization event message compensated for propagation +time on the upstream link, and the time at which sync receipt timeout occurs if a subsequent Sync message is +not received by then. The information is used by the SiteSync entity to compute the rate ratio of the local +oscillator relative to the master and is communicated to the other PortSync entities for use in computing +master clock timing information. +When sent from the SiteSync entity to the PortSync or ClockMaster entity, the structure contains +information needed to compute the synchronization information that will be included in respective fields of +the time-synchronization event and general messages that will be sent, and also to compute the synchronized +time that the ClockSlave entity will supply to the ClockTarget entity. +*/ +struct port_sync_sync +{ + /* IEEE 802.1AS-2020 section 10.2.2.1.2 + This parameter is the domain number of the gPTP domain in which this structure is sent. + NOTE: The domain number member is not essential because the state machines that send and receive this structure are + per domain, and each state machine implicitly knows the number of the domain in which it operates + u8 domainNumber; + */ + u16 localPortNumber; + struct ptp_u_scaled_ns syncReceiptTimeoutTime; + struct ptp_scaled_ns followUpCorrectionField; + struct ptp_port_identity sourcePortIdentity; + s8 logMessageInterval; + struct ptp_timestamp preciseOriginTimestamp; + struct ptp_u_scaled_ns upstreamTxTime; + ptp_double rateRatio; + u16 gmTimeBaseIndicator; + struct ptp_scaled_ns lastGmPhaseChange; + ptp_double lastGmFreqChange; +}; + +/* 9.2.1 +This interface is used by the ClockSource entity to provide time to the ClockMaster entity of a time-aware +system. The ClockSource entity invokes the ClockSourceTime.invoke function to provide the time, relative +to the ClockSource, that this function is invoked. +9.2.2 */ +struct clock_source_time_invoke { + struct ptp_extended_timestamp source_time; + u16 time_base_indicator; + struct ptp_scaled_ns last_gm_phase_change; + ptp_double last_gm_freq_change; +}; + +/***************** ClockSlave entity *********************/ + + +/* +The ClockSlave entity receives grandmaster time-synchronization and +current grandmaster information from the SiteSync entity, and makes the +information available to an external application, referred to as a clockTarget +entity (see 9.3 through 9.6), via one or more application service interfaces +*/ +struct ptp_clock_slave_entity { + struct port_sync_sync pssync; + + /* + 10.2.12.1.1 a Boolean variable that notifies the current state machine when a PortSyncSync + structure is received from the SiteSyncSync state machine of the SiteSync entity. This variable is reset by + this state machine. + */ + bool rcvdPSSync; + + /* + 10.2.12.1.2 a Boolean variable that notifies the current state machine when the + LocalClock entity updates its time. This variable is reset by this state machine. + */ + bool rcvdLocalClockTick; + + /* + 10.2.12.1.3 a pointer to the received PortSyncSync structure + */ + struct port_sync_sync *rcvdPSSyncPtr; + + /* + * Non standard, GM clock identity of the previous sync received + */ + struct ptp_clock_identity prev_gm_clock_identity; +}; + +typedef enum { + CLOCK_SLAVE_SYNC_SM_STATE_INITIALIZING = 0, + CLOCK_SLAVE_SYNC_SM_STATE_SEND_SYNC_INDICATION +}ptp_clock_slave_sync_sm_state_t; + + +/***************** ClockMaster entity *********************/ + + +/* +ClockMasterSyncSend (one instance per time-aware system): receives masterTime from the +ClockMasterSyncReceive state machine, receives phase and frequency offset between masterTime +and syncReceiptTime from the ClockMasterSyncOffset state machine, and provides masterTime +(i.e., synchronized time) and the phase and frequency offset to the SiteSync entity using a +PortSyncSync structure. This state machine is optional for time-aware systems that are not +grandmaster-capable (see 8.6.2.1 and 10.1.2). +*/ +struct clock_master_sync_send_sm { + /* + the time in seconds, relative to the LocalClock entity, when synchronization + information will next be sent to the SiteSync entity, via a PortSyncSync structure. The data type for + syncSendTime is UScaledNs. + */ + struct ptp_u_scaled_ns syncSendTime; + + /* + a pointer to the PortSyncSync structure transmitted by the state machine. + */ + struct port_sync_sync *txPSSyncPtr; +}; + +/* +ClockMasterSyncReceive (one instance per time-aware system): receives ClockSourceTime.invoke +functions from the ClockSource entity and notifications of LocalClock entity ticks (see 10.2.3.18), +updates masterTime, and provides masterTime to ClockMasterSyncOffset and +ClockMasterSyncSend state machines. This state machine is optional for time-aware systems that +are not grandmaster-capable (see 8.6.2.1 and 10.1.2). +*/ +struct clock_master_sync_receive_sm { + /* + a Boolean variable that notifies the current state machine when + ClockSourceTime.invoke function is received from the Clock source entity. This variable is reset by this + state machine. + */ + bool rcvd_clock_source_req; + + /* + a pointer to the received ClockSourceTime.invoke function + parameters. + */ + struct clock_source_time_invoke *rcvd_clock_source_req_ptr; + + /* + a Boolean variable that notifies the current state machine when the + LocalClock entity updates its time. This variable is reset by this state machine. + */ + bool rcvd_local_clock_tick; +}; + + +/* +ClockMasterSyncOffset (one instance per time-aware system): receives syncReceiptTime from the +ClockSlave entity and masterTime from the ClockMasterSyncReceive state machine, computes +phase offset and frequency offset between masterTime and syncReceiptTime if the time-aware +system is not the grandmaster, and provides the frequency and phase offsets to the +ClockMasterSyncSend state machine. This state machine is optional for time-aware systems that are +not grandmaster-capable (see 8.6.2.1 and 10.1.2). +*/ +struct clock_master_sync_offset_sm { + /* + a Boolean variable that notifies the current state machine when + syncReceiptTime is received from the ClockSlave entity. This variable is reset by this state machine. + */ + bool rcvd_sync_receipt_time; +}; + + +/* +The ClockMaster entity receives information from an external time +source, referred to as a ClockSource entity (see 9.2), via an application interface, and provides the +information to the SiteSync entity +*/ +struct ptp_clock_master_entity { + struct port_sync_sync pssync; + struct clock_master_sync_send_sm sync_send_sm; + struct clock_master_sync_receive_sm sync_receive_sm; + struct clock_master_sync_offset_sm sync_offset_sm; + + struct ptp_u_scaled_ns sync_receipt_time_prev; + struct ptp_u_scaled_ns sync_receipt_local_time_prev; + bool clock_source_freq_offset_init; + ptp_double ratio_average; +}; + + +typedef enum { + CLOCK_MASTER_SYNC_SEND_SM_STATE_INITIALIZING = 0, + CLOCK_MASTER_SYNC_SEND_SM_STATE_SEND_SYNC_INDICATION +}ptp_clock_master_sync_send_sm_state_t; + +typedef enum { + CLOCK_MASTER_SYNC_SEND_SM_EVENT_INTERVAL = 0, + CLOCK_MASTER_SYNC_SEND_SM_EVENT_RUN +}ptp_clock_master_sync_send_sm_event_t; + +typedef enum { + CLOCK_MASTER_SYNC_OFFSET_SM_STATE_INITIALIZING = 0, + CLOCK_MASTER_SYNC_OFFSET_SM_STATE_RECEIVED_SYNC_RECEIPT_TIME +}ptp_clock_master_sync_offset_sm_state_t; + +typedef enum { + CLOCK_MASTER_SYNC_RECEIVE_SM_STATE_INITIALIZING = 0, + CLOCK_MASTER_SYNC_RECEIVE_SM_STATE_WAITING, + CLOCK_MASTER_SYNC_RECEIVE_SM_STATE_RECEIVE_SOURCE_TIME +}ptp_clock_master_sync_receive_sm_state_t; + +/***************** PortSync entity *********************/ + + + +/* PortSyncsyncReceiveSM variables 10.2.7.1 +* +*/ +struct port_sync_sync_receive_sm +{ + /* + a Boolean variable that notifies the current state machine when an + MDSyncReceive structure is received from the MDSyncReceiveSM state machine of an MD entity of the + same port (see 10.2.2.1). This variable is reset by this state machine. + */ + bool rcvdMDSync; + + /* + a pointer to the received MDSyncReceive structure indicated by rcvdMDSync. + */ + struct md_sync_receive *rcvdMDSyncPtr; + + /* + a pointer to the PortSyncSync structure transmitted by the state machine. + */ + struct port_sync_sync *txPSSyncPtr; + + /* + a Double variable that holds the ratio of the frequency of the grandmaster to the + frequency of the LocalClock entity. This frequency ratio is computed by (a) measuring the ratio of the + grandmaster frequency to the LocalClock frequency at the grandmaster time-aware system and initializing + rateRatio to this value in the ClockMasterSend state machine of the grandmaster node, and (b) + accumulating, in the PortSyncSyncReceive state machine of each time-aware system, the frequency offset of + the LocalClock entity of the time-aware system at the remote end of the link attached to that port to the + frequency of the LocalClock entity of this time-aware system. + */ + ptp_double rateRatio; +}; + + +typedef enum { + PORT_SYNC_SYNC_SEND_SM_STATE_TRANSMIT_INIT = 0, + PORT_SYNC_SYNC_SEND_SM_STATE_SEND_MD_SYNC, + PORT_SYNC_SYNC_SEND_SM_STATE_SYNC_RECEIPT_TIMEOUT, +} ptp_port_sync_sync_send_sm_state_t; + +typedef enum { + PORT_SYNC_SYNC_SEND_SM_EVENT_PSSYNC_TIMEOUT = 0, + PORT_SYNC_SYNC_SEND_SM_EVENT_RUN +} ptp_port_sync_sync_send_sm_event_t; + +/* PortSyncsyncSendSM variables 802.1AS-2020 section 10.2.12.1 +* +*/ +struct port_sync_sync_send_sm +{ + /* + Boolean variable that notifies the current state machine when a PortSyncSync + structure is received from the SiteSyncSync state machine of the SiteSync entity of the time-aware system + (see 10.2.2.3). This variable is reset by this state machine. + */ + bool rcvd_pssync_psss; + + /* + pointer to the received PortSyncSync structure indicated by rcvdPSSync. + */ + struct port_sync_sync *rcvd_pssync_ptr; + + /* + the sourcePortIdentity member of the most recently received + PortSyncSync structure. The data type for lastSourcePortIdentity is PortIdentity. + */ + struct ptp_port_identity last_source_port_identity; + + /* + the preciseOriginTimestamp member of the most recently + received PortSyncSync structure. The data type for lastPreciseOriginTimestamp is Timestamp. + */ + struct ptp_timestamp last_precise_origin_timestamp; + + /* + the followUpCorrectionField member of the most recently + received PortSyncSync member. The data type for lastFollowUpCorrectionField is ScaledNs. + */ + struct ptp_scaled_ns last_follow_up_correction_field; + + /* + the rateRatio member of the most recently received PortSyncSync structure. The + data type for lastRateRatio is Double. + */ + ptp_double last_rate_ratio; + + /* + the upstreamTxTime of the most recently received PortSyncSync + member. The data type for lastUpstreamTxTime is UScaledNs. + */ + struct ptp_u_scaled_ns last_upstream_tx_time; + + /* + the value of currentTime (i.e., the time relative to the LocalClock entity) + when the most recent MDSyncSend structure was sent. The data type for lastSyncSentTime is UScaledNs. + */ + struct ptp_u_scaled_ns last_sync_sent_time; + + /* + the portNumber of the port on which time-synchronization information was + most recently received. The data type for lastReceivedPortNum is UInteger16. + */ + u16 last_rcvd_port_num; + + /* + the gmTimeBaseIndicator of the most recently received + PortSyncSync member. The data type for lastGmTimeBaseIndicator is UInteger16. + */ + u16 last_gm_time_base_indicator; + + /* + the lastGmPhaseChange of the most recently received PortSyncSync + member. The data type for lastGmPhaseChange is ScaledNs. + */ + struct ptp_scaled_ns last_gm_phase_change; + + /* + the lastGmFreqChange of the most recently received PortSyncSync + member. The data type for lastGmPhaseChange is Double. + */ + ptp_double last_gm_freq_change; + + /* + a pointer to the MDSyncSend structure sent to the MD entity of this port. + */ + struct md_sync_send *tx_md_sync_send_ptr; + + /* + 10.2.12.1.13 numberSyncTransmissions: A count of the number of consecutive Sync message + transmissions after the SyncIntervalSetting state machine (see Figure 10-20) has set syncSlowdown + (see 10.2.5.17) to TRUE. The data type for numberSyncTransmissions is UInteger8. + */ + u8 number_sync_transmissions; + + /* + 10.2.12.1.14 interval1: A local variable that holds either syncInterval or oldSyncInterval. The data type for + interval1 is UScaledNs. + */ + struct ptp_u_scaled_ns interval1; + + /* + the value of the syncReceiptTimeoutTime member of the most + recently received PortSyncSync structure. The data type for syncReceiptTimeoutTime is UScaledNs. + */ + struct ptp_u_scaled_ns sync_receipt_timeout_time; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_port_sync_sync_send_sm_state_t state; + + struct timer sync_transmit_timer; + struct timer sync_receipt_timeout_timer; +}; + + +typedef enum { + PORT_ANNOUNCE_RCV_SM_STATE_DISCARD = 0, + PORT_ANNOUNCE_RCV_SM_STATE_RECEIVE +} ptp_port_announce_rcv_sm_state_t; + +/* PortAnnounceReceiveSM variables IEEE 802.1AS-2020 section 10.3.11 +* +*/ +struct port_announce_receive_sm { + /* + a Boolean variable that notifies the current state machine when Announce + message information is received from the MD entity of the same port. + This variable is reset by this state machine. + */ + bool rcvd_announce_par; + + /* + a pointer to the announce pdu received on the port + */ + struct ptp_announce_pdu *rcvd_announce_ptr; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_port_announce_rcv_sm_state_t state; + + struct timer timeout_timer; +}; + +/* PortAnnounceInformationSM variables 10.3.11 +* +*/ + +struct port_announce_information_sm { + /* + a variable used to save the time at which announce receipt + timeout occurs. The data type for announceReceiptTimeoutTime is UScaledNs. + */ + struct ptp_u_scaled_ns announceReceiptTimeoutTime; + + /* + the messagePriorityVector corresponding to the received Announce + information. The data type for messagePriority is UInteger224 (see 10.3.4). + */ + struct ptp_priority_vector messagePriority; + + /* + an Enumeration2 that holds the value returned by rcvInfo() (see 10.3.11.2.1). + */ + u8 rcvdInfo; +}; + + +typedef enum { + PORT_ANNOUNCE_TRANSMIT_SM_STATE_TRANSMIT_INIT = 0, + PORT_ANNOUNCE_TRANSMIT_SM_STATE_TRANSMIT_ANNOUNCE, + PORT_ANNOUNCE_TRANSMIT_SM_STATE_TRANSMIT_PERIODIC, + PORT_ANNOUNCE_TRANSMIT_SM_STATE_IDLE +} ptp_port_announce_transmit_sm_state_t; + +typedef enum { + PORT_ANNOUNCE_TRANSMIT_SM_EVENT_MIN = 0, + PORT_ANNOUNCE_TRANSMIT_SM_EVENT_TRANSMIT_INTERVAL = PORT_ANNOUNCE_TRANSMIT_SM_EVENT_MIN, + PORT_ANNOUNCE_TRANSMIT_SM_EVENT_RUN, + PORT_ANNOUNCE_TRANSMIT_SM_EVENT_MAX = PORT_ANNOUNCE_TRANSMIT_SM_EVENT_RUN +} ptp_port_announce_transmit_sm_event_t; + + +/* +* PortAnnounceTransmit state machine variables - 802.1AS-2020 section 10.3.16 +*/ + +struct port_announce_transmit_sm { + /* + The time relative to the local clock at which the next transmission of announce information is to occur. + */ + struct ptp_u_scaled_ns announce_send_time; + + /* + A count of the number of consecutives announce message transmissions after the AnnounceIntervalSetting + state mahcine has announceSlowDown to TRUE. + */ + u8 number_announce_transmissions; + + /* + A local variable that holds either announceInterval or oldAnnounceInterval. + */ + struct ptp_u_scaled_ns interval2; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_port_announce_transmit_sm_state_t state; + + struct timer transmit_timer; +}; + + +typedef enum { + PORT_ANNOUNCE_INTERVAL_SM_STATE_NOT_ENABLED = 0, + PORT_ANNOUNCE_INTERVAL_SM_STATE_INITIALIZE, + PORT_ANNOUNCE_INTERVAL_SM_STATE_SET_INTERVAL, +} ptp_port_announce_interval_sm_state_t; + +/* +* AnnounceIntervalSetting state machine variables - 802.1AS-2020 section 10.3.17 +*/ + +struct port_announce_interval_sm { + /* + a Boolean variable that notifies the current state machine when a Signaling + message that contains a Message Interval Request TLV (see 10.5.4.3) is received. This variable is reset by + the current state machine. + */ + bool rcvd_signaling_msg2; + + /* + a pointer to a structure whose members contain the values of the fields of the + received Signaling message that contains a Message Interval Request TLV (see 10.5.4.3). + */ + struct ptp_signaling_pdu *rcvd_signaling_ptr_ais; + + /* + 10.3.17.1.3 logSupportedAnnounceIntervalMax: The maximum supported logarithm to base 2 of the + announce interval. The data type for logSupportedAnnounceIntervalMax is Integer8. + */ + s8 log_supported_announce_interval_max; + + /* + 10.3.17.1.4 logSupportedClosestLongerAnnounceInterval: The logarithm to base 2 of the announce + interval, such that logSupportedClosestLongerAnnounceInterval > logRequestedAnnounceInterval, that is + numerically closest to logRequestedAnnounceInterval, where logRequestedAnnounceInterval is the + argument of the function computeLogAnnounceInterval() (see 10.3.17.2.2). The data type for + logSupportedClosestLongerAnnounceInterval is Integer8. + */ + s8 log_supported_closest_longer_announce_interval; + + /* + 10.3.17.1.5 computedLogAnnounceInterval: A variable used to hold the result of the function + computeLogAnnounceInterval(). The data type for computedLogAnnounceInterval is Integer8. + */ + s8 computed_log_announce_interval; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_port_announce_interval_sm_state_t state; +}; + + +/* +The PortSync entity for a SlavePort receives best master selection information +from the time-aware system at the other end of the associated link, compares this to +the current best master information that it has, and forwards the result of the +comparison to the Site Sync entity. + +The PortSync entity for a SlavePort also receives time-synchronization information from the MD +entity associated with the port, and forwards it to the SiteSync entity. + +The PortSync entity for a MasterPort sends best master selection and time-synchronization +information to the MD entity for the port, which in turn sends the respective messages +*/ +struct ptp_port_sync_entity { + struct port_sync_sync sync; + struct port_sync_sync_receive_sm sync_receive_sm; + struct port_sync_sync_send_sm sync_send_sm; + struct port_announce_receive_sm announce_receive_sm; + struct port_announce_information_sm announce_information_sm; +}; + +typedef enum { + PORT_SYNC_SYNC_RCV_SM_STATE_DISCARD = 0, + PORT_SYNC_SYNC_RCV_SM_STATE_RECEIVED_MDSYNC +} ptp_port_sync_sync_rcv_sm_state_t; + +typedef enum { + PORT_ANNOUNCE_INFO_SM_STATE_DISABLED = 0, + PORT_ANNOUNCE_INFO_SM_STATE_AGED, + PORT_ANNOUNCE_INFO_SM_STATE_UPDATE, + PORT_ANNOUNCE_INFO_SM_STATE_CURRENT, + PORT_ANNOUNCE_INFO_SM_STATE_RECEIVE, + PORT_ANNOUNCE_INFO_SM_STATE_SUPERIOR_MASTER_PORT, + PORT_ANNOUNCE_INFO_SM_STATE_REPEATED_MASTER_PORT, + PORT_ANNOUNCE_INFO_SM_STATE_INFERIOR_MASTER_OR_OTHER +} ptp_port_announce_info_sm_state_t; + +typedef enum { + PORT_ANNOUNCE_INFO_SM_EVENT_SYNC_TIMEOUT = 0, + PORT_ANNOUNCE_INFO_SM_EVENT_ANNOUNCE_TIMEOUT, + PORT_ANNOUNCE_INFO_SM_EVENT_RUN, +} ptp_port_announce_info_sm_event_t; + + +typedef enum { + ANNOUNCE_PRIO_SUPERIOR_MASTER_INFO = 0, + ANNOUNCE_PRIO_REPEATED_MASTER_INFO, + ANNOUNCE_PRIO_INFERIOR_MASTER_INFO, + ANNOUNCE_PRIO_OTHER_INFO, +}ptp_announce_priority_info_t; + + +typedef enum { + PORT_STATE_SM_STATE_INIT_BRIDGE = 0, + PORT_STATE_SM_STATE_SELECTION +} ptp_port_state_sm_state_t; + + +/***************** SiteSync entity *********************/ + + +/* SiteSyncSyncSM variables 10.2.6.1 +* +*/ +struct ptp_site_sync_sync_sm { + /* + a Boolean variable that notifies the current state machine when a PortSyncSync + structure (see 10.2.2.3) is received from the PortSyncSyncReceive state machine of a PortSync entity or + from the ClockMasterSyncSend state machine of the ClockMaster entity. This variable is reset by this state + machine. + */ + bool rcvdPSSync; + + /* + a pointer to the received PortSyncSync structure indicated by rcvdPSSync. + */ + struct port_sync_sync *rcvdPSSyncPtr; + + /* + a pointer to the PortSyncSync structure transmitted by the state machine. + */ + struct port_sync_sync *txPSSyncPtr; +}; + + + +/* +The SiteSync entity executes the portion of best master clock selection associated with +the time-aware system as a whole, i.e., it uses the best master information received on +each port to determine which port has received the best information, and updates the roles +of all the ports.It also distributes synchronization information received on the SlavePort +to all the ports whose role is MasterPort +*/ +struct ptp_site_sync_entity { + struct port_sync_sync pssync; + struct ptp_site_sync_sync_sm sync_sm; +}; + + +typedef enum { + SITE_SYNC_SYNC_SM_STATE_INITIALIZING = 0, + SITE_SYNC_SYNC_SM_STATE_RECEIVING_SYNC +} ptp_site_sync_sync_sm_state_t; + + +/***************** MD entity *********************/ + + +/* +* Per Instance and per PTP-Port MD entity global variables (11.2.13) +*/ +struct ptp_instance_md_entity_globals { + /* + the sequenceId for the next Sync message to be sent by this MD entity. The data + type for syncSequenceId is UInteger16. + */ + u16 syncSequenceId; + + /* + Boolean variable that is TRUE if the PTP Port is capable of receiving one-step + Sync messages. + */ + bool oneStepReceive; + + /* + A Boolean variable that is TRUE if the PTP Port is capable of transmitting + one-step Sync messages.*/ + bool oneStepTransmit; + + /* + A Boolean variable that is TRUE if the PTP Port will be transmitting one-step + Sync messages (see 11.1.3). + */ + bool oneStepTxOper; +}; + + +/* +* Per PTP-Port or per Link-Port MD entity global variables (11.2.13) +*/ +struct ptp_md_entity_globals { + /* + the current value of the logarithm to base 2 of the mean time + interval, in seconds, between the sending of successive Pdelay_Req messages (see 11.5.2.2). This value is + set in the LinkDelaySyncIntervalSetting state machine (see 11.2.17). The data type for + currentLogPdelayReqInterval is Integer8. + */ + s8 currentLogPdelayReqInterval; + + /* + the initial value of the logarithm to base 2 of the mean time + interval, in seconds, between the sending of successive Pdelay_Req messages (see 11.5.2.2). The data type + for initialLogPdelayReqInterval is Integer8. + */ + s8 initialLogPdelayReqInterval; + + /* + a variable containing the mean Pdelay_Req message transmission interval + for the port corresponding to this MD entity. The value is set in the LinkDelaySyncIntervalSetting state + machine (see 11.2.17). The data type for pdelayReqInterval is UScaledNs. + */ + struct ptp_u_scaled_ns pdelayReqInterval; + + /* + the number of Pdelay_Req messages for which a valid response is not + received, above which a port is considered to not be exchanging peer delay messages with its neighbor. The + data type for allowedLostResponses is UInteger8. The required value of allowedLostResponses is given in 11.5.3. + */ + u8 allowedLostResponses; + + /* + The number of faults above which asCapableAcrossDomains is set to FALSE, i.e., the port is considered not capable + of interoperating with its neighbor via the IEEE 802.1AS protocol + */ + u8 allowedFaults; + + /* + a Boolean that is TRUE if the port is measuring link propagation delay. For a + full-duplex, point-to-point link, the port is measuring link propagation delay if it is receiving Pdelay_Resp + and Pdelay_Resp_Follow_Up messages from the port at the other end of the link (i.e., it performs the + measurement using the peer delay mechanism). + */ + bool isMeasuringDelay; + + /* + the propagation time threshold, above which a port is not considered + capable of participating in the IEEE 802.1AS protocol. If meanLinkDelay (see 10.2.4.7) exceeds + meanLinkDelayThresh, then asCapable (see 11.2.12.4) is set to FALSE. The data type for + meanLinkDelayThresh is UScaledNs. + */ + struct ptp_u_scaled_ns meanLinkDelayThresh; +}; + + +typedef enum { + PDELAY_REQ_SM_STATE_MIN_VALUE = 0, + PDELAY_REQ_SM_STATE_NOT_ENABLED = PDELAY_REQ_SM_STATE_MIN_VALUE, + PDELAY_REQ_SM_STATE_INITIAL_SEND_PDELAY_REQ, + PDELAY_REQ_SM_STATE_RESET, + PDELAY_REQ_SM_STATE_SEND_PDELAY_REQ, + PDELAY_REQ_SM_STATE_WAITING_FOR_PDELAY_RESP, + PDELAY_REQ_SM_STATE_WAITING_FOR_PDELAY_RESP_FOLLOW_UP, + PDELAY_REQ_SM_STATE_WAITING_FOR_PDELAY_INTERVAL_TIMER, + PDELAY_REQ_SM_STATE_MAX_VALUE = PDELAY_REQ_SM_STATE_WAITING_FOR_PDELAY_INTERVAL_TIMER +} ptp_pdelay_req_sm_state_t; + +typedef enum { + PDELAY_REQ_SM_EVENT_MIN_VALUE = 0, + PDELAY_REQ_SM_EVENT_REQ_INTERVAL = PDELAY_REQ_SM_EVENT_MIN_VALUE, + PDELAY_REQ_SM_EVENT_RUN, + PDELAY_REQ_SM_EVENT_MAX_VALUE = PDELAY_REQ_SM_EVENT_RUN +} ptp_pdelay_req_sm_event_t; + +/* +* MDPdelayReqSM variables 11.2.15.1 +*/ +struct ptp_md_entity_pdelay_req_sm { + /* + a variable used to save the time at which the Pdelay_Req interval timer is + started A Pdelay_Req message is sent when this timer expires. + */ + u64 pdelayIntervalTimer; + + /* + a Boolean variable that notifies the current state machine when a Pdelay_Resp + message is received. This variable is reset by the current state machine. + */ + bool rcvdPdelayResp; + + /* + a pointer to a structure whose members contain the values of the fields of the + Pdelay_Resp message whose receipt is indicated by rcvdPdelayResp (see 11.2.15.1.2). + */ + struct ptp_pdelay_resp_pdu *rcvdPdelayRespPtr; + + /* + a Boolean variable that notifies the current state machine when a + Pdelay_Resp_Follow_Up message is received. This variable is reset by the current state machine + */ + bool rcvdPdelayRespFollowUp; + + /* + a pointer to a structure whose members contain the values of the + fields of the Pdelay_Resp_Follow_Up message whose receipt is indicated by rcvdPdelayRespFollowUp + */ + struct ptp_pdelay_resp_follow_up_pdu *rcvdPdelayRespFollowUpPtr; + + /* + a pointer to a structure whose members contain the values of the fields of a + Pdelay_Req message to be transmitted. + */ + struct ptp_pdelay_req_pdu *txPdelayReqPtr; + + /* + a Boolean variable that notifies the current state machine when + the (see 11.3.2.1) for a transmitted Pdelay_Req message is received. + This variable is reset by the current state machine. + */ + bool rcvdMDTimestampReceive; + + /* + a variable that holds the sequenceId for the next Pdelay_Req message + to be transmitted by this MD entity. The data type for pdelayReqSequenceId is UInteger16. + */ + u16 pdelayReqSequenceId; + + /* + a count of the number of consecutive Pdelay_Req messages sent by the port, + for which Pdelay_Resp and/or Pdelay_Resp_Follow_Up messages are not received. The data type for + lostResponses is UInteger16. + */ + u16 lostResponses; + + /* PICS AVnu PTP-5 + a count of the number of multiple Pdelay_Resp messages received by the port. + The data type for multipleResponses is UInteger16. + */ + u16 multipleResponses; + + /* + a Boolean variable that indicates whether or not the function + computePdelayRateRatio() (see 11.2.15.2.3) successfully computed neighborRateRatio (see 10.2.4.6). + */ + bool neighborRateRatioValid; + + /* + A count of the number of consecutive faults + */ + u8 detectedFaults; + + /* + Boolean variable whose value is equal to ptpPortEnabled (see 10.2.5.13) if + this state machine is invoked by the instance-specific peer-to-peer delay mechanism and is equal to + cmldsLinkPortEnabled (see 11.2.18.1) if this state machine is invoked by the CMLDS + */ + /*Note: Type changed to pointer to make easier tracking of the ptpPortEnabled/cmldsLinkPortEnabled value changes + see gptp.c::gptp_begin_port_update_fsm() */ + bool *portEnabled0; + + /* + A variable whose value is +1 if this state machine is invoked by the instance-specific peer-to- + peer delay mechanism and -1 if this state machine is invoked by the CMLDS + */ + s8 s; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + u64 prev_pdelay_response_event_ingress_timestamp; // in nanoseconds + u64 prev_corrected_responder_event_timestamp; // in nanoseconds + struct filter pdelay_filter; + + unsigned int prev_rate_ratio_local_clk_phase_discont; + unsigned int prev_pdelay_local_clk_phase_discont; + + ptp_pdelay_req_sm_state_t state; + + u64 req_tx_ts; + u64 resp_rx_ts; + + struct timer req_timer; + + struct ptp_pdelay_req_pdu req_tx; + struct ptp_pdelay_resp_pdu resp_rx; + struct ptp_pdelay_resp_follow_up_pdu resp_fup_rx; + + /* pdelay timing profiling */ + u64 req_time; + u64 req_to_ts_min; + u64 req_to_ts_max; + u64 ts_time; + u64 resp_time; + u64 fup_time; + u64 req_to_ts; + u64 req_to_resp; + u64 req_to_fup; +}; + + +typedef enum { + PDELAY_RESP_SM_STATE_MIN_VALUE = 0, + PDELAY_RESP_SM_STATE_NOT_ENABLED = PDELAY_REQ_SM_EVENT_MIN_VALUE, + PDELAY_RESP_SM_STATE_WAITING_FOR_PDELAY_REQ, + PDELAY_RESP_SM_STATE_INITIAL_WAITING_FOR_PDELAY_REQ, + PDELAY_RESP_SM_STATE_SENT_PDELAY_RESP_WAITING_FOR_TIMESTAMP, + PDELAY_RESP_SM_STATE_MAX_VALUE = PDELAY_RESP_SM_STATE_SENT_PDELAY_RESP_WAITING_FOR_TIMESTAMP +} ptp_pdelay_resp_sm_state_t; + + +typedef enum { + PDELAY_RESP_SM_EVENT_MIN_VALUE = 0, + PDELAY_RESP_SM_EVENT_REQ_RECEIVED = PDELAY_RESP_SM_EVENT_MIN_VALUE, + PDELAY_RESP_SM_EVENT_RUN, + PDELAY_RESP_SM_EVENT_MAX_VALUE = PDELAY_RESP_SM_EVENT_RUN +} ptp_pdelay_resp_sm_event_t; + +/* +* MDPdelayRespSM variables 11.2.16.1 +*/ +struct ptp_md_entity_pdelay_resp_sm { + /* + a Boolean variable that notifies the current state machine when a Pdelay_Req + message is received. This variable is reset by the current state machine. + */ + bool rcvdPdelayReq; + + /* + a Boolean variable that notifies the current state machine when + the (see 11.3.2.1) for a transmitted Pdelay_Resp message is received. + This variable is reset by the current state machine. + */ + bool rcvdMDTimestampReceive; + + /* + a pointer to a structure whose members contain the values of the fields of a + Pdelay_Resp message to be transmitted. + */ + struct ptp_pdelay_resp_pdu *txPdelayRespPtr; + + /* + a pointer to a structure whose members contain the values of the + fields of a Pdelay_Resp_Follow_Up message to be transmitted. + */ + struct ptp_pdelay_resp_follow_up_pdu *txPdelayRespFollowUpPtr; + + /* + Boolean variable whose value is equal to ptpPortEnabled (see 10.2.5.13) if + this state machine is invoked by the instance-specific peer-to-peer delay mechanism and is equal to + cmldsLinkPortEnabled (see 11.2.18.1) if this state machine is invoked by the CMLDS + */ + /*Note: Type changed to pointer to make easier tracking of the ptpPortEnabled/cmldsLinkPortEnabled value changes + see gptp.c::gptp_begin_port_update_fsm() */ + bool *portEnabled1; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + + /* + A variable whose value is +1 if this state machine is invoked by the instance-specific peer-to- + peer delay mechanism and -1 if this state machine is invoked by the CMLDS + */ + s8 s; + + ptp_pdelay_resp_sm_state_t state; + + u64 req_rx_ts; + u64 resp_tx_ts; + + struct ptp_pdelay_req_pdu req_rx; + struct ptp_pdelay_resp_pdu resp_tx; + struct ptp_pdelay_resp_follow_up_pdu resp_fup_tx; +}; + + + +/* +This structure contains information that is sent by the MD entity of a port to the PortSync entity of that port. +It provides the PortSync entity with master clock timing information and timestamp of receipt of a time- +synchronization event message compensated for propagation time on the upstream link. The information is +sent to the PortSync entity upon receipt of time-synchronization information by the MD entity of the port. +The information is in turn provided by the PortSync entity to the SiteSync entity. The information is used by +the SiteSync entity to compute the rate ratio of the local oscillator relative to the master and is +communicated to the other PortSync entities for use in computing master clock timing information. +*/ + +struct md_sync_receive { + struct ptp_scaled_ns followUpCorrectionField; + struct ptp_port_identity sourcePortIdentity; + u8 logMessageInterval; + struct ptp_timestamp preciseOriginTimestamp; + struct ptp_u_scaled_ns upstreamTxTime; + ptp_double rateRatio; + u16 gmTimeBaseIndicator; + struct ptp_scaled_ns lastGmPhaseChange; + double lastGmFreqChange; +}; + +/* +This structure contains information that is sent by the PortSync entity of a port to the MD entity of that port +when requesting that the MD entity cause time-synchronization information to be sent. The structure +contains information that reflects the most recent time-synchronization information received by this time- +aware system, and is used to determine the contents of the time-synchronization event message and possibly +separate general message that will be sent by this port. +*/ +struct md_sync_send { + struct ptp_scaled_ns followUpCorrectionField; + struct ptp_port_identity sourcePortIdentity; + s8 logMessageInterval; + struct ptp_timestamp preciseOriginTimestamp; + struct ptp_u_scaled_ns upstreamTxTime; + ptp_double rateRatio; + u16 gmTimeBaseIndicator; + struct ptp_scaled_ns lastGmPhaseChange; + ptp_double lastGmFreqChange; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + + /* Added for AVnu test gPTP.com.c.14.2. portNumber of the port on which time-synchronization information was most recently received.*/ + u16 lastRcvdPortNum; +}; + +/* +* MDSyncReceiveSM variables 11.2.13.1 +*/ +struct ptp_md_entity_sync_receive_sm{ + /* + a variable used to save the time at which the information + conveyed by a received Sync message will be discarded if the associated Follow_Up message is not received + by then. The data type for syncReceiptTimeout is UScaledNs + */ + struct ptp_u_scaled_ns followUpReceiptTimeoutTime; + + /* + a Boolean variable that notifies the current state machine when a Sync message is + received. This variable is reset by the current state machine. + */ + bool rcvdSync; + + /* + a Boolean variable that notifies the current state machine when a Follow_Up + message is received. This variable is reset by the current state machine. + */ + bool rcvdFollowUp; + + /* + a pointer to a structure whose members contain the values of the fields of the + Sync message whose receipt is indicated by rcvdSync (see 11.2.13.1.2). + */ + struct ptp_sync_pdu *rcvdSyncPtr; + + /* + a pointer to a structure whose members contain the values of the fields of the + Follow_Up message whose receipt is indicated by rcvdFollowUp (see 11.2.13.1.3). + */ + struct ptp_follow_up_pdu *rcvdFollowUpPtr; + + /* + a pointer to a structure whose members contain the values of the + parameters of an MDSyncReceive structure to be transmitted. + */ + struct md_sync_receive *txMDSyncReceivePtr; + + /* + the sync interval (see 10.6.2.1) for the upstream port that sent the + received Sync message + */ + s8 upstreamSyncInterval; +}; + + +/* +* MDSyncSendSM variables 11.2.14.1 +*/ +struct ptp_md_entity_sync_send_sm{ + /* + a Boolean variable that notifies the current state machine when an MDSyncSend + structure is received. This variable is reset by the current state machine. + */ + bool rcvdMDSync; + + /* + a pointer to a structure whose members contain the values of the fields of a Sync + message to be transmitted. + */ + struct ptp_sync_pdu *txSyncPtr; + + /* + a Boolean variable that notifies the current state machine when + the (see 11.3.2.1) for a transmitted Sync message is received. This variable is + reset by the current state machine. + */ + bool rcvdMDTimestampReceive; + + /* + a pointer to the received MDTimestampReceive structure (see 11.2.9). + */ + struct md_timestamp_receive *rcvdMDTimestampReceivePtr; + + /* + a pointer to a structure whose members contain the values of the fields of a + Follow_Up message to be transmitted. + */ + struct ptp_follow_up_pdu *txFollowUpPtr; +}; + + +typedef enum { + LINK_INTERVAL_SETTING_SM_STATE_MIN_VALUE = 0, + LINK_INTERVAL_SETTING_SM_STATE_NOT_ENABLED = LINK_INTERVAL_SETTING_SM_STATE_MIN_VALUE, + LINK_INTERVAL_SETTING_SM_STATE_INITIALIZE, + LINK_INTERVAL_SETTING_SM_STATE_SET_INTERVAL, + LINK_INTERVAL_SETTING_SM_STATE_MAX_VALUE = LINK_INTERVAL_SETTING_SM_STATE_SET_INTERVAL +} ptp_link_interval_setting_sm_state_t; + + + +/* +* LinkDelayIntervalSetting variables 11.2.21 +*/ +struct ptp_md_link_delay_interval_setting_sm { + + /* + 11.2.21.2.1 rcvdSignalingMsg1: A Boolean variable that notifies the current state machine when a + Signaling message that contains a Message Interval Request TLV (see 10.6.4.3) is received. This variable is + reset by the current state machine. + */ + bool rcvd_signaling_msg1; + + /* + 11.2.21.2.2 rcvdSignalingPtrLDIS: A pointer to a structure whose members contain the values of the fields + of the received Signaling message that contains a Message Interval Request TLV (see 10.6.4.3). + */ + struct ptp_signaling_pdu *rcvd_signaling_ptr_ldis; + + /* + 11.2.21.2.3 portEnabled3: A Boolean variable whose value is equal to ptpPortEnabled (see 10.2.5.13) if + this state machine is invoked by the instance-specific peer-to-peer delay mechanism and is equal to + cmldsLinkPortEnabled (see 11.2.18.1) if this state machine is invoked by the CMLDS. + */ + /*Note: Type changed to pointer to make easier tracking of the ptpPortEnabled/cmldsLinkPortEnabled value changes + see gptp.c::gptp_begin_port_update_fsm() */ + bool *portEnabled3; + + /* + 11.2.21.2.4 logSupportedPdelayReqIntervalMax: The maximum supported logarithm to base 2 of the + Pdelay_Req interval. The data type for logSupportedPdelayReqIntervalMax is Integer8. + */ + s8 log_supported_pdelayreq_interval_max; + + /* + 11.2.21.2.5 logSupportedClosestLongerPdelayReqInterval: The logarithm to base 2 of the Pdelay_Req + interval, such that logSupportedClosestLongerPdelayReqInterval > logRequestedPdelayReqInterval, that is + numerically closest to logRequestedPdelayReqInterval, where logRequestedPdelayReqInterval is the + argument of the function computeLogPdelayReqInterval() (see 11.2.21.3.2). The data type for + logSupportedClosestLongerPdelayReqInterval is Integer8. + */ + s8 log_supported_closest_longer_pdelayreq_Interval; + + /* + 11.2.21.2.6 computedLogPdelayReqInterval: A variable used to hold the result of the function + computeLogPdelayReqInterval(). The data type for computedLogPdelayReqInterval is Integer8 + */ + s8 computed_log_pdelayReq_interval; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_link_interval_setting_sm_state_t state; +}; + + +typedef enum { + SYNC_INTERVAL_SETTING_SM_STATE_MIN_VALUE = 0, + SYNC_INTERVAL_SETTING_SM_STATE_NOT_ENABLED = SYNC_INTERVAL_SETTING_SM_STATE_MIN_VALUE, + SYNC_INTERVAL_SETTING_SM_STATE_INITIALIZE, + SYNC_INTERVAL_SETTING_SM_STATE_SET_INTERVAL, + SYNC_INTERVAL_SETTING_SM_STATE_MAX_VALUE = SYNC_INTERVAL_SETTING_SM_STATE_SET_INTERVAL +} ptp_sync_interval_setting_sm_state_t; + + +/* +* SyncIntervalSetting variables 10.3.18 +*/ +struct ptp_sync_interval_setting_sm { + /* + 10.3.18.1.1 A Boolean variable that notifies the current state machine when a + Signaling message that contains a Message Interval Request TLV (see 10.6.4.3) is received. This variable is + reset by the current state machine. + */ + bool rcvd_signaling_msg3; + + /* + 10.3.18.1.2 A pointer to a structure whose members contain the values of the fields + of the received Signaling message that contains a Message Interval Request TLV (see 10.6.4.3). + */ + struct ptp_signaling_pdu *rcvd_signaling_ptr_sis; + + /* + 10.3.18.1.3 logSupportedSyncIntervalMax: The maximum supported logarithm to base 2 of the sync + interval. The data type for logSupportedSyncIntervalMax is Integer8. + */ + s8 log_supported_sync_interval_max; + + /* + 10.3.18.1.4 The logarithm to base 2 of the sync interval, such + that logSupportedClosestLongerSyncInterval > logRequestedSyncInterval, that is numerically closest to + logRequestedSyncInterval, where logRequestedSyncInterval is the argument of the function + computeLogSyncInterval() (see 10.3.18.2.2). The data type for logSupportedClosestLongerSyncInterval is + Integer8. + */ + s8 log_supported_closest_longer_sync_interval; + + /* + 10.3.18.1.5 A variable used to hold the result of the function + computeLogSyncInterval(). The data type for computedLogSyncInterval is Integer8. + */ + s8 computed_log_sync_interval; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_sync_interval_setting_sm_state_t state; +}; + + +typedef enum { + GPTP_CAPABLE_TRANSMIT_SM_EVENT_MIN_VALUE = 0, + GPTP_CAPABLE_TRANSMIT_SM_EVENT_RUN = GPTP_CAPABLE_TRANSMIT_SM_EVENT_MIN_VALUE, + GPTP_CAPABLE_TRANSMIT_SM_EVENT_INTERVAL, + GPTP_CAPABLE_TRANSMIT_SM_EVENT_MAX_VALUE = GPTP_CAPABLE_TRANSMIT_SM_EVENT_INTERVAL +} ptp_gptp_capable_transmit_sm_event_t; + +typedef enum { + GPTP_CAPABLE_TRANSMIT_SM_STATE_MIN_VALUE = 0, + GPTP_CAPABLE_TRANSMIT_SM_STATE_NOT_ENABLED = GPTP_CAPABLE_TRANSMIT_SM_STATE_MIN_VALUE, + GPTP_CAPABLE_TRANSMIT_SM_STATE_INITIALIZE, + GPTP_CAPABLE_TRANSMIT_SM_STATE_TRANSMIT_TLV, + GPTP_CAPABLE_TRANSMIT_SM_STATE_MAX_VALUE = GPTP_CAPABLE_TRANSMIT_SM_STATE_TRANSMIT_TLV +} ptp_gptp_capable_transmit_sm_state_t; + +/* +* gPTPCapableTransmit variables 802.1AS-2020 - 10.4.1 +*/ +struct ptp_gptp_capable_transmit_sm { + /* + 10.4.1.1.1 intervalTimer: A variable used to save the time at which the gPTP-capable message interval + timer is set (see Figure 10-21). A Signaling message containing a gPTP-capable TLV is sent when this timer + expires. The data type for intervalTimer is UScaledNs. + */ + struct ptp_u_scaled_ns interval_timer; + + /* + 10.4.1.1.2 txSignalingMsgPtr: A pointer to a structure whose members contain the values of the fields of a + Signaling message to be transmitted, which contains a gPTP-capable TLV (see 10.6.4.4). + */ + struct ptp_signaling_pdu *tx_signaling_msg_ptr; + + /* + 10.4.1.1.3 interval3: A local variable that holds either gPtpCapableMessageInterval + oldGptpCapableMessageInterval. The data type for interval3 is UScaledNs. + */ + struct ptp_u_scaled_ns interval3; + + /* + 10.4.1.1.4 numberGptpCapableMessageTransmissions: A count of the number of consecutive + transmissions of Signaling messages that contain a gPTP-capable TLV, after the GptpCapableIntervalSetting + state machine (see Figure 10-23 in 10.4.3.3) has set gPtpCapableMessageSlowdown (see 10.2.5.19) to + TRUE. The data type for numberGptpCapableMessageTransmissions is UInteger8. + */ + u8 number_gptp_capable_message_transmissions; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_gptp_capable_transmit_sm_state_t state; + + struct timer timeout_timer; + + struct ptp_signaling_pdu msg; +}; + + +typedef enum { + GPTP_CAPABLE_RECEIVE_SM_EVENT_MIN_VALUE = 0, + GPTP_CAPABLE_RECEIVE_SM_EVENT_RUN = GPTP_CAPABLE_RECEIVE_SM_EVENT_MIN_VALUE, + GPTP_CAPABLE_RECEIVE_SM_EVENT_TIMEOUT, + GPTP_CAPABLE_RECEIVE_SM_EVENT_MAX_VALUE = GPTP_CAPABLE_RECEIVE_SM_EVENT_TIMEOUT +} ptp_gptp_capable_receive_sm_event_t; + +typedef enum { + GPTP_CAPABLE_RECEIVE_SM_STATE_MIN_VALUE = 0, + GPTP_CAPABLE_RECEIVE_SM_STATE_NOT_ENABLED = GPTP_CAPABLE_RECEIVE_SM_STATE_MIN_VALUE, + GPTP_CAPABLE_RECEIVE_SM_STATE_INITIALIZE, + GPTP_CAPABLE_RECEIVE_SM_STATE_RECEIVED_TLV, + GPTP_CAPABLE_RECEIVE_SM_STATE_MAX_VALUE = GPTP_CAPABLE_RECEIVE_SM_STATE_RECEIVED_TLV +} ptp_gptp_capable_receive_sm_state_t; + +/* +* gPTPCapableReceive variables 802.1AS-2020 - 10.4.2 +*/ +struct ptp_gptp_capable_receive_sm { + /* + 10.4.2.1.1 rcvdGptpCapableTlv: A Boolean variable that notifies the current state machine when a + Signaling message containing a gPTP-capable TLV is received. This variable is reset by the current state + machine. + */ + bool rcvd_gptp_capable_tlv; + + /* + 10.4.2.1.2 rcvdSignalingMsgPtr: A pointer to a structure whose members contain the values of the fields of + a Signaling message whose receipt is indicated by rcvdGptpCapableTlv (see 10.4.2.1.1). + */ + struct ptp_signaling_pdu *rcvd_signaling_msg_ptr; + + /* + 10.4.2.1.3 gPtpCapableReceiptTimeoutTimeInterval: The time interval after which, if a Signaling + message containing a gPTP-capable TLV is not received, the neighbor of this PTP Port is considered to no + longer be invoking gPTP. The data type for gPtpCapableReceiptTimeoutTimeInterval is UScaledNs. + */ + struct ptp_u_scaled_ns gptp_capable_receipt_timeout_time_interval; + + /* + 10.4.2.1.4 timeoutTime: A variable used to save the time at which the neighbor of this PTP Port is + considered to no longer be invoking gPTP if a Signaling message containing a gPTP-capable TLV is not + received. The data type for timeoutTime is UScaledNs. + */ + struct ptp_u_scaled_ns timeout_time; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_gptp_capable_receive_sm_state_t state; + + struct timer timeout_timer; +}; + + +typedef enum { + GPTP_CAPABLE_INTERVAL_SM_STATE_MIN_VALUE = 0, + GPTP_CAPABLE_INTERVAL_SM_STATE_NOT_ENABLED = GPTP_CAPABLE_INTERVAL_SM_STATE_MIN_VALUE, + GPTP_CAPABLE_INTERVAL_SM_STATE_INITIALIZE, + GPTP_CAPABLE_INTERVAL_SM_STATE_SET_INTERVAL, + GPTP_CAPABLE_INTERVAL_SM_STATE_MAX_VALUE = GPTP_CAPABLE_INTERVAL_SM_STATE_SET_INTERVAL +} ptp_gptp_capable_interval_setting_sm_state_t; + +/* +* gPTPCapableIntervalSetting variables 802.1AS-2020 - 10.4.4 +*/ +struct ptp_gptp_capable_interval_setting_sm { + /* + 10.4.3.1.1 rcvdSignalingMsg4: A Boolean variable that notifies the current state machine when a Signaling + message that contains a gPTP-capable Message Interval Request TLV (see 10.6.4.5) is received. This + variable is reset by the current state machine. + */ + bool rcvd_signaling_msg4; + + /* + 10.4.3.1.2 rcvdSignalingPtrGIS: A pointer to a structure whose members contain the values of the fields of + the received Signaling message that contains a gPTP-capable Message Interval Request TLV (see 10.6.4.5). + */ + struct ptp_signaling_pdu *rcvd_signaling_ptr_gis; + + /* + 10.4.3.1.3 logSupportedGptpCapableMessageIntervalMax: The maximum supported logarithm to base 2 + of the gPTP-capable message interval. The data type for logSupportedGptpCapableMessageIntervalMax is + Integer8. + */ + s8 log_supported_gptp_capable_message_interval_max; + + /* + 10.4.3.1.4 logSupportedClosestLongerGptpCapableMessageInterval: The logarithm to base 2 of the + gPTP-capable message interval, such that logSupportedClosestLongerGptpCapableMessageInterval > + logRequestedGptpCapableMessageInterval,that is numerically closest to + logRequestedGptpCapableMessageInterval, where logRequestedGptpCapableMessageInterval is the + argument of the function computeLogGptpCapableMessageInterval() (see 10.4.3.2.2). The data type for + logSupportedClosestLongerGptpCapableMessageInterval is Integer8. + */ + s8 log_supported_closest_longer_gptp_capable_message_interval; + + /* + 10.4.3.1.5 computedLogGptpCapableMessageInterval: A variable used to hold the result of the function + computeLogGptpCapableMessageInterval(). The data type for computedLogGptpCapableMessageInterval + is Integer8. + */ + s8 computed_log_gptp_capable_message_interval; + + /* + Custom additions (not specified in standard but needed for some computations) + */ + ptp_gptp_capable_interval_setting_sm_state_t state; +}; + + +/* +A time-aware system contains one MD entity per port. This entity contains functions generic to all media +*/ +struct ptp_instance_md_entity { + struct ptp_instance_md_entity_globals globals; + struct ptp_md_entity_sync_receive_sm sync_rcv_sm; + struct ptp_md_entity_sync_send_sm sync_send_sm; + struct md_sync_receive sync_rcv; + struct md_sync_send sync_snd; +}; + +/* MD entity elements used by both Domain 0 instance and the CMLDS */ +struct ptp_md_entity { + struct ptp_md_entity_globals globals; + struct ptp_md_entity_pdelay_req_sm pdelay_req_sm; + struct ptp_md_entity_pdelay_resp_sm pdelay_resp_sm; + struct ptp_md_link_delay_interval_setting_sm link_interval_sm; +}; + + +typedef enum { + SYNC_RCV_SM_STATE_MIN_VALUE = 0, + SYNC_RCV_SM_STATE_DISCARD = SYNC_RCV_SM_STATE_MIN_VALUE, + SYNC_RCV_SM_STATE_WAITING_FOR_FOLLOW_UP, + SYNC_RCV_SM_STATE_WAITING_FOR_SYNC, + SYNC_RCV_SM_STATE_MAX_VALUE = SYNC_RCV_SM_STATE_WAITING_FOR_SYNC +} ptp_sync_rcv_sm_state_t; + +typedef enum { + SYNC_RCV_SM_EVENT_MIN_VALUE = 0, + SYNC_RCV_SM_EVENT_FUP_TIMEOUT = SYNC_RCV_SM_EVENT_MIN_VALUE, + SYNC_RCV_SM_EVENT_RUN, + SYNC_RCV_SM_EVENT_MAX_VALUE = SYNC_RCV_SM_EVENT_RUN +} ptp_sync_rcv_sm_event_t; + +typedef enum { + SYNC_SND_SM_STATE_MIN_VALUE = 0, + SYNC_SND_SM_STATE_INITIALIZING = SYNC_SND_SM_STATE_MIN_VALUE, + SYNC_SND_SM_STATE_SEND_SYNC, + SYNC_SND_SM_STATE_SEND_FOLLOW_UP, + SYNC_SND_SM_STATE_MAX_VALUE = SYNC_SND_SM_STATE_SEND_FOLLOW_UP +} ptp_sync_snd_sm_state_t; + + +typedef enum { + SYNC_SND_SM_EVENT_MIN_VALUE = 0, + SYNC_SND_SM_EVENT_RUN = SYNC_SND_SM_EVENT_MIN_VALUE, + SYNC_SND_SM_EVENT_MAX_VALUE = SYNC_SND_SM_EVENT_RUN, +} ptp_sync_snd_sm_event_t; + + +typedef enum { + LINK_INTERVAL_SM_STATE_MIN_VALUE = 0, + LINK_INTERVAL_SM_STATE_NOT_ENABLED = LINK_INTERVAL_SM_STATE_MIN_VALUE, + LINK_INTERVAL_SM_STATE_INITIALIZE, + LINK_INTERVAL_SM_STATE_SET_INTERVALS, + LINK_INTERVAL_SM_STATE_MAX_VALUE = LINK_INTERVAL_SM_STATE_SET_INTERVALS +} ptp_link_delay_interval_sm_state_t; + + +typedef enum { + LINK_INTERVAL_SM_EVENT_MIN_VALUE = 0, + LINK_INTERVAL_SM_EVENT_RCVD_MSG1 = LINK_INTERVAL_SM_EVENT_MIN_VALUE, + LINK_INTERVAL_SM_EVENT_RUN, + LINK_INTERVAL_SM_EVENT_MAX_VALUE = LINK_INTERVAL_SM_EVENT_RUN +} ptp_link_delay_interval_sm_event_t; + +typedef enum { + LINK_TRANSMIT_SM_STATE_INITIAL = 0, + LINK_TRANSMIT_SM_STATE_OPER +} ptp_link_delay_transmit_sm_state_t; + + +typedef enum { + LINK_TRANSMIT_SM_EVENT_INITIAL = 0, + LINK_TRANSMIT_SM_EVENT_OPER, + LINK_TRANSMIT_SM_EVENT_RATIO_NOT_VALID, + LINK_TRANSMIT_SM_EVENT_RATIO_VALID +} ptp_link_delay_transmit_sm_event_t; + + +/***************** LocalClock entity *********************/ + +/* +The LocalClock entity is a free-running clock (see 3.3) that provides a common time to the time-aware +system, relative to an arbitrary epoch +*/ +struct ptp_local_clock_entity { + /* Non standard */ + unsigned int phase_discont; /* Incremented for each offset adjustment of the local clock */ + ptp_double rate_ratio_adjustment; /* current rate ratio adjustment applied to the local clock */ +}; + + +/***************** Common Mean Link Delay Service *********************/ + +/* +commonServicesPortDS 802.1AS-2020 - 14.14 + +The commonServicesPortDS enables a PTP Port of a PTP Instance to determine which port of the respective +common service corresponds to that PTP Port. +*/ +struct ptp_common_services_port_ds { + /* The value is the portNumber attribute of the cmldsLinkPortDS.portIdentity + of the Link Port that corresponds to this PTP Port. */ + u16 cmlds_link_port_port_number; +}; + +/* +cmldsDefaultDS 802.1AS-2020 - 14.15 + +The cmldsDefaultDS describes the per-time-aware-system attributes of the Common Mean Link Delay +Services +*/ +struct ptp_cmlds_default_ds { + struct ptp_clock_identity clock_identity; + u16 number_link_ports; +}; + +/* +cmldsLinkPortDS 802.1AS-2020 - 14.16 + +The cmldsLinkPortDS represents time-aware Link Port capabilities for the Common Mean Link Delay +Service of a Link Port of a time-aware system +*/ +struct ptp_cmlds_link_port_ds { + struct ptp_port_identity port_identity; + + bool cmlds_link_port_enabled; + + bool is_measuring_delay; + + bool as_capable_across_domains; + + struct ptp_time_interval mean_link_delay; + struct ptp_time_interval mean_link_delay_thresh; + struct ptp_time_interval delay_asymmetry; + + s32 neighbor_rate_Ratio; + + s8 initial_log_pdelayreq_interval; + s8 current_log_pdelayreq_interval; + bool use_mgt_settable_log_pdelayreq_interval; + s8 mgt_settable_log_pdelayreq_interval; + + bool initial_compute_neighbor_rate_ratio; + bool current_compute_neighbor_rate_ratio; + bool use_mgt_settable_compute_neighbor_rate_ratio; + bool mgt_settable_compute_neighbor_rate_ratio; + + bool initial_compute_mean_link_delay; + bool current_compute_mean_link_delay; + bool use_mgt_settable_compute_mean_link_delay; + bool mgt_settable_compute_mean_link_delay; + + u8 allowed_lost_responses; + u8 allowed_faults; + + u8 version_number; + + struct ptp_timestamp pdelay_truncated_timestamps_array[4]; + + u8 minor_version_number; +}; + +/* +cmldsLinkPortStatisticsDS - 802.1AS-2020 - 14.17 + +There is one cmldsLinkPortStatisticsDS table per Link Port of a time-aware system. The +cmldsLinkPortStatisticsDS table contains a set of counters for each Link Port that supports the time- +synchronization capability +*/ +struct ptp_cmlds_link_port_statistics_ds { + u32 rx_pdelay_request_count; + u32 rx_pdelay_response_count; + u32 rx_pdelay_response_follow_up_count; + u32 rx_ptp_packet_discard_count; + u32 pdelay_allowed_lost_responses_exceeded_count; + u32 tx_pdelay_request_count; + u32 tx_pdelay_response_count; + u32 tx_pdelay_response_follow_up_count; +}; + +/* +cmldsAsymmetryMeasurementModeDS - 802.1AS-2020 - 14.18 + +For every Link Port of the Common Mean Link Delay Service of a time-aware system, the +cmldsAsymmetryMeasurementModeDS contains the single member asymmetryMeasurementMode, which +is used to enable/disable the Asymmetry Compensation Measurement Procedure +*/ +struct ptp_cmlds_asymmetry_measurement_mode_ds { + bool asymmetry_measurement_mode; +}; + +#endif /* _PTP_H_ */ diff --git a/common/ptp_time_ops.h b/common/ptp_time_ops.h new file mode 100644 index 0000000..b37b4ca --- /dev/null +++ b/common/ptp_time_ops.h @@ -0,0 +1,463 @@ +/* +* Copyright 2015 Freescale Semiconductor, Inc. +* Copyright 2016-2021, 2023 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + @file ptp_time_ops.h + @brief PTP Time operations common definitions +*/ + +#ifndef _PTP_TIME_OPS_H_ +#define _PTP_TIME_OPS_H_ + +#include "common/types.h" +#include "common/ptp.h" +#include "common/net.h" +#include "common/log.h" +#include "common/timer.h" +#include "os/clock.h" + +#define PTP_TS_UNSET_U64_VALUE 0xffffffffffffffffULL + +#define POW_2_80 (1.208925819614629e24) +#define POW_2_79 (6.044629098073146e23) +#define POW_2_64 (18446744073709551616.0) +#define POW_2_41 (2199023255552.0) +#define POW_2_32 (4294967296.0) +#define POW_2_16 (65536.0) + +#define PTP_TIMESTAMP_MAX_SECONDS_MSB 4 +static inline u64 pdu_ptp_timestamp_to_u64(struct ptp_timestamp ptp_ts) +{ + u64 nanoseconds = (u64)ntohl(ptp_ts.nanoseconds) + ((u64)ntohl(ptp_ts.seconds_lsb) + ((u64)ntohs(ptp_ts.seconds_msb) << 32))*NSECS_PER_SEC; + if (ntohs(ptp_ts.seconds_msb) >= PTP_TIMESTAMP_MAX_SECONDS_MSB) + os_log(LOG_ERR, "Likely overflow in PTP timestamp structure (seconds_msb>=4): seconds_msb = %u seconds_lsb = %u nanoseconds = %u\n", + ntohs(ptp_ts.seconds_msb), ntohl(ptp_ts.seconds_lsb), ntohl(ptp_ts.nanoseconds)); + + return nanoseconds; +} + +static inline void ntoh_ptp_timestamp(struct ptp_timestamp *h_ptp_ts, const struct ptp_timestamp *n_ptp_ts) +{ + h_ptp_ts->seconds_msb = ntohs(n_ptp_ts->seconds_msb); + h_ptp_ts->seconds_lsb = ntohl(n_ptp_ts->seconds_lsb); + h_ptp_ts->nanoseconds = ntohl(n_ptp_ts->nanoseconds); +} + +static inline void ntoh_scaled_ns(struct ptp_scaled_ns *h_scaled_ns, const struct ptp_scaled_ns *n_scaled_ns) +{ + h_scaled_ns->u.s.nanoseconds_msb = ntohs(n_scaled_ns->u.s.nanoseconds_msb); + h_scaled_ns->u.s.nanoseconds = ntohll(n_scaled_ns->u.s.nanoseconds); + h_scaled_ns->u.s.fractional_nanoseconds = ntohs(n_scaled_ns->u.s.fractional_nanoseconds); +} + +static inline void hton_scaled_ns(struct ptp_scaled_ns *n_scaled_ns, const struct ptp_scaled_ns *h_scaled_ns) +{ + n_scaled_ns->u.s.nanoseconds_msb = htons(h_scaled_ns->u.s.nanoseconds_msb); + n_scaled_ns->u.s.nanoseconds = htonll(h_scaled_ns->u.s.nanoseconds); + n_scaled_ns->u.s.fractional_nanoseconds = htons(h_scaled_ns->u.s.fractional_nanoseconds); +} + +static inline void scaled_ns_to_u_scaled_ns(struct ptp_u_scaled_ns *u_scaled_ns, const struct ptp_scaled_ns *scaled_ns) +{ + /* This is a cast from a 96bit signed integer to a 96bit unsigned integer, all bits (including sign bit) remain in the same place */ + u_scaled_ns->u.s.nanoseconds_msb = scaled_ns->u.s.nanoseconds_msb; + u_scaled_ns->u.s.nanoseconds = scaled_ns->u.s.nanoseconds; + u_scaled_ns->u.s.fractional_nanoseconds = scaled_ns->u.s.fractional_nanoseconds; +} + +static inline void u_scaled_ns_to_scaled_ns(struct ptp_scaled_ns *scaled_ns, const struct ptp_u_scaled_ns *u_scaled_ns) +{ + /* This is a cast from a 96bit unsigned integer to a 96bit signed integer, all bits (including sign bit) remain in the same place */ + scaled_ns->u.s.nanoseconds_msb = u_scaled_ns->u.s.nanoseconds_msb; + scaled_ns->u.s.nanoseconds = u_scaled_ns->u.s.nanoseconds; + scaled_ns->u.s.fractional_nanoseconds = u_scaled_ns->u.s.fractional_nanoseconds; +} + +/* Two's complement of a 96bit unsigned integer */ +static inline void minus_u_scaled_ns(struct ptp_u_scaled_ns *result, const struct ptp_u_scaled_ns *a) +{ + /* Two's complement */ + + /* One's complement */ + result->u.s.nanoseconds_msb = ~a->u.s.nanoseconds_msb; + result->u.s.nanoseconds = ~a->u.s.nanoseconds; + result->u.s.fractional_nanoseconds = ~a->u.s.fractional_nanoseconds; + + /* +1 */ + result->u.s.fractional_nanoseconds++; + if (result->u.s.fractional_nanoseconds < 1) { + /* Carry */ + result->u.s.nanoseconds++; + + if (result->u.s.nanoseconds < 1) + /* Carry */ + result->u.s.nanoseconds_msb++; + } +} + +static inline void u64_to_pdu_ptp_timestamp(struct ptp_timestamp *ts, u64 nanoseconds) +{ + u64 seconds; + + seconds = nanoseconds / NSECS_PER_SEC; + ts->seconds_msb = htons(seconds >> 32); + ts->seconds_lsb = htonl(seconds & 0xffffffff); + ts->nanoseconds = htonl(nanoseconds % NSECS_PER_SEC); +} + +static inline void u64_to_u_scaled_ns(struct ptp_u_scaled_ns *u_scaled_ns, u64 u64_ts_ns) +{ + u_scaled_ns->u.s.nanoseconds_msb = 0; // FIXME ignored, see u_scaled_ns structure definition + u_scaled_ns->u.s.nanoseconds = u64_ts_ns; + u_scaled_ns->u.s.fractional_nanoseconds = 0; +} + +/* This function truncates fractional nanoseconds */ +static inline void ptp_extended_timestamp_to_ptp_timestamp(struct ptp_timestamp *ptp_ts, const struct ptp_extended_timestamp *ptp_ext_ts) +{ + ptp_ts->seconds_msb = ptp_ext_ts->seconds_msb; + ptp_ts->seconds_lsb = ptp_ext_ts->seconds_lsb; + ptp_ts->nanoseconds = ptp_ext_ts->fractional_nanoseconds_msb; +} + +static inline void ptp_timestamp_to_ptp_extended_timestamp(struct ptp_extended_timestamp *ptp_ext_ts, const struct ptp_timestamp *ptp_ts) +{ + ptp_ext_ts->seconds_msb = ptp_ts->seconds_msb; + ptp_ext_ts->seconds_lsb = ptp_ts->seconds_lsb; + ptp_ext_ts->fractional_nanoseconds_msb = ptp_ts->nanoseconds; + ptp_ext_ts->fractional_nanoseconds_lsb = 0; +} + +static inline void ptp_extended_timestamp_to_u_scaled_ns(struct ptp_u_scaled_ns *u_scaled_ns, const struct ptp_extended_timestamp *ptp_ext_ts) +{ + u64 tmp, tmp1; + + tmp = (u64)ptp_ext_ts->seconds_msb * NSECS_PER_SEC; /* nanoseconds >> 32 units */ + + u_scaled_ns->u.s.nanoseconds_msb = tmp >> 32; + + tmp1 = ptp_ext_ts->fractional_nanoseconds_msb + (u64)ptp_ext_ts->seconds_lsb * NSECS_PER_SEC; + if (tmp1 < ptp_ext_ts->fractional_nanoseconds_msb) + u_scaled_ns->u.s.nanoseconds_msb++; + + u_scaled_ns->u.s.nanoseconds = tmp1 + ((tmp & 0xffffffff) << 32); + if (u_scaled_ns->u.s.nanoseconds < tmp1) + u_scaled_ns->u.s.nanoseconds_msb++; + + u_scaled_ns->u.s.fractional_nanoseconds = ptp_ext_ts->fractional_nanoseconds_lsb; +} + +static inline void u_scaled_ns_to_ptp_extended_timestamp(struct ptp_extended_timestamp *ptp_ext_ts, const struct ptp_u_scaled_ns *u_scaled_ns) +{ + u16 seconds_msb; + u64 seconds; + u32 nanoseconds; + + seconds_msb = ((u64)u_scaled_ns->u.s.nanoseconds_msb << 32) / NSECS_PER_SEC; /* seconds >> 32 units, remainder 0*/ + + seconds = u_scaled_ns->u.s.nanoseconds / NSECS_PER_SEC; + nanoseconds = u_scaled_ns->u.s.nanoseconds - seconds * NSECS_PER_SEC; + + ptp_ext_ts->seconds_msb = (seconds >> 32) + seconds_msb; + ptp_ext_ts->seconds_lsb = seconds & 0xffffffff; + ptp_ext_ts->fractional_nanoseconds_msb = nanoseconds; + ptp_ext_ts->fractional_nanoseconds_lsb = u_scaled_ns->u.s.fractional_nanoseconds; +} + +static inline void u64_to_ptp_extended_timestamp(struct ptp_extended_timestamp *ptp_ext_ts, u64 u64_ts_ns) +{ + struct ptp_u_scaled_ns u_scaled_ns; + + u64_to_u_scaled_ns(&u_scaled_ns, u64_ts_ns); + u_scaled_ns_to_ptp_extended_timestamp(ptp_ext_ts, &u_scaled_ns); +} + +static inline void ptp_timestamp_to_u_scaled_ns(struct ptp_u_scaled_ns *u_scaled_ns, const struct ptp_timestamp *ptp_ts) +{ + struct ptp_extended_timestamp ptp_ext_ts; + + ptp_timestamp_to_ptp_extended_timestamp(&ptp_ext_ts, ptp_ts); + + ptp_extended_timestamp_to_u_scaled_ns(u_scaled_ns, &ptp_ext_ts); +} + + +static inline void hton_ptp_timestamp(struct ptp_timestamp *pdu_ptp_ts, const struct ptp_timestamp *ptp_ts) +{ + pdu_ptp_ts->seconds_msb = htons(ptp_ts->seconds_msb); + pdu_ptp_ts->seconds_lsb = htonl(ptp_ts->seconds_lsb); + pdu_ptp_ts->nanoseconds = htonl(ptp_ts->nanoseconds); +} + +static inline void ptp_double_to_u_scaled_ns(struct ptp_u_scaled_ns *u_scaled_ns, ptp_double double_ts_ns) +{ + ptp_double nanoseconds; + + u_scaled_ns->u.s.nanoseconds_msb = 0; + u_scaled_ns->u.s.nanoseconds = 0; + u_scaled_ns->u.s.fractional_nanoseconds = 0; + + if (double_ts_ns < 0) { + os_log(LOG_ERR, "Trying to convert a negative value (%10.3f) to u_scaled_ns type, returning 0.\n", double_ts_ns); + return; + } + + if (double_ts_ns > POW_2_80) + os_log(LOG_ERR, "Conversion overflow %f\n", double_ts_ns); + + u_scaled_ns->u.s.nanoseconds_msb = double_ts_ns / POW_2_64; + nanoseconds = double_ts_ns - u_scaled_ns->u.s.nanoseconds_msb * POW_2_64; + u_scaled_ns->u.s.nanoseconds = nanoseconds; + u_scaled_ns->u.s.fractional_nanoseconds = (nanoseconds - u_scaled_ns->u.s.nanoseconds)*(1 << 16); +} + +static inline void ptp_double_to_scaled_ns(struct ptp_scaled_ns *scaled_ns, ptp_double double_ts_ns) +{ + struct ptp_u_scaled_ns tmp; + unsigned int negative = 0; + + if (double_ts_ns > POW_2_79) + os_log(LOG_ERR, "Conversion overflow %f\n", double_ts_ns); + + if (double_ts_ns < 0.0) { + negative = 1; + double_ts_ns = -double_ts_ns; + } + + ptp_double_to_u_scaled_ns(&tmp, double_ts_ns); + + if (negative) + minus_u_scaled_ns(&tmp, &tmp); + + u_scaled_ns_to_scaled_ns(scaled_ns, &tmp); +} + + +static inline void ptp_double_to_ptp_extended_timestamp(struct ptp_extended_timestamp *ptp_ext_ts, ptp_double double_ts_ns) +{ + struct ptp_u_scaled_ns u_scaled_ns; + + ptp_double_to_u_scaled_ns(&u_scaled_ns, double_ts_ns); + u_scaled_ns_to_ptp_extended_timestamp(ptp_ext_ts, &u_scaled_ns); +} + + +static inline void pdu_correction_field_to_scaled_ns(struct ptp_scaled_ns *scaled_ns, s64 correction_field) +{ + struct ptp_u_scaled_ns u_scaled_ns; + s64 correction_field_host = ntohll(correction_field); /* the sign bit position it's correct after endianess conversion*/ + unsigned int negative = 0; + + if (correction_field_host < 0) { + negative = 1; + correction_field_host = -correction_field_host; + } + + u_scaled_ns.u.s.nanoseconds_msb = 0; + u_scaled_ns.u.s.nanoseconds = (correction_field_host >> 16) & 0xffffffffffffffff; + u_scaled_ns.u.s.fractional_nanoseconds = correction_field_host & 0xffff; + + if (negative) + minus_u_scaled_ns(&u_scaled_ns, &u_scaled_ns); + + u_scaled_ns_to_scaled_ns(scaled_ns, &u_scaled_ns); +} + +static inline void scaled_ns_to_pdu_correction_field(s64 *correction_field, struct ptp_scaled_ns *scaled_ns) +{ + struct ptp_u_scaled_ns u_scaled_ns; + s64 correction_field_host; + unsigned int negative = 0; + + scaled_ns_to_u_scaled_ns(&u_scaled_ns, scaled_ns); + + if (scaled_ns->u.s.nanoseconds_msb & 0x8000) { /* ptp_scaled_ns struct has unsigned fields */ + negative = 1; + minus_u_scaled_ns(&u_scaled_ns, &u_scaled_ns); + } + + correction_field_host = u_scaled_ns.u.s.fractional_nanoseconds + (u_scaled_ns.u.s.nanoseconds << 16); + + if (negative) + correction_field_host = -correction_field_host; + + *correction_field = htonll(correction_field_host); +} + + +/* Unsigned integer overflow: drop higher bits */ +/* Unsigned integer negation: -a = ~a + 1 (two's complement) */ +/* Unsigned integer subtraction: a - b = a + (-b) */ + +/* Adds two 96bit unsigned integers with correct overflow handling */ +static inline void u_scaled_ns_add(struct ptp_u_scaled_ns *result, const struct ptp_u_scaled_ns *a, const struct ptp_u_scaled_ns *b) +{ + struct ptp_u_scaled_ns tmp; + + tmp.u.s.fractional_nanoseconds = a->u.s.fractional_nanoseconds + b->u.s.fractional_nanoseconds; + tmp.u.s.nanoseconds = a->u.s.nanoseconds + b->u.s.nanoseconds; + tmp.u.s.nanoseconds_msb = a->u.s.nanoseconds_msb + b->u.s.nanoseconds_msb; + + if (tmp.u.s.nanoseconds < a->u.s.nanoseconds) + tmp.u.s.nanoseconds_msb++; + + if (tmp.u.s.fractional_nanoseconds < a->u.s.fractional_nanoseconds) { + tmp.u.s.nanoseconds++; + if (tmp.u.s.nanoseconds < 1) + tmp.u.s.nanoseconds_msb++; + } + + *result = tmp; +} + +/* Subtracts two 96bit unsigned integers with correct support for "negative" values */ +static inline void u_scaled_ns_sub(struct ptp_u_scaled_ns *result, const struct ptp_u_scaled_ns *a, const struct ptp_u_scaled_ns *b) +{ + struct ptp_u_scaled_ns tmp; + + minus_u_scaled_ns(&tmp, b); + u_scaled_ns_add(result, a, &tmp); +} + +/* This function does rounding as well */ +static inline void u_scaled_ns_to_u64(u64 *u64_ts_ns, const struct ptp_u_scaled_ns *u_scaled_ns) +{ + if (u_scaled_ns->u.s.nanoseconds_msb) + os_log(LOG_ERR, "Conversion overflow %u.%"PRIu64".%u\n", + u_scaled_ns->u.s.nanoseconds_msb, u_scaled_ns->u.s.nanoseconds, u_scaled_ns->u.s.fractional_nanoseconds); + + *u64_ts_ns = u_scaled_ns->u.s.nanoseconds; + if (u_scaled_ns->u.s.fractional_nanoseconds >= 32768) + *u64_ts_ns += 1; +} + +/* This function may loose precision */ +static inline void u_scaled_ns_to_ptp_double( ptp_double *double_ts_ns, const struct ptp_u_scaled_ns *u_scaled_ns) +{ + *double_ts_ns = ((ptp_double)u_scaled_ns->u.s.nanoseconds_msb * POW_2_64) + u_scaled_ns->u.s.nanoseconds + ((ptp_double)u_scaled_ns->u.s.fractional_nanoseconds / POW_2_16); + + if (u_scaled_ns->u.s.nanoseconds_msb || (u_scaled_ns->u.s.nanoseconds > (1ULL << 53))) + os_log(LOG_ERR, "Possible precision truncation %u.%"PRIu64".%u != %f\n", + u_scaled_ns->u.s.nanoseconds_msb, u_scaled_ns->u.s.nanoseconds, u_scaled_ns->u.s.fractional_nanoseconds, *double_ts_ns); +} + +/* This function compares two ptp_scaled_ns values a and b. It returns an interger +less than, equal to, or greater than 0 if a is found respectively, to be less, to match, or be greater +than b */ +static inline int u_scaled_ns_cmp(const struct ptp_u_scaled_ns *a, const struct ptp_u_scaled_ns *b) +{ + if (a->u.s.nanoseconds_msb < b->u.s.nanoseconds_msb) + return -1; + if (a->u.s.nanoseconds_msb > b->u.s.nanoseconds_msb) + return 1; + + if (a->u.s.nanoseconds < b->u.s.nanoseconds) + return -1; + if (a->u.s.nanoseconds > b->u.s.nanoseconds) + return 1; + + if (a->u.s.fractional_nanoseconds < b->u.s.fractional_nanoseconds) + return -1; + if (a->u.s.fractional_nanoseconds > b->u.s.fractional_nanoseconds) + return 1; + + return 0; +} + +/* This function may loose precision */ +static inline void scaled_ns_to_ptp_double( ptp_double *double_ts_ns, const struct ptp_scaled_ns *scaled_ns) +{ + struct ptp_u_scaled_ns tmp; + unsigned int negative = 0; + + scaled_ns_to_u_scaled_ns(&tmp, scaled_ns); + + if (scaled_ns->u.s.nanoseconds_msb & 0x8000) { + negative = 1; + minus_u_scaled_ns(&tmp, &tmp); + } + + u_scaled_ns_to_ptp_double(double_ts_ns, &tmp); + + if (negative) + *double_ts_ns = -*double_ts_ns; +} + +/* This function may loose precision */ +static inline void ptp_timestamp_to_ptp_double_ns(ptp_double *double_ts_ns, struct ptp_timestamp ptp_ts) +{ + *double_ts_ns = ((ptp_double)ptp_ts.seconds_msb * POW_2_32 * NSECS_PER_SEC) + ptp_ts.seconds_lsb * NSECS_PER_SEC + ptp_ts.nanoseconds; + + if (*double_ts_ns > (1ULL << 53)) + os_log(LOG_ERR, "Possible precision truncation %u.%u.%u != %f\n", + ptp_ts.seconds_msb, ptp_ts.seconds_lsb, ptp_ts.nanoseconds, *double_ts_ns); +} + +static inline void scaled_ns_add(struct ptp_scaled_ns *result, struct ptp_scaled_ns a, struct ptp_scaled_ns b) +{ + result->u.s.nanoseconds_msb = a.u.s.nanoseconds_msb + b.u.s.nanoseconds_msb; + + result->u.s.fractional_nanoseconds = a.u.s.fractional_nanoseconds + b.u.s.fractional_nanoseconds; + + result->u.s.nanoseconds = a.u.s.nanoseconds + b.u.s.nanoseconds; + if (result->u.s.fractional_nanoseconds < a.u.s.fractional_nanoseconds) + result->u.s.nanoseconds += 1; +} + + +static inline void double_scaled_ns_mul(struct ptp_scaled_ns *result, ptp_double a, struct ptp_scaled_ns b) +{ + ptp_double tmp, carry = 0.0; + + tmp = a * b.u.s.fractional_nanoseconds; + if (tmp > (1 << 16)) { + carry = tmp - (1 << 16); + tmp -= carry; + } else { + result->u.s.fractional_nanoseconds = a * b.u.s.fractional_nanoseconds; + } + + // FIXME ignored, see u_scaled_ns structure definition + /*tmp = a * b.u.s.nanoseconds + carry; + if (tmp > (1 << 16)) { + + } else { + + }*/ + + result->u.s.nanoseconds_msb = a * b.u.s.nanoseconds_msb; + result->u.s.nanoseconds = a * b.u.s.nanoseconds; + +} + +static inline u64 log_to_ns (signed char log_val) +{ + u64 ns; + + /* make sure log interval won't be out of bounds */ + if (log_val > CFG_GPTP_MAX_LOG_INTERVAL) { + os_log(LOG_DEBUG, "log value %u out of bounds, replaced by %u\n", log_val, CFG_GPTP_MAX_LOG_INTERVAL); + log_val = CFG_GPTP_MAX_LOG_INTERVAL; + } else if (log_val < CFG_GPTP_MIN_LOG_INTERVAL) { + os_log(LOG_DEBUG, "log value %u out of bounds, replaced by %u\n", log_val, CFG_GPTP_MIN_LOG_INTERVAL); + log_val = CFG_GPTP_MIN_LOG_INTERVAL; + } + + if (log_val < 0) + ns = NSECS_PER_SEC / (1 << -log_val); + else + ns = (u64)NSECS_PER_SEC * (1 << log_val); + + return ns; +} + +static inline u32 log_to_ms (signed char log_val) +{ + return (u32)(log_to_ns(log_val) / NS_PER_MS); +} + +#endif /* _PTP_TIME_OPS_H_ */ diff --git a/common/random.c b/common/random.c new file mode 100644 index 0000000..93be346 --- /dev/null +++ b/common/random.c @@ -0,0 +1,26 @@ +/* +* Copyright 2021, 2023 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + @file + @brief Generic random functions + @details +*/ + +#include "random.h" +#include "os/stdlib.h" + +/** + * Random helper function + * \return a random number in [min : max] (min and max included) + * \param min + * \param max + */ +long int random_range(long int min, long int max) +{ + /* rand between MIN and MAX => (rand % (MAX + 1 - MIN)) + MIN */ + return ((os_random() % (max + 1 - min)) + min); +} diff --git a/common/random.h b/common/random.h new file mode 100644 index 0000000..83be87c --- /dev/null +++ b/common/random.h @@ -0,0 +1,17 @@ +/* +* Copyright 2021, 2023 NXP +* +* SPDX-License-Identifier: BSD-3-Clause +*/ + +/** + @file + @brief Generic random functions + @details +*/ +#ifndef _COMMON_RANDOM_H_ +#define _COMMON_RANDOM_H_ + +long int random_range(long int min, long int max); + +#endif /* _COMMON_RANDOM_H_ */ diff --git a/common/srp.c b/common/srp.c new file mode 100644 index 0000000..50eff55 --- /dev/null +++ b/common/srp.c @@ -0,0 +1,53 @@ +/* + * Copyright 2020-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file srp.c + @brief SRP protocol common definitions + @details PDU and protocol definitions for all SRP applications +*/ + +#include "genavb/ether.h" + +#include "common/srp.h" +#include "os/clock.h" + + +/** Returns total frame size for the specific MAC and physical layer (802.1Q-2018, section 34.4) +* For now assumes 802.3 ethernet +* \return total frame size +* \param logical_port logical port id +* \param frame_size frame size without any MAC framing +*/ +static int mac_frame_size(unsigned int logical_port, unsigned int frame_size) +{ + unsigned int ether_frame_size; + + ether_frame_size = sizeof(struct eth_hdr) + frame_size + ETHER_FCS; + + if (ether_frame_size < ETHER_MIN_FRAME_SIZE) + ether_frame_size = ETHER_MIN_FRAME_SIZE; + + return ETHER_IFG + ETHER_PREAMBLE + ether_frame_size; +} + +/** Calculates idle slope given a tspec (802.1Q-2018, section 35.2.4.2) +* \return calculated idle slope +* \param logical_port logical port id +* \param sr_class SR class +* \param max_frame_size max_frame_size (in bytes) +* \param max_interval_frames max_interval_frames +*/ +int srp_tspec_to_idle_slope(unsigned int logical_port, sr_class_t sr_class, unsigned int max_frame_size, unsigned int max_interval_frames) +{ + unsigned int max_frame_rate, idle_slope; + + max_frame_rate = ((uint64_t)max_interval_frames * sr_class_interval_q(sr_class) * NSECS_PER_SEC) / sr_class_interval_p(sr_class); + + idle_slope = (mac_frame_size(logical_port, max_frame_size + sizeof(struct vlanhdr) + 1)) * 8 * max_frame_rate; + + return idle_slope; +} diff --git a/common/srp.h b/common/srp.h new file mode 100644 index 0000000..b87e1e3 --- /dev/null +++ b/common/srp.h @@ -0,0 +1,253 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file srp.h + @brief SRP protocol common definitions + @details PDU and protocol definitions for all SRP applications +*/ + + +#ifndef _PROTO_SRP_H_ +#define _PROTO_SRP_H_ + +#include "genavb/srp.h" +#include "genavb/sr_class.h" +#include "common/types.h" + + +/* +* +* MRP common header (802.1Qat -2010 - 10.8.1.2) +* +*/ + +struct __attribute__ ((packed)) mvrp_pdu_header { + u8 attribute_type; /**< packet type such as (MSRP) Domain, Talker Advertise or (MVRP) VLAN, etc...*/ + u8 attribute_length; /**< length of the first value fields */ +}; + +struct __attribute__ ((packed)) mrp_pdu_header { + u8 attribute_type; /**< packet type such as (MSRP) Domain, Talker Advertise or (MVRP) VLAN, etc...*/ + u8 attribute_length; /**< length of the first value fields */ + u16 attribute_list_length; +}; + +/** + * MRP default Timer values from 802.1Q-2011, Table 10-7 + */ + +#define MRP_JOINTIMER_VAL 200 /**< join timeout in msec */ +#define MRP_JOINTIMER_POINT_TO_POINT_VAL 100 /**< maximum 3 transmit opportunities in 1.5xJoinTime */ +#define MRP_LVTIMER_VAL 1000 /**< leave timeout in msec */ +#define MRP_LVATIMER_VAL_MIN 10000 /**< Min leaveall timeout in msec (LeaveAllTime per 802.1Q-2018 10.7.4.3) */ +#define MRP_LVATIMER_VAL_MAX 15000 /**< Max leaveall timeout in msec (1.5 x LeaveAllTime per 802.1Q-2018 10.7.4.3) */ +#define MRP_PERIODTIMER_VAL 1000 /**< periodic timeout in msec */ + +/* +* +* MSRP +* +*/ + + +/** +* MSRP PDU Direction (802.1Qat 35.2.1.2) +*/ +#define MSRP_DIRECTION_TALKER 0x00 +#define MSRP_DIRECTION_LISTENER 0x01 + + +/** +* MSRP PDU Protocol Version (802.1Qat 35.2.2.3) +*/ +#define MSRP_PROTO_VERSION 0x00 + +/** +* MSRP PDU Attribute Type definitions (802.1Qat 35.2.2.4) +*/ +typedef enum +{ + MSRP_ATTR_TYPE_TALKER_ADVERTISE = 1, + MSRP_ATTR_TYPE_TALKER_FAILED, + MSRP_ATTR_TYPE_LISTENER, + MSRP_ATTR_TYPE_DOMAIN, + MSRP_ATTR_TYPE_MAX +} msrp_attribute_type_t; + + +/** +* MSRP Declaration Type definitions (802.1Qat 35.2.1.3) +*/ +typedef enum +{ + MSRP_TALKER_DECLARATION_TYPE_ADVERTISE = 0, + MSRP_TALKER_DECLARATION_TYPE_FAILED, + MSRP_TALKER_DECLARATION_TYPE_NONE = 0xffff +} msrp_talker_declaration_type_t; + +typedef enum +{ + /* (802.1Qat 35.1.2.2) */ + MSRP_LISTENER_DECLARATION_TYPE_ASKING_FAILED = 1, /**< none of the listeners can receive the stream due to bandwidth or network problems */ + MSRP_LISTENER_DECLARATION_TYPE_READY, /**< there is enough bandwidth for all listeners to receive the stream */ + MSRP_LISTENER_DECLARATION_TYPE_READY_FAILED, /**< at least enough bandwidth for one listener to receive the stream */ + MSRP_LISTENER_DECLARATION_TYPE_NONE = 0xffff +} msrp_listener_declaration_type_t; + + +/** +* MSRP PDU Attribute Length values (802.1Qat 35.2.2.5) +*/ +#define MSRP_ATTR_LEN_TALKER_ADVERTISE 0x19 /* 25 */ +#define MSRP_ATTR_LEN_TALKER_FAILED 0x22 /* 34 */ +#define MSRP_ATTR_LEN_LISTENER 0x08 /* 8 */ +#define MSRP_ATTR_LEN_DOMAIN 0x04 /* 4 */ + +/** +* MSRP PDU Four Packed Events values (802.1Qat 35.2.2.7) +*/ +typedef enum +{ + MSRP_FOUR_PACKED_IGNORE = 0, + MSRP_FOUR_PACKED_ASKING_FAILED, + MSRP_FOUR_PACKED_READY, + MSRP_FOUR_PACKED_READY_FAILED, + MSRP_FOUR_PACKED_MAX +} msrp_four_packed_event_t; + +struct msrp_data_frame { + u8 destination_address[6]; + u16 vlan_identifier; +}; + +struct msrp_tspec { + u16 max_frame_size; + u16 max_interval_frames; +}; + +/** +* MSRP PDU Listener First Value definition (802.1Qat 35.2.2.8.1) +*/ +struct __attribute__ ((packed)) msrp_pdu_fv_listener { + u64 stream_id; +}; + + +/** +* MSRP PDU Talker Advertise First Value definition (802.1Qat 35.2.2.8.1) +*/ +struct __attribute__ ((packed)) msrp_pdu_fv_talker_advertise { + u64 stream_id; + struct msrp_data_frame data_frame; + struct msrp_tspec tspec; +#ifdef __BIG_ENDIAN__ + u8 priority:3; + u8 rank:1; + u8 reserved:4; +#else + u8 reserved:4; + u8 rank:1; + u8 priority:3; +#endif + u32 accumulated_latency; +}; + + +/** +* MSRP PDU Talker Advertise Failed First Value definition (802.1Qat 35.2.2.8.1) +*/ +struct __attribute__ ((packed)) msrp_pdu_fv_talker_failed { + u64 stream_id; + struct msrp_data_frame data_frame; + struct msrp_tspec tspec; +#ifdef __BIG_ENDIAN__ + u8 priority:3; + u8 rank:1; + u8 reserved:4; +#else + u8 reserved:4; + u8 rank:1; + u8 priority:3; +#endif + u32 accumulated_latency; + struct msrp_failure_information failure_info; +}; + + +/** +* MSRP PDU Domain Discovery definition (802.1Qat 35.2.2.9.1) +*/ +struct __attribute__ ((packed)) msrp_pdu_fv_domain { + u8 sr_class_id; + u8 sr_class_priority; + u16 sr_class_vid; +}; + + +/* +* +* MVRP +* +*/ + +#define MVRP_PROTO_VERSION 0x00 + +/** + * MVRP declaration type + */ +#define MVRP_ATTR_TYPE_VID 0x01 + +/** + *MVRP attribute length + * + */ +#define MVRP_ATTR_LEN 0x02 + +struct __attribute__ ((packed)) mvrp_pdu_fv { + u16 vid; /**< vlan ID value */ +}; + +/** +* MVRP PDU for the case where "Leave all" is not signaled. +*/ +struct __attribute__ ((packed)) mvrp_pdu_no_leave_all { + u8 attribute_type; /**< declaration type, should always be 1 */ + u8 attribute_length; /**< always 2 bytes for MVRP */ + u16 vector_header; /**< vector = leaveall only, leaveall + vector, vector only, value= number of value always 1 for MVRP*/ + struct mvrp_pdu_fv fv; /**< vlan ID value */ + u8 attribute_event; /**< NEW | JOININ | IN | JOINMT | MT | LV*/ + u16 end_mark; /**< always 0x0000 */ +}; + +/** +* MVRP PDU for the case where "Leave all" is signaled. +*/ +struct __attribute__ ((packed)) mvrp_pdu_leave_all { + u8 attribute_type; + u8 attribute_length; + u16 vector_header; + struct mvrp_pdu_fv fv; + u16 end_mark; +}; + + +/* +* +* MMRP +* +*/ + + +/** +* MMRP PDU Protocol Version +*/ +#define MMRP_PROTO_VERSION 0x00 + +int srp_tspec_to_idle_slope(unsigned int logical_port, sr_class_t sr_class, unsigned int max_frame_size, unsigned int max_interval_frames); + +#endif /* _PROTO_SRP_H_ */ diff --git a/common/stats.c b/common/stats.c new file mode 100644 index 0000000..e1d1681 --- /dev/null +++ b/common/stats.c @@ -0,0 +1,109 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Statistics service implementation + @details +*/ + +#include "stats.h" +#include "common/log.h" + +void stats_reset(struct stats *s) +{ + s->current_count = 0; + s->current_min = 0x7fffffff; + s->current_mean = 0; + s->current_max = -0x7fffffff; + s->current_ms = 0; +} + +/** Example function to be passed to stats_init + * This function expects the priv field to be a character string. + * Usage: + * stats_init(s, log2_size, "your string", print_stats); + */ +void stats_print(struct stats *s) +{ + os_log(LOG_INFO, "stats(%p) %s min %d mean %d max %d rms^2 %"PRIu64" stddev^2 %"PRIu64"\n", s, (char *)s->priv, s->min, s->mean, s->max, s->ms, s->variance); +} + + +/** Update stats with a given sample. + * @s: handler for the stats being monitored + * @val: sample to be added + * + * This function adds a sample to the set. + * If the number of accumulated samples is equal to the requested size of the set, the following indicators will be computed: + * . minimum observed value, + * . maximum observed value, + * . mean value, + * . square of the RMS (i.e. mean of the squares) + * . square of the standard deviation (i.e. variance) + */ +void stats_update(struct stats *s, s32 val) +{ + s->current_count++; + + s->current_mean += val; + s->current_ms += (s64)val * val; + + if (val < s->current_min) { + s->current_min = val; + if (val < s->abs_min) + s->abs_min = val; + } + + if (val > s->current_max) { + s->current_max = val; + if (val > s->abs_max) + s->abs_max = val; + } + + + if (s->current_count == (1U << s->log2_size)) { + s->ms = s->current_ms >> s->log2_size; + s->variance = s->ms - ((s->current_mean * s->current_mean) >> (2*s->log2_size)); + s->mean = s->current_mean >> s->log2_size; + + s->min = s->current_min; + s->max = s->current_max; + + if (s->func) + s->func(s); + + stats_reset(s); + } +} + + +/** Compute current stats event if set size hasn't been reached yet. + * @s: handler for the stats being monitored + * + * This function computes current statistics for the stats: + * . minimum observed value, + * . maximum observed value, + * . mean value, + * . square of the RMS (i.e. mean of the squares) + * . square of the standard deviation (i.e. variance) + */ +void stats_compute(struct stats *s) +{ + if (s->current_count) { + s->ms = s->current_ms / s->current_count; + s->variance = s->ms - (s->current_mean * s->current_mean) / ((s64)s->current_count * s->current_count); + s->mean = s->current_mean / s->current_count; + } else { + s->mean = 0; + s->ms = 0; + s->variance = 0; + } + + s->min = s->current_min; + s->max = s->current_max; +} diff --git a/common/stats.h b/common/stats.h new file mode 100644 index 0000000..65ade0e --- /dev/null +++ b/common/stats.h @@ -0,0 +1,69 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Statistics service implementation + @details +*/ + +#ifndef _COMMON_STATS_H_ +#define _COMMON_STATS_H_ + +#include "common/types.h" + +struct stats { + u32 log2_size; + u32 current_count; + + s32 current_min; + s32 current_max; + s64 current_mean; + u64 current_ms; + + /* Stats snapshot */ + s32 min; + s32 max; + s32 mean; + u64 ms; + u64 variance; + + /* absolute min/max (never reset) */ + s32 abs_min; + s32 abs_max; + + void *priv; + void (*func)(struct stats *s); +}; + +void stats_reset(struct stats *s); +void stats_print(struct stats *s); +void stats_update(struct stats *s, s32 val); +void stats_compute(struct stats *s); + + +/** Initialize a stats structure. + * @s: Pointer to structure to be initialized + * @log2_size: Set size to be reached before statistics are computed, expressed as a power of 2 + * @priv: private field for use by func + * @func: pointer to the function to be called when stats are computed + * + */ +static inline void stats_init(struct stats *s, unsigned int log2_size, void *priv, void (*func)(struct stats *s)) +{ + s->log2_size = log2_size; + s->priv = priv; + s->func = func; + + s->abs_min = 0x7fffffff; + s->abs_max = -0x7fffffff; + + stats_reset(s); +} + + +#endif /* _COMMON_STATS_H_ */ diff --git a/common/timer.c b/common/timer.c new file mode 100644 index 0000000..2720328 --- /dev/null +++ b/common/timer.c @@ -0,0 +1,356 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016, 2018-2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Timer Service implementation + @details +*/ + +#include "common/list.h" +#include "common/timer.h" +#include "common/log.h" + +#include "os/string.h" +#include "os/timer.h" +#include "os/clock.h" + +static void timer_process(struct os_timer *os_timer, int count) +{ + struct timer_sys *timer_sys = container_of(os_timer, struct timer_sys, os_timer); + struct list_head *entry; + struct timer *t; + + /* Go through all the children of this system timer and call corresponding callback. */ + /* If the timer callback removes the next entry from under our nose, timer_stop will make sure next_to_process still contains the correct next entry. */ + for (entry = list_first(&timer_sys->head); timer_sys->next_to_process = list_next(entry), entry != &timer_sys->head; entry = timer_sys->next_to_process) { + + t = container_of(entry, struct timer, list); + + t->ms -= timer_sys->ms * count; + + if (t->ms <= 0) { + os_log(LOG_DEBUG, "timer(%p) expired\n", t); + + /* By default timers are one shot */ + timer_stop(t); + + if (t->func) + t->func(t->data); + } + } +} + + +static int timer_match_timeout(int sys_ms, int ms) +{ + int match = 0; + + /* only support full matching timeout value for now */ + if (sys_ms == ms) + match = 1; + + return match; +} + +static struct timer_sys *timer_sys_find(struct timer_ctx *tctx, unsigned int ms) +{ + int i; + struct timer_sys *timer_sys; + + /* walk through the system timer table and look for one matching the requested timeout */ + for (i = 0; i < tctx->max_sys_timers; i++) { + + timer_sys = &tctx->timer_sys_table[i]; + + if ((timer_sys->flags & TIMER_STATE_CREATED) && !(timer_sys->flags & TIMER_TYPE_SYS)) { + + if (timer_match_timeout(timer_sys->ms, ms)) + return timer_sys; + } + } + + return NULL; +} + +static struct timer_sys *timer_sys_create(struct timer_ctx *tctx, unsigned int flags, unsigned int ms) +{ + struct timer_sys *timer_sys; + int i; + + if (!(flags & TIMER_TYPE_SYS)) { + /* Put an upper bound on the timer granularity, to avoid big timer errors */ + if (ms > 100) + ms = 100; + + timer_sys = timer_sys_find(tctx, ms); + if (timer_sys) + goto done; + } + + /* a dedicated system timer has been requested or a new shared one is needed to + shelter a soft timer */ + + if (tctx->num_sys_timers == tctx->max_sys_timers) + goto err; + + for (i = 0; i < tctx->max_sys_timers; i++) { + timer_sys = &tctx->timer_sys_table[i]; + + if (!(timer_sys->flags & TIMER_STATE_CREATED)) + goto found; + } + + return NULL; + +found: + if (os_timer_create(&timer_sys->os_timer, OS_CLOCK_SYSTEM_MONOTONIC_COARSE, 0, timer_process, tctx->priv) < 0) + goto err; + + timer_sys->flags = TIMER_STATE_CREATED | (flags & TIMER_TYPE_SYS); + timer_sys->ms = ms; + + /* we have consumed a new system timer... */ + tctx->num_sys_timers++; + +done: + timer_sys->users++; + + return timer_sys; + +err: + return NULL; +} + +static void timer_sys_destroy(struct timer_sys *timer_sys) +{ + timer_sys->users--; + + if (!timer_sys->users) { + /* if this was the last user can destroy the system timer */ + + os_timer_destroy(&timer_sys->os_timer); + + timer_sys->flags &= ~TIMER_STATE_CREATED; + + timer_sys->ctx->num_sys_timers--; + } +} + +static int timer_sys_start(struct timer_sys *timer_sys) +{ + int rc; + + if (!(timer_sys->flags & TIMER_STATE_STARTED)) { + + if (timer_sys->flags & TIMER_TYPE_SYS) + rc = os_timer_start(&timer_sys->os_timer, (u64)timer_sys->ms * NSECS_PER_MS, 0, 0, 0); + else + rc = os_timer_start(&timer_sys->os_timer, 0, (u64)timer_sys->ms * NSECS_PER_MS, 1, 0); + + if (rc < 0) + return -1; + + timer_sys->flags |= TIMER_STATE_STARTED; + } + + return 0; +} + +static void timer_sys_stop(struct timer_sys *timer_sys) +{ + if ((timer_sys->flags & TIMER_STATE_STARTED) && list_empty(&timer_sys->head)) { + + os_timer_stop(&timer_sys->os_timer); + + timer_sys->flags &= ~TIMER_STATE_STARTED; + } +} + + +/* + * ms is the timer base resolution for shared timers + */ +int timer_init(struct timer_ctx *tctx, struct timer *t, unsigned int flags, unsigned int ms) +{ + struct timer_sys *timer_sys; + + /* get a suitable timer system */ + timer_sys = timer_sys_create(tctx, flags, ms); + if (!timer_sys) { + os_log(LOG_ERR, "timer(%p) creation failed\n", t); + goto err; + } + + /* a timer system has been found, attach it to our timer */ + t->timer_sys = timer_sys; + t->flags = TIMER_STATE_CREATED; + + tctx->num_soft_timers++; + + os_log(LOG_INFO, "timer(%p) created in timer_ctx(%p) using os_timer(%p), flags: %x, ms: %d\n", + t, tctx, &t->timer_sys->os_timer, flags, ms); + + return 0; + +err: + return -1; +} + + +int timer_start(struct timer *t, unsigned int ms) +{ + struct timer_sys *timer_sys = t->timer_sys; + + os_log(LOG_DEBUG, "timer(%p) ms: %d\n", t, ms); + + if (!timer_sys) { + os_log(LOG_ERR, "timer(%p) not created\n", t); + goto err; + } + + if (t->flags & TIMER_STATE_STARTED) { + os_log(LOG_ERR, "timer(%p) already started\n", t); + goto err; + } + + if (!ms) { + os_log(LOG_ERR, "timer(%p) 0ms period\n", t); + goto err; + } + + t->ms = ms; + + if (timer_sys->flags & TIMER_TYPE_SYS) + timer_sys->ms = ms; + else + t->ms += timer_sys->ms; /* Add the system timer granularity to guarantee that we will wait at least ms */ + + list_add(&timer_sys->head, &t->list); + t->flags |= TIMER_STATE_STARTED; + + return timer_sys_start(timer_sys); + +err: + return -1; +} + + +void timer_stop(struct timer *t) +{ + struct timer_sys *timer_sys = t->timer_sys; + + os_log(LOG_DEBUG, "timer(%p)\n", t); + + if (!(t->flags & TIMER_STATE_STARTED)) { + os_log(LOG_DEBUG, "timer(%p) not started\n", t); + goto out; + } + + if (&t->list == timer_sys->next_to_process) + timer_sys->next_to_process = list_next(&t->list); + list_del(&t->list); + t->flags &= ~TIMER_STATE_STARTED; + + timer_sys_stop(timer_sys); + +out: + return; +} + +int timer_is_running(struct timer *t) +{ + struct timer_sys *timer_sys = t->timer_sys; + int rc = 0; + + os_log(LOG_DEBUG, "timer(%p)\n", t); + + if (!timer_sys) { + os_log(LOG_ERR, "timer(%p) not created\n", t); + rc = - 1; + goto err; + } + + if (t->flags & TIMER_STATE_STARTED) + rc = 1; + +err: + return rc; +} + +int timer_restart(struct timer *t, unsigned int ms) +{ + int rc = timer_is_running(t); + + if(rc < 0) + goto err; + + if(rc) + timer_stop(t); + + timer_start(t, ms); + +err: + return rc; +} + +int timer_destroy(struct timer *t) +{ + struct timer_sys *timer_sys = t->timer_sys; + + os_log(LOG_INFO, "timer(%p)\n", t); + + timer_stop(t); + + if (!timer_sys) { + os_log(LOG_ERR, "timer(%p) not created\n", t); + goto err; + } + + t->timer_sys = NULL; + t->flags &= ~TIMER_STATE_CREATED; + + timer_sys_destroy(timer_sys); + + timer_sys->ctx->num_soft_timers--; + + return 0; + +err: + return -1; +} + +unsigned int timer_pool_size(unsigned int n) +{ + return sizeof(struct timer_ctx) + n * sizeof(struct timer_sys); +} + +int timer_pool_init(struct timer_ctx *tctx, unsigned int n, unsigned long priv) +{ + int i; + + os_log(LOG_INFO, "timer_ctx(%p)\n", tctx); + + os_memset(tctx, 0, timer_pool_size(n)); + + tctx->max_sys_timers = n; + tctx->priv = priv; + + for (i = 0; i < tctx->max_sys_timers; i++) { + struct timer_sys *timer_sys = &tctx->timer_sys_table[i]; + + list_head_init(&timer_sys->head); + timer_sys->ctx = tctx; + } + + return 0; +} + +void timer_pool_exit(struct timer_ctx *tctx) +{ + os_log(LOG_INFO, "timer_ctx(%p)\n", tctx); +} diff --git a/common/timer.h b/common/timer.h new file mode 100644 index 0000000..8615a26 --- /dev/null +++ b/common/timer.h @@ -0,0 +1,71 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2017, 2019, 2021, 2023 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Timer Service implementation + @details +*/ +#ifndef _COMMON_TIMER_H_ +#define _COMMON_TIMER_H_ + +#include "common/list.h" +#include "os/timer.h" + +#include "config.h" + +/* software timers flags */ +#define TIMER_TYPE_SYS (1 << 0) +#define TIMER_STATE_CREATED (1 << 1) +#define TIMER_STATE_STARTED (1 << 2) + +#define NS_PER_MS (1000*1000) +#define MS_PER_S (1000) + +struct timer_sys { + struct list_head head; + struct list_head *next_to_process; + unsigned int ms; + unsigned int flags; + unsigned int users; + struct timer_ctx *ctx; + struct os_timer os_timer; /* OS dependant fields */ +}; + + +struct timer { + struct list_head list; + int ms; + unsigned int flags; + struct timer_sys *timer_sys; + void (*func)(void *); + void *data; +}; + +struct timer_ctx { + unsigned short max_sys_timers; + unsigned short num_soft_timers; + unsigned short num_sys_timers; + unsigned long priv; + + /* variable size array */ + struct timer_sys timer_sys_table[]; /* contains system timers only , either shared or exclusively used */ +}; + +#define timer_create timer_init +int timer_init(struct timer_ctx *tctx, struct timer *t, unsigned int flags, unsigned int ms); +int timer_start(struct timer *t, unsigned int ms); +int timer_restart(struct timer *t, unsigned int ms); +void timer_stop(struct timer *t); +int timer_is_running(struct timer *t); +int timer_destroy(struct timer *t); +unsigned int timer_pool_size(unsigned int n); +int timer_pool_init(struct timer_ctx *tctx, unsigned int n, unsigned long priv); +void timer_pool_exit(struct timer_ctx *tctx); + + +#endif /* _COMMON_TIMER_H_ */ diff --git a/common/types.h b/common/types.h new file mode 100644 index 0000000..5b99f23 --- /dev/null +++ b/common/types.h @@ -0,0 +1,78 @@ +/* + * Copyright 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2016-2019, 2021, 2023-2024 NXP + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + @file + @brief Basic types OS abstraction + @details +*/ + +#ifndef _COMMON_TYPES_H_ +#define _COMMON_TYPES_H_ + +#include "genavb/types.h" +#include "genavb/net_types.h" +#include "os/sys_types.h" + +#define container_of(entry, type, member) ((type *)((unsigned char *)(entry) - offsetof(type, member))) + +#define min(a,b) ((a)<(b)?(a):(b)) +#define max(a,b) ((a)>(b)?(a):(b)) + +/* The functions below use 32bit accesses to possibly non 32bit aligned addresses, + * this works for ARMv7 and above CPU's. For other architectures we may need + * to re-define these functions. + * Volatile is used when casting to u32 * to prevent compiler from "merging" loads + * and stores (use ldrd/strd/ldm/stm instructions for possibly unaligned addresses). + */ +static inline u64 get_64(const void *a) +{ +#if defined(__BIG_ENDIAN__) + return (((u64)(*(volatile u32 *)a)) << 32) | ((u64)(*((volatile u32 *)a + 1))); +#elif defined(__LITTLE_ENDIAN__) + return (((u64)(*((volatile u32 *)a + 1))) << 32) | ((u64)(*(volatile u32 *)a)); +#else + #error +#endif +} + +static inline u64 get_48(const void *a) +{ +#if defined(__BIG_ENDIAN__) + return (((u64)(*(volatile u32 *)a)) << 16) | ((u64)(*((u16 *)a + 2))); +#elif defined(__LITTLE_ENDIAN__) + return (((u64)(*((u16 *)a + 2))) << 32) | ((u64)(*(volatile u32 *)a)); +#else + #error +#endif +} + +static inline u64 get_ntohll(const void *a) +{ + return ntohll(get_64(a)); +} + +static inline u64 get_htonll(const void *a) +{ + return htonll(get_64(a)); +} + +static inline int cmp_64(const void *a, const void *b) +{ + if ((*(volatile u32 *)a == *(volatile u32 *)b) && (*((volatile u32 *)a + 1) == *((volatile u32 *)b + 1))) + return 1; + else + return 0; +} + +static inline void copy_64(void *dst, void const *src) +{ + *(volatile u32 *)dst = *(volatile u32 *)src; + *((volatile u32 *)dst + 1) = *(volatile u32 *)((volatile u32 *)src + 1); +} + +#endif /* _COMMON_TYPES_H_ */ diff --git a/config_armgcc.cmake b/config_armgcc.cmake new file mode 100644 index 0000000..db7759c --- /dev/null +++ b/config_armgcc.cmake @@ -0,0 +1,44 @@ +# TOOLCHAIN EXTENSION +if(WIN32) + set(toolchain_ext ".exe") +else() + set(toolchain_ext "") +endif() + +set(toolchain_dir $ENV{ARMGCC_DIR}) +string(REGEX REPLACE "\\\\" "/" toolchain_dir "${toolchain_dir}") + +if(NOT toolchain_dir) + message(FATAL_ERROR "*** Please set ARMGCC_DIR in environment variables ***") +endif() + +message(STATUS "toolchain_dir: " ${toolchain_dir}) + +set(target_prefix $ENV{TARGET_PREFIX}) +if(NOT target_prefix) + set(target_prefix "arm-none-eabi-") + message(WARNING "*** TARGET_PREFIX environment variable not found, defaults to ${target_prefix} ***") +endif() + +set(toolchain_bin_dir ${toolchain_dir}/bin) +set(toolchain_inc_dir ${toolchain_dir}/${target_prefix}/include) +set(toolchain_lib_dir ${toolchain_dir}/${target_prefix}/lib) + +if(target_prefix MATCHES "linux") + set(CMAKE_SYSTEM_NAME Linux) +else() + set(CMAKE_SYSTEM_NAME Generic) +endif() +set(CMAKE_SYSTEM_PROCESSOR arm) + +set(CMAKE_C_COMPILER ${toolchain_bin_dir}/${target_prefix}gcc${toolchain_ext}) +set(CMAKE_CXX_COMPILER ${toolchain_bin_dir}/${target_prefix}g++${toolchain_ext}) +set(CMAKE_ASM_COMPILER ${toolchain_bin_dir}/${target_prefix}gcc${toolchain_ext}) + +set(CMAKE_FIND_ROOT_PATH ${toolchain_dir}/${target_prefix} ${extra_find_path}) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(CMAKE_C_COMPILER_FORCED TRUE) +set(CMAKE_CXX_COMPILER_FORCED TRUE) diff --git a/config_freertos_imx8mm_ca53.cmake b/config_freertos_imx8mm_ca53.cmake new file mode 100644 index 0000000..a8d39c6 --- /dev/null +++ b/config_freertos_imx8mm_ca53.cmake @@ -0,0 +1,42 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8a) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_DIR) + message(WARNING "Undefined RTOS_DIR") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(NOT DEFINED RTOS_ABSTRACTION_LAYER_DIR) + message(WARNING "Undefined RTOS_ABSTRACTION_LAYER_DIR") +endif() + +set(FREERTOS_PORT GCC/ARM_CA53_64_BIT) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/common/freertos) +set(APP_GENAVB_SDK_INCLUDE ${AppPath}/common/boards/evkmimx8mm) +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMX8MM6 ${MCUX_SDK}/devices/MIMX8MM6/drivers) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core_AArch64/Include ${MCUX_SDK}/drivers/cache/armv8-a) +set(MCUX_SDK_DRIVERS_DIRS ${MCUX_SDK}/drivers ${MCUX_SDK}/drivers/common ${MCUX_SDK}/drivers/enet ${MCUX_SDK}/drivers/gpt ${MCUX_SDK}/components/phy/mdio/enet ${MCUX_SDK}/components/phy/device/phyar8031) + +set(INCLUDE_DIR ${RTOS_DIR}/include) + +add_compile_definitions(GUEST CPU_MIMX8MM6DVTLZ_ca53 ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) +add_compile_options(-O0 -Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-march=armv8-a) + diff --git a/config_freertos_imx8mn_ca53.cmake b/config_freertos_imx8mn_ca53.cmake new file mode 100644 index 0000000..1ceae40 --- /dev/null +++ b/config_freertos_imx8mn_ca53.cmake @@ -0,0 +1,42 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8a) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_DIR) + message(WARNING "Undefined RTOS_DIR") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(NOT DEFINED RTOS_ABSTRACTION_LAYER_DIR) + message(WARNING "Undefined RTOS_ABSTRACTION_LAYER_DIR") +endif() + +set(FREERTOS_PORT GCC/ARM_CA53_64_BIT) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/common/freertos) +set(APP_GENAVB_SDK_INCLUDE ${AppPath}/common/boards/evkmimx8mn) +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMX8MN6 ${MCUX_SDK}/devices/MIMX8MN6/drivers) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core_AArch64/Include ${MCUX_SDK}/drivers/cache/armv8-a) +set(MCUX_SDK_DRIVERS_DIRS ${MCUX_SDK}/drivers ${MCUX_SDK}/drivers/common ${MCUX_SDK}/drivers/enet ${MCUX_SDK}/drivers/gpt ${MCUX_SDK}/components/phy/mdio/enet ${MCUX_SDK}/components/phy/device/phyar8031) + +set(INCLUDE_DIR ${RTOS_DIR}/include) + +add_compile_definitions(GUEST CPU_MIMX8MN6DVTJZ_ca53 ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) +add_compile_options(-O0 -Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-march=armv8-a) + diff --git a/config_freertos_imx8mp_ca53.cmake b/config_freertos_imx8mp_ca53.cmake new file mode 100644 index 0000000..a9c4f55 --- /dev/null +++ b/config_freertos_imx8mp_ca53.cmake @@ -0,0 +1,42 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8a) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_DIR) + message(WARNING "Undefined RTOS_DIR") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(NOT DEFINED RTOS_ABSTRACTION_LAYER_DIR) + message(WARNING "Undefined RTOS_ABSTRACTION_LAYER_DIR") +endif() + +set(FREERTOS_PORT GCC/ARM_CA53_64_BIT) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/common/freertos) +set(APP_GENAVB_SDK_INCLUDE ${AppPath}/common/boards/evkmimx8mp) +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMX8ML8 ${MCUX_SDK}/devices/MIMX8ML8/drivers) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core_AArch64/Include ${MCUX_SDK}/drivers/cache/armv8-a) +set(MCUX_SDK_DRIVERS_DIRS ${MCUX_SDK}/drivers ${MCUX_SDK}/drivers/common ${MCUX_SDK}/drivers/enet ${MCUX_SDK}/drivers/enet_qos ${MCUX_SDK}/drivers/gpt) + +set(INCLUDE_DIR ${RTOS_DIR}/include) + +add_compile_definitions(GUEST CPU_MIMX8ML8DVNLZ_ca53 ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) +add_compile_options(-O0 -Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-march=armv8-a) + diff --git a/config_freertos_imx93_ca55.cmake b/config_freertos_imx93_ca55.cmake new file mode 100644 index 0000000..28dfe55 --- /dev/null +++ b/config_freertos_imx93_ca55.cmake @@ -0,0 +1,41 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8a) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_DIR) + message(WARNING "Undefined RTOS_DIR") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(NOT DEFINED RTOS_ABSTRACTION_LAYER_DIR) + message(WARNING "Undefined RTOS_ABSTRACTION_LAYER_DIR") +endif() + +set(FREERTOS_PORT GCC/AARCH64_SRE) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/common/freertos) +set(APP_GENAVB_SDK_INCLUDE ${AppPath}/common/boards/mcimx93evk) +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMX9352 ${MCUX_SDK}/devices/MIMX9352/drivers) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core_AArch64/Include ${MCUX_SDK}/drivers/cache/armv8-a) +set(MCUX_SDK_DRIVERS_DIRS ${MCUX_SDK}/drivers ${MCUX_SDK}/drivers/common ${MCUX_SDK}/drivers/enet_qos ${MCUX_SDK}/drivers/tpm) + +set(INCLUDE_DIR ${RTOS_DIR}/include) + +add_compile_definitions(GUEST CPU_MIMX9352DVUXM_ca55 ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) +add_compile_options(-O0 -Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-march=armv8-a) diff --git a/config_freertos_rt1052.cmake b/config_freertos_rt1052.cmake new file mode 100644 index 0000000..1293878 --- /dev/null +++ b/config_freertos_rt1052.cmake @@ -0,0 +1,38 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv7m) + +set(RTOS_ABSTRACTION_LAYER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rtos/freertos) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos-kernel) +set(FREERTOS_PORT GCC/ARM_CM4F) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/boards/evkbimxrt1050/demo_apps/avb_tsn/common ${RTOS_APPS}/boards/src/demo_apps/avb_tsn/common) +if(NOT DEFINED APP_GENAVB_SDK_INCLUDE) +set(APP_GENAVB_SDK_INCLUDE ${RTOS_APPS}/boards/evkbimxrt1050/demo_apps/avb_tsn/common) +endif() +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMXRT1052 ${MCUX_SDK}/devices/MIMXRT1052/drivers ${MCUX_SDK}/devices/MIMXRT1052/drivers/cm7) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core/Include) + +set(INCLUDE_DIR usr/include) + +add_compile_definitions(CPU_MIMXRT1052DVL6B ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) +add_compile_options(-Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-Os -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16) + diff --git a/config_freertos_rt1176.cmake b/config_freertos_rt1176.cmake new file mode 100644 index 0000000..3a87e58 --- /dev/null +++ b/config_freertos_rt1176.cmake @@ -0,0 +1,39 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv7m) + +set(RTOS_ABSTRACTION_LAYER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rtos/freertos) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos-kernel) +set(FREERTOS_PORT GCC/ARM_CM4F) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/boards/evkmimxrt1170/demo_apps/avb_tsn/common ${RTOS_APPS}/boards/src/demo_apps/avb_tsn/common) +if(NOT DEFINED APP_GENAVB_SDK_INCLUDE) +set(APP_GENAVB_SDK_INCLUDE ${RTOS_APPS}/boards/evkmimxrt1170/demo_apps/avb_tsn/common) +endif() +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMXRT1176 ${MCUX_SDK}/devices/MIMXRT1176/drivers ${MCUX_SDK}/devices/MIMXRT1176/drivers/cm7) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core/Include) + +set(INCLUDE_DIR usr/include) + +add_compile_definitions(CPU_MIMXRT1176DVMAA_cm7 ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) +add_compile_options(-Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-Os -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16) + +add_compile_definitions(NET_RX_PACKETS=10) diff --git a/config_freertos_rt1187_cm33.cmake b/config_freertos_rt1187_cm33.cmake new file mode 100644 index 0000000..81ba4de --- /dev/null +++ b/config_freertos_rt1187_cm33.cmake @@ -0,0 +1,44 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8m) + +set(RTOS_ABSTRACTION_LAYER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rtos/freertos) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(EXISTS ${MCUX_SDK}/rtos/freertos/freertos_kernel) +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos_kernel) +else() +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos-kernel) +endif() +set(FREERTOS_PORT GCC/ARM_CM33_NTZ/non_secure) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/boards/evkmimxrt1180a/demo_apps/avb_tsn/common/cm33 ${RTOS_APPS}/boards/src/demo_apps/avb_tsn/common) +if(NOT DEFINED APP_GENAVB_SDK_INCLUDE) +set(APP_GENAVB_SDK_INCLUDE ${RTOS_APPS}/boards/evkmimxrt1180a/demo_apps/avb_tsn/common/cm33 ${RTOS_APPS}/devices/MIMXRT118x/common/cm33 ${RTOS_APPS}/devices/MIMXRT118x/tsn_app/cm33) +endif() +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMXRT1187 ${MCUX_SDK}/devices/MIMXRT1187/drivers ${MCUX_SDK}/devices/MIMXRT1187/drivers/cm33 ${MCUX_SDK}/platform/drivers/netc/socs/imxrt1180) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core/Include) + +set(INCLUDE_DIR usr/include) + +add_compile_definitions(CPU_MIMXRT1187AVM8B_cm33) +add_compile_options(-Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-Os -mthumb -mcpu=cortex-m33 -mfloat-abi=hard -mfpu=fpv5-sp-d16) + +add_compile_definitions(NET_RX_PACKETS=2) +add_compile_definitions(NET_RX_PERIOD_MUL=2) diff --git a/config_freertos_rt1187_cm7.cmake b/config_freertos_rt1187_cm7.cmake new file mode 100644 index 0000000..35bdc0a --- /dev/null +++ b/config_freertos_rt1187_cm7.cmake @@ -0,0 +1,43 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8m) + +set(RTOS_ABSTRACTION_LAYER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rtos/freertos) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(EXISTS ${MCUX_SDK}/rtos/freertos/freertos_kernel) +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos_kernel) +else() +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos-kernel) +endif() +set(FREERTOS_PORT GCC/ARM_CM4F) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/boards/evkmimxrt1180a/demo_apps/avb_tsn/common/cm7 ${RTOS_APPS}/boards/src/demo_apps/avb_tsn/common) +if(NOT DEFINED APP_GENAVB_SDK_INCLUDE) +set(APP_GENAVB_SDK_INCLUDE ${RTOS_APPS}/boards/evkmimxrt1180a/demo_apps/avb_tsn/common/cm7 ${RTOS_APPS}/devices/MIMXRT118x/common/cm7) +endif() +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMXRT1187 ${MCUX_SDK}/devices/MIMXRT1187/drivers ${MCUX_SDK}/devices/MIMXRT1187/drivers/cm7 ${MCUX_SDK}/platform/drivers/netc/socs/imxrt1180) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core/Include) + +set(INCLUDE_DIR usr/include) + +add_compile_definitions(CPU_MIMXRT1187AVM8B_cm7) +add_compile_options(-Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-Os -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-sp-d16) + +add_compile_definitions(NET_RX_PACKETS=8) diff --git a/config_freertos_rt1189_cm33.cmake b/config_freertos_rt1189_cm33.cmake new file mode 100644 index 0000000..90fcf7d --- /dev/null +++ b/config_freertos_rt1189_cm33.cmake @@ -0,0 +1,44 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8m) + +set(RTOS_ABSTRACTION_LAYER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rtos/freertos) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(EXISTS ${MCUX_SDK}/rtos/freertos/freertos_kernel) +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos_kernel) +else() +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos-kernel) +endif() +set(FREERTOS_PORT GCC/ARM_CM33_NTZ/non_secure) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/boards/evkmimxrt1180/demo_apps/avb_tsn/common/cm33 ${RTOS_APPS}/devices/MIMXRT118x/common/cm33 ${RTOS_APPS}/boards/src/demo_apps/avb_tsn/common) +if(NOT DEFINED APP_GENAVB_SDK_INCLUDE) +set(APP_GENAVB_SDK_INCLUDE ${RTOS_APPS}/boards/evkmimxrt1180/demo_apps/avb_tsn/common/cm33 ${RTOS_APPS}/devices/MIMXRT118x/tsn_app/cm33) +endif() +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMXRT1189 ${MCUX_SDK}/devices/MIMXRT1189/drivers ${MCUX_SDK}/devices/MIMXRT1189/drivers/cm33 ${MCUX_SDK}/platform/drivers/netc/socs/imxrt1180) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core/Include) + +set(INCLUDE_DIR usr/include) + +add_compile_definitions(CPU_MIMXRT1189CVM8B_cm33) +add_compile_options(-Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-Os -mthumb -mcpu=cortex-m33 -mfloat-abi=hard -mfpu=fpv5-sp-d16) + +add_compile_definitions(NET_RX_PACKETS=2) +add_compile_definitions(NET_RX_PERIOD_MUL=2) diff --git a/config_freertos_rt1189_cm7.cmake b/config_freertos_rt1189_cm7.cmake new file mode 100644 index 0000000..ae66954 --- /dev/null +++ b/config_freertos_rt1189_cm7.cmake @@ -0,0 +1,43 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8m) + +set(RTOS_ABSTRACTION_LAYER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rtos/freertos) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(EXISTS ${MCUX_SDK}/rtos/freertos/freertos_kernel) +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos_kernel) +else() +set(RTOS_DIR ${MCUX_SDK}/rtos/freertos/freertos-kernel) +endif() +set(FREERTOS_PORT GCC/ARM_CM4F) +set(FREERTOS_CONFIG_INCLUDES ${RTOS_APPS}/boards/evkmimxrt1180/demo_apps/avb_tsn/common/cm7 ${RTOS_APPS}/devices/MIMXRT118x/common/cm7 ${RTOS_APPS}/boards/src/demo_apps/avb_tsn/common) +if(NOT DEFINED APP_GENAVB_SDK_INCLUDE) +set(APP_GENAVB_SDK_INCLUDE ${RTOS_APPS}/boards/evkmimxrt1180/demo_apps/avb_tsn/common/cm7) +endif() +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMXRT1189 ${MCUX_SDK}/devices/MIMXRT1189/drivers ${MCUX_SDK}/devices/MIMXRT1189/drivers/cm7 ${MCUX_SDK}/platform/drivers/netc/socs/imxrt1180) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core/Include) + +set(INCLUDE_DIR usr/include) + +add_compile_definitions(CPU_MIMXRT1189CVM8B_cm7) +add_compile_options(-Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) +add_compile_options(-Os -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-sp-d16) + +add_compile_definitions(NET_RX_PACKETS=8) diff --git a/config_linux_common.cmake b/config_linux_common.cmake new file mode 100644 index 0000000..27036ea --- /dev/null +++ b/config_linux_common.cmake @@ -0,0 +1,24 @@ +if(EXISTS local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS linux) + +if(NOT DEFINED KERNELDIR) + message(WARNING "No KERNELDIR specified") +endif() + +set(BIN_DIR usr/bin) +set(LIB_DIR usr/lib) +set(INCLUDE_DIR usr/include) +set(CONFIG_DIR etc/genavb) +set(INITSCRIPT_DIR etc/init.d) +set(FIRMWARE_DIR usr/lib/firmware/genavb) + +add_compile_options(-O2 -Wall -g -Werror -Wpointer-arith -std=c99 -Wdeclaration-after-statement) diff --git a/config_linux_imx6.cmake b/config_linux_imx6.cmake new file mode 100644 index 0000000..8b5af17 --- /dev/null +++ b/config_linux_imx6.cmake @@ -0,0 +1,5 @@ +include(config_linux_common.cmake) + +set(TARGET_ARCH armv7a) + +add_compile_options(-march=armv7-a) diff --git a/config_linux_imx6ull.cmake b/config_linux_imx6ull.cmake new file mode 100644 index 0000000..7b0013e --- /dev/null +++ b/config_linux_imx6ull.cmake @@ -0,0 +1,5 @@ +include(config_linux_common.cmake) + +set(TARGET_ARCH armv7ve) + +add_compile_options(-march=armv7ve) diff --git a/config_linux_imx8.cmake b/config_linux_imx8.cmake new file mode 100644 index 0000000..32a3a70 --- /dev/null +++ b/config_linux_imx8.cmake @@ -0,0 +1,5 @@ +include(config_linux_common.cmake) + +set(TARGET_ARCH armv8a) + +add_compile_options(-march=armv8-a) diff --git a/config_linux_ls1028.cmake b/config_linux_ls1028.cmake new file mode 100644 index 0000000..32a3a70 --- /dev/null +++ b/config_linux_ls1028.cmake @@ -0,0 +1,5 @@ +include(config_linux_common.cmake) + +set(TARGET_ARCH armv8a) + +add_compile_options(-march=armv8-a) diff --git a/config_zephyr_imx8mm_ca53.cmake b/config_zephyr_imx8mm_ca53.cmake new file mode 100644 index 0000000..775c654 --- /dev/null +++ b/config_zephyr_imx8mm_ca53.cmake @@ -0,0 +1,46 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8a) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_DIR) + message(WARNING "Undefined RTOS_DIR") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(NOT DEFINED RTOS_ABSTRACTION_LAYER_DIR) + message(WARNING "Undefined RTOS_ABSTRACTION_LAYER_DIR") +endif() + +set(APP_GENAVB_SDK_INCLUDE ${AppPath}/common/boards/evkmimx8mm) +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMX8MM6 ${MCUX_SDK}/devices/MIMX8MM6/drivers) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core_AArch64/Include ${MCUX_SDK}/drivers/cache/armv8-a) +set(MCUX_SDK_DRIVERS_DIRS ${MCUX_SDK}/drivers ${MCUX_SDK}/drivers/common ${MCUX_SDK}/drivers/enet ${MCUX_SDK}/drivers/gpt) + +set(INCLUDE_DIR ${RTOS_DIR}/include) + +# Include the zephyr toolchain definitions early in source files +add_compile_options("-DCONFIG_ARM64=1") +add_compile_options(-isystem "${ZEPHYR_BASE}/include") +# "SHELL" prefix to prevent de-duplication feature +add_compile_options(SHELL:-include "${ZEPHYR_BASE}/include/zephyr/toolchain.h") + +add_compile_definitions(GUEST CPU_MIMX8MM6DVTLZ_ca53 ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) +add_compile_options(-O2 -Wall -g -Werror -Wpointer-arith -std=c99) +add_compile_options(-march=armv8-a) + diff --git a/config_zephyr_imx8mn_ca53.cmake b/config_zephyr_imx8mn_ca53.cmake new file mode 100644 index 0000000..654c6bf --- /dev/null +++ b/config_zephyr_imx8mn_ca53.cmake @@ -0,0 +1,46 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8a) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_DIR) + message(WARNING "Undefined RTOS_DIR") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(NOT DEFINED RTOS_ABSTRACTION_LAYER_DIR) + message(WARNING "Undefined RTOS_ABSTRACTION_LAYER_DIR") +endif() + +set(APP_GENAVB_SDK_INCLUDE ${AppPath}/common/boards/evkmimx8mn) +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMX8MN6 ${MCUX_SDK}/devices/MIMX8MN6/drivers) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core_AArch64/Include ${MCUX_SDK}/drivers/cache/armv8-a) +set(MCUX_SDK_DRIVERS_DIRS ${MCUX_SDK}/drivers ${MCUX_SDK}/drivers/common ${MCUX_SDK}/drivers/enet ${MCUX_SDK}/drivers/gpt) + +set(INCLUDE_DIR ${RTOS_DIR}/include) + +# Include the zephyr toolchain definitions early in source files +add_compile_options("-DCONFIG_ARM64=1") +add_compile_options(-isystem "${ZEPHYR_BASE}/include") +# "SHELL" prefix to prevent de-duplication feature +add_compile_options(SHELL:-include "${ZEPHYR_BASE}/include/zephyr/toolchain.h") + +add_compile_definitions(GUEST CPU_MIMX8MN6DVTJZ_ca53 ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) +add_compile_options(-O2 -Wall -g -Werror -Wpointer-arith -std=c99) +add_compile_options(-march=armv8-a) + diff --git a/config_zephyr_imx8mp_ca53.cmake b/config_zephyr_imx8mp_ca53.cmake new file mode 100644 index 0000000..2f3e300 --- /dev/null +++ b/config_zephyr_imx8mp_ca53.cmake @@ -0,0 +1,45 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8a) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_DIR) + message(WARNING "Undefined RTOS_DIR") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(NOT DEFINED RTOS_ABSTRACTION_LAYER_DIR) + message(WARNING "Undefined RTOS_ABSTRACTION_LAYER_DIR") +endif() + +set(APP_GENAVB_SDK_INCLUDE ${AppPath}/common/boards/evkmimx8mp) +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMX8ML8 ${MCUX_SDK}/devices/MIMX8ML8/drivers) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core_AArch64/Include ${MCUX_SDK}/drivers/cache/armv8-a) +set(MCUX_SDK_DRIVERS_DIRS ${MCUX_SDK}/drivers ${MCUX_SDK}/drivers/common ${MCUX_SDK}/drivers/enet_qos ${MCUX_SDK}/drivers/gpt) + +set(INCLUDE_DIR ${RTOS_DIR}/include) + +# Include the zephyr toolchain definitions early in source files +add_compile_options("-DCONFIG_ARM64=1") +add_compile_options(-isystem "${ZEPHYR_BASE}/include") +# "SHELL" prefix to prevent de-duplication feature +add_compile_options(SHELL:-include "${ZEPHYR_BASE}/include/zephyr/toolchain.h") + +add_compile_options(-O2 -Wall -g -Werror -Wpointer-arith -std=c99) +add_compile_options(-march=armv8-a) + diff --git a/config_zephyr_imx93_ca55.cmake b/config_zephyr_imx93_ca55.cmake new file mode 100644 index 0000000..1abd2b3 --- /dev/null +++ b/config_zephyr_imx93_ca55.cmake @@ -0,0 +1,43 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local_config_${TARGET}.cmake) + include(local_config_${TARGET}.cmake) +endif() + +if(NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "No toolchain specified, make sure the correct one is selected") +else() + message(STATUS "Toolchain: ${CMAKE_TOOLCHAIN_FILE}") +endif() + +set(TARGET_OS rtos) +set(TARGET_ARCH armv8a) + +if(NOT DEFINED MCUX_SDK) + message(WARNING "Undefined MCUX_SDK") +endif() + +if(NOT DEFINED RTOS_DIR) + message(WARNING "Undefined RTOS_DIR") +endif() + +if(NOT DEFINED RTOS_APPS) + message(WARNING "Undefined RTOS_APPS, default used") +endif() + +if(NOT DEFINED RTOS_ABSTRACTION_LAYER_DIR) + message(WARNING "Undefined RTOS_ABSTRACTION_LAYER_DIR") +endif() + +set(APP_GENAVB_SDK_INCLUDE ${AppPath}/common/boards/mcimx93evk) +set(MCUX_SDK_DEVICE_DIRS ${MCUX_SDK}/devices/MIMX9352 ${MCUX_SDK}/devices/MIMX9352/drivers) +set(MCUX_SDK_TARGET_DIRS ${MCUX_SDK}/CMSIS/Core_AArch64/Include ${MCUX_SDK}/drivers/cache/armv8-a) +set(MCUX_SDK_DRIVERS_DIRS ${MCUX_SDK}/drivers ${MCUX_SDK}/drivers/common ${MCUX_SDK}/drivers/enet_qos ${MCUX_SDK}/drivers/tpm) + +set(INCLUDE_DIR ${RTOS_DIR}/include) + +# Include the zephyr toolchain definitions early in source files +add_compile_options("-DCONFIG_ARM64=1") +add_compile_options(-isystem "${ZEPHYR_BASE}/include") +# "SHELL" prefix to prevent de-duplication feature +add_compile_options(SHELL:-include "${ZEPHYR_BASE}/include/zephyr/toolchain.h") + +add_compile_options(-O2 -Wall -g -Werror -Wpointer-arith -std=c99) diff --git a/configs/bridge.cmake b/configs/bridge.cmake new file mode 100644 index 0000000..1175a37 --- /dev/null +++ b/configs/bridge.cmake @@ -0,0 +1,5 @@ +genavb_set_option(CONFIG_GPTP ON) +genavb_set_option(CONFIG_SRP ON) +genavb_set_option(CONFIG_MANAGEMENT ON) +genavb_set_option(CONFIG_API ON) +genavb_set_option(CONFIG_APPS ON) diff --git a/configs/bridge_dsa.cmake b/configs/bridge_dsa.cmake new file mode 100644 index 0000000..df35b4e --- /dev/null +++ b/configs/bridge_dsa.cmake @@ -0,0 +1,8 @@ +genavb_set_option(CONFIG_GPTP OFF) +genavb_set_option(CONFIG_SRP ON) +genavb_set_option(CONFIG_MANAGEMENT ON) +genavb_set_option(CONFIG_API ON) +genavb_set_option(CONFIG_APPS ON) +genavb_set_option(CONFIG_SOCKET ON) +genavb_set_option(CONFIG_HSR ON) +genavb_set_option(CONFIG_DSA ON) diff --git a/configs/configs.cmake b/configs/configs.cmake new file mode 100644 index 0000000..ba05798 --- /dev/null +++ b/configs/configs.cmake @@ -0,0 +1,18 @@ +function(genavb_set_option option value) + # Define in parent scope to be visible to parent CMakeList + set(${option} ${value} PARENT_SCOPE) + # Define in current (function) scope to be used here. + set(${option} ${value}) + + if(${option}) + add_definitions(-D${option}=1) + endif() +endfunction() + +if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/${CONFIG}.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/${CONFIG}.cmake) +else() + message(FATAL_ERROR "Configuration file for ${CONFIG} does not exist") +endif() + +message(STATUS "CONFIG: ${CONFIG}") diff --git a/configs/endpoint_avb.cmake b/configs/endpoint_avb.cmake new file mode 100644 index 0000000..4cb5e58 --- /dev/null +++ b/configs/endpoint_avb.cmake @@ -0,0 +1,10 @@ +genavb_set_option(CONFIG_AVTP ON) +genavb_set_option(CONFIG_AVDECC ON) +genavb_set_option(CONFIG_MAAP ON) +genavb_set_option(CONFIG_GPTP ON) +genavb_set_option(CONFIG_SRP ON) +genavb_set_option(CONFIG_MANAGEMENT ON) +genavb_set_option(CONFIG_API ON) +genavb_set_option(CONFIG_APPS ON) +genavb_set_option(CONFIG_SOCKET ON) + diff --git a/configs/endpoint_avb_tsn.cmake b/configs/endpoint_avb_tsn.cmake new file mode 100644 index 0000000..e2dde6f --- /dev/null +++ b/configs/endpoint_avb_tsn.cmake @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_LIST_DIR}/endpoint_avb.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/endpoint_tsn.cmake) diff --git a/configs/endpoint_avb_tsn_bridge.cmake b/configs/endpoint_avb_tsn_bridge.cmake new file mode 100644 index 0000000..2123e23 --- /dev/null +++ b/configs/endpoint_avb_tsn_bridge.cmake @@ -0,0 +1,3 @@ +include(${CMAKE_CURRENT_LIST_DIR}/endpoint_avb.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/endpoint_tsn.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/bridge.cmake) diff --git a/configs/endpoint_avb_tsn_hybrid.cmake b/configs/endpoint_avb_tsn_hybrid.cmake new file mode 100644 index 0000000..2123e23 --- /dev/null +++ b/configs/endpoint_avb_tsn_hybrid.cmake @@ -0,0 +1,3 @@ +include(${CMAKE_CURRENT_LIST_DIR}/endpoint_avb.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/endpoint_tsn.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/bridge.cmake) diff --git a/configs/endpoint_tsn.cmake b/configs/endpoint_tsn.cmake new file mode 100644 index 0000000..dc88a51 --- /dev/null +++ b/configs/endpoint_tsn.cmake @@ -0,0 +1,7 @@ +genavb_set_option(CONFIG_GPTP ON) +genavb_set_option(CONFIG_SRP ON) +genavb_set_option(CONFIG_MANAGEMENT ON) +genavb_set_option(CONFIG_API ON) +genavb_set_option(CONFIG_APPS ON) +genavb_set_option(CONFIG_SOCKET ON) + diff --git a/configs/endpoint_tsn_no_gptp.cmake b/configs/endpoint_tsn_no_gptp.cmake new file mode 100644 index 0000000..f0933b6 --- /dev/null +++ b/configs/endpoint_tsn_no_gptp.cmake @@ -0,0 +1,5 @@ +genavb_set_option(CONFIG_MANAGEMENT ON) +genavb_set_option(CONFIG_API ON) +genavb_set_option(CONFIG_APPS ON) +genavb_set_option(CONFIG_SOCKET ON) + diff --git a/configs/hybrid_avb.cmake b/configs/hybrid_avb.cmake new file mode 100644 index 0000000..b1755f6 --- /dev/null +++ b/configs/hybrid_avb.cmake @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_LIST_DIR}/endpoint_avb.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/bridge.cmake) diff --git a/configs/hybrid_tsn.cmake b/configs/hybrid_tsn.cmake new file mode 100644 index 0000000..0eef6a6 --- /dev/null +++ b/configs/hybrid_tsn.cmake @@ -0,0 +1,7 @@ +genavb_set_option(CONFIG_GPTP ON) +genavb_set_option(CONFIG_SRP ON) +genavb_set_option(CONFIG_MANAGEMENT ON) +genavb_set_option(CONFIG_API ON) +genavb_set_option(CONFIG_APPS ON) +genavb_set_option(CONFIG_SOCKET ON) +genavb_set_option(CONFIG_HSR ON) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..9e7cb80 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,88 @@ +option(BUILD_DOC "Build Doxygen Documentation" OFF) +option(BUILD_DOC_STRICT "Build Doxygen Documentation with Warnings as Errors" OFF) + +if (BUILD_DOC OR BUILD_DOC_STRICT) + find_package(Doxygen + REQUIRED dot mscgen) + + set(DOXYGEN_COMMON_INPUT_FILES + ${CMAKE_CURRENT_LIST_DIR}/../include/genavb + ${CMAKE_CURRENT_LIST_DIR}/init.md + ${CMAKE_CURRENT_LIST_DIR}/streaming.md + ${CMAKE_CURRENT_LIST_DIR}/stream_formats.md + ${CMAKE_CURRENT_LIST_DIR}/control.md + ${CMAKE_CURRENT_LIST_DIR}/socket.md + ${CMAKE_CURRENT_LIST_DIR}/clock.md) + + if("${TARGET_OS}" STREQUAL "linux") + set(DOXYGEN_ENABLED_SECTIONS LINUX) + set(DOXYGEN_OS_INPUT_FILES + ${CMAKE_CURRENT_LIST_DIR}/../include/linux + ${CMAKE_CURRENT_LIST_DIR}/gptp.md + ${CMAKE_CURRENT_LIST_DIR}/config.md + ${CMAKE_CURRENT_LIST_DIR}/platform_linux.md + ${CMAKE_CURRENT_LIST_DIR}/mainpage_linux.md) + elseif("${TARGET_OS}" STREQUAL "rtos") + set(DOXYGEN_ENABLED_SECTIONS RTOS) + set(DOXYGEN_OS_INPUT_FILES + ${CMAKE_CURRENT_LIST_DIR}/../include/rtos + ${CMAKE_CURRENT_LIST_DIR}/timer.md + ${CMAKE_CURRENT_LIST_DIR}/scheduled_traffic.md + ${CMAKE_CURRENT_LIST_DIR}/frame_preemption.md + ${CMAKE_CURRENT_LIST_DIR}/mainpage_rtos.md) + endif() + + set(DOXYGEN_GENERATE_HTML YES) + if (BUILD_DOC_STRICT) + set(DOXYGEN_WARN_AS_ERROR YES) + endif() + set(DOXYGEN_MARKDOWN_SUPPORT YES) + set(DOXYGEN_GENERATE_TREEVIEW YES) + set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) + set(DOXYGEN_MACRO_EXPANSION YES) + set(DOXYGEN_EXPAND_ONLY_PREDEF YES) + set(DOXYGEN_PREDEFINED "__attribute__(x)= static inline") + set(DOXYGEN_WARN_IF_UNDOCUMENTED NO) + set(DOXYGEN_FILE_PATTERNS *.h *.md *.dox *.png *.jpg *.gif) + set(DOXYGEN_PROJECT_NAME "NXP GenAVB/TSN") + set(DOXYGEN_LAYOUT_FILE ${CMAKE_CURRENT_LIST_DIR}/help_template/HTML_PageLayout.xml) + set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_LIST_DIR}/help_template/html_header.html) + set(DOXYGEN_HTML_FOOTER ${CMAKE_CURRENT_LIST_DIR}/help_template/html_footer.html) + set(DOXYGEN_HTML_EXTRA_STYLESHEET ${CMAKE_CURRENT_LIST_DIR}/help_template/html_custom.css) + set(DOXYGEN_HTML_OUTPUT html) + if(DOC_OUTPUT_DIR) + set(DOXYGEN_OUTPUT_DIRECTORY ${DOC_OUTPUT_DIR}/${TARGET_OS}) + else() + set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/${TARGET_OS}) + endif() + set(DOXYGEN_IMAGE_PATH ${DOXYGEN_OUTPUT_DIRECTORY}) + + if(${CMAKE_VERSION} GREATER_EQUAL "3.12.0") + # Add build doc to default build target + doxygen_add_docs(doc_doxygen + ALL + ${DOXYGEN_COMMON_INPUT_FILES} ${DOXYGEN_OS_INPUT_FILES} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + COMMENT "Generating ${TARGET_OS} HTML docs") + else() + doxygen_add_docs(doc_doxygen + ${DOXYGEN_COMMON_INPUT_FILES} ${DOXYGEN_OS_INPUT_FILES} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + COMMENT "Generating ${TARGET_OS} HTML docs") + endif() + + add_custom_target(doc_dot_gen + COMMAND ${CMAKE_COMMAND} -E make_directory ${DOXYGEN_OUTPUT_DIRECTORY}/${DOXYGEN_HTML_OUTPUT}/ + COMMAND Doxygen::dot -T png ${CMAKE_CURRENT_LIST_DIR}/avdecc_control.dot -o ${DOXYGEN_OUTPUT_DIRECTORY}/${DOXYGEN_HTML_OUTPUT}/avdecc_control.png + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generate PNG from dot") + + add_dependencies(doc_doxygen doc_dot_gen) + + add_custom_target(doc_msc_gen + COMMAND ${CMAKE_COMMAND} -E make_directory ${DOXYGEN_OUTPUT_DIRECTORY}/${DOXYGEN_HTML_OUTPUT}/ + COMMAND Doxygen::mscgen -Tpng -F arial -i ${CMAKE_CURRENT_LIST_DIR}/acmp_connect.msc -o ${DOXYGEN_OUTPUT_DIRECTORY}/${DOXYGEN_HTML_OUTPUT}/acmp_connect.png + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generate PNG from MSC") + add_dependencies(doc_doxygen doc_msc_gen) +endif() diff --git a/doc/acmp_connect.msc b/doc/acmp_connect.msc new file mode 100644 index 0000000..717d839 --- /dev/null +++ b/doc/acmp_connect.msc @@ -0,0 +1,34 @@ +msc { + arcgradient = 0, width = 1600; + + y[label="app",linecolor=maroon,textcolor=maroon], z[label="stack",linecolor=maroon,textcolor=maroon], a[label="app",linecolor=olive,textcolor=olive], b[label="stack",linecolor=olive,textcolor=olive], c[label="stack",linecolor=teal,textcolor=teal], d[label="app",linecolor=teal,textcolor=teal]; + + y rbox z [label="Controller", textbgcolour=maroon], + a rbox b [label="Listener", textbgcolour=olive], + c rbox d [label="Talker", textbgcolour=teal], + |||; + y:>z [label="CONNECT_RX_COMMAND",linecolor=gray,textcolor=gray, arcskip=1], + |||; + |||; + z->b [label="CONNECT_RX_COMMAND",linecolor=gray,textcolor=gray, arcskip=2]; + |||; + |||; + b->c [label="CONNECT_TX_COMMAND", arcskip=2]; + |||; + |||; + c:>d [label="MEDIA_STACK_CONNECT", arcskip=5], + b<-c [label="CONNECT_TX_RESPONSE", arcskip=2]; + |||; + |||; + a<:b [label="MEDIA_STACK_CONNECT", arcskip=5], + z<-b [label="CONNECT_RX_RESPONSE",linecolor=gray,textcolor=gray, arcskip=2]; + |||; + |||; + y<:z [label="CONNECT_RX_RESPONSE",linecolor=gray,textcolor=gray, arcskip=1]; + c<:d [label="genavb_stream_create", arcskip=2]; + |||; + |||; + a:>b [label="genavb_stream_create", arcskip=2]; + |||; + |||; +} diff --git a/doc/avdecc_control.dot b/doc/avdecc_control.dot new file mode 100644 index 0000000..3553191 --- /dev/null +++ b/doc/avdecc_control.dot @@ -0,0 +1,84 @@ +digraph { +fontname=Helvetica; +node[fontname=Helvetica]; +size="!1,1"; +label="AVDECC control paths"; +rankdir=LR; +ordering=out; +streamer[shape=Mrecord, label="Talker/Listener\nstreaming\napplication", rank=source]; +controller[shape=Mrecord, label="Controller\napplication", rank=source]; +controlled[shape=Mrecord, label="Talker/Listener\ncontrol\napplication", rank=source]; + +subgraph cluster2 { + shape=box; + label="Media stack channel"; + node[shape="record"]; + media[label=" CONNECT | DISCONNECT"]; +} + +subgraph cluster1 { + shape=box; + label="Controller channel"; + node[shape="record"]; + ACMP1[label="ACMP| commands | responses"]; + ADP1[label="{ ADP messages}"]; + AECP1[label="AECP| commands | responses"]; +} + +subgraph cluster3 { + shape=box; + label="Controlled channel"; + node[shape="record"]; + AECP2[label="AECP| commands | responses"]; +} + +subgraph cluster0 { + shape="Mrecord"; + rank=sink; + node[shape=Mrecord]; + label = " AVDECC\n component"; + labeljust = r; + ACMP + ADP[label="
ADP| entity\ndatabase"]; + AECP +} + +network[shape=Mrecord, label="AVB\nnetwork", rank=sink]; + + +controller -> AECP1:cmd; +AECP1:rsp -> controller [constraint=false]; + +controller -> ACMP1:cmd; +ACMP1:rsp -> controller [constraint=false]; + +controller -> ADP1 [dir="both"]; + +AECP1:cmd -> AECP; +AECP -> AECP1:rsp [constraint=false]; + +ACMP1:cmd -> ACMP; +ACMP -> ACMP1:rsp [constraint=false]; + +ADP1 -> ADP:db [dir="both"]; + + +controlled -> AECP2:rsp; +AECP2:cmd -> controlled [constraint=false]; + +media:conn -> streamer [constraint=false]; +media:dis -> streamer [constraint=false]; +streamer -> media [style=invis]; + +ACMP -> media:conn [constraint=false]; +ACMP -> media:dis [constraint=false]; + +AECP -> AECP2:cmd [constraint=false]; +AECP2:rsp -> AECP; + +AECP -> network [dir="both"]; +ACMP -> network [dir="both"]; +ADP:main -> network [dir="both"]; + + +} diff --git a/doc/clock.md b/doc/clock.md new file mode 100644 index 0000000..49b2cdc --- /dev/null +++ b/doc/clock.md @@ -0,0 +1,14 @@ +Clock API usage {#clock_usage} +====================================== + +The clock API offers a high-level API to retrieve the current time for various clocks. + +* ::GENAVB_CLOCK_MONOTONIC is based on a monotonic system time and should be used when a continous time is needed. +* ::GENAVB_CLOCK_GPTP_0_0 and ::GENAVB_CLOCK_GPTP_0_1 are gPTP clocks. Respectively domain 0 and 1 for interface 0 +* ::GENAVB_CLOCK_GPTP_1_0 and ::GENAVB_CLOCK_GPTP_1_1 are gPTP clocks. Respectively domain 0 and 1 for interface 1 + +Specificities of the time returned by gPTP clocks: +* it has no guarantee to be monotonic and discontinuities may happen +* it is not guaranteed to be synchronized to gPTP Grandmaster. Making sure local system is well synchronized to the gPTP Grandmaster is out-of-scope of this API. + +The current time is retrieved using ::genavb_clock_gettime64 function which provides a 64 bits nanoseconds time value. diff --git a/doc/config.md b/doc/config.md new file mode 100644 index 0000000..c1d6198 --- /dev/null +++ b/doc/config.md @@ -0,0 +1,312 @@ +GenAVB/TSN Configuration {#genavb_config} +================================== + +The GenAVB/TSN stack is configured at startup through different configuration files. + + +--- + + +# Main Configuration + +The main configuration file for GenAVB/TSN stack is under: `/etc/genavb/config` + +The general parameters defined in the `/etc/genavb/config` are the following: + +### Auto start +Parameter | Format & Range + ---------------------- | :----------- +CFG_AUTO_START | 0 or 1 (default 1) + +Set to 1 to start the AVB stack automatically at system startup time. Set to 0 will require starting the AVB stack manually (# avb.sh start). + +> Note: This option is only functional on systems with sysvinit as startup program. For target with systemd, a genavb-tsn.service is available to handle automatic startup. + +### Setting system clock to be gPTP-based +Parameter | Format & Range + ---------------------- | :----------- +CFG_USE_PHC2SYS | 0 or 1 (default 1) + +Set this parameter to 1 in order to base the system clock on gPTP time. This will ensure that applications (like gstreamer) using the system clock have their process gPTP-based. + +### GenAVB/TSN endpoint configuration mode + +On targets that support both Endpoint TSN and Endpoint AVB, the configuration mode is set by the `GENAVB_TSN_CONFIG` parameter (depending on available configs): + +> GENAVB_TSN_CONFIG=1 +GENAVB_TSN_CONFIG 1 - Endpoint TSN +GENAVB_TSN_CONFIG 2 - Endpoint AVB + + +--- + + +# GenAVB/TSN Endpoint Configuration profile + +The `GENAVB_TSN_CFG_FILE` previously set in `/etc/genavb/config` (depending on the configuration mode: Endpoint AVB or Endpoint TSN) is made of a pair of configuration files. + +Parameter | Format & Range | Description + ----------------------| :----------- | :----------- +APPS_CFG_FILE | Text string (default `/etc/genavb/apps-listener-alsa.cfg` for Endpoint AVB and `/etc/genavb/apps-tsn-network-controller.cfg` for Endpoint TSN) | Points to a apps-*.cfg file containing a demo configuration (media apps to use, controller option...). It is parsed by the startup script avb.sh +GENAVB_CFG_FILE | Text string (default `/etc/genavb/genavb-listener.cfg`) | **Only valid for Endpoint AVB** : Points to a genavb-*.cfg file containing the configuration of the AVB stack, and is parsed by the avb application. + +There are several pre-defined profiles, which can be used to set the role and behavior of the Endpoint node. Set these parameters according to the description of each demo preparation steps. + +--- + + +# Application profile parameters +The profile parameters are defined in the apps-*.cfg files of the `/etc/genavb/` directory. Several profiles are already defined, targeted to suit example usage scenarii. The meaning of the parameters is as follows: + +### Defining a media application +Parameter | Format & Range + ------------------------------ | :----------- +CFG_EXTERNAL_MEDIA_APP | Text string (default alsa-audio-app for Endpoint AVB and tsn-app for Endpoint TSN) +CFG_EXTERNAL_MEDIA_APP_OPT | Text string in double quotes (default "") + +These parameters indicate the name of the media application binary to be loaded along with the GenAVB/TSN stack, and its associated input parameters. +The media application binary must be present in the file system of the target under the /usr/bin repository. The application name must be specified as absolute path or be in the default PATH. + +### Defining a control application (Valid only for Endpoint AVB) +Parameter | Format & Range + ------------------------------ | :----------- +CFG_USE_EXTERNAL_CONTROLS | 0 or 1 (default 0) +CFG_EXTERNAL_CONTROLS_APP | Text string (default genavb-controls-app) + +These parameters enable and indicate the name of the control application binary to be loaded along with the AVB stack. Set this parameter to 1 and specify the control application name to enable such application type to handle AECP controls commands such as volume control from the other entity. +The control application binary must be present in the file system of the target under the /usr/bin repository. The application name must be specified as absolute path or be in the default PATH. + + +--- + + +# Endpoint AVB stack profile parameters + +The Endpoint AVB stack parameters are defined in the genavb-*.cfg files of the `/etc/genavb/` directory. Several profiles are already defined, targeted to suit example usage scenarii. +Cfg files are organized by sections (noted [XXX]), containing several key/value pairs. + +Use # to comment lines. +When a section or key/value pair is missing in the cfg file, default values will be used. +The meaning of the section and keys is as follows: + +### Section [AVB_GENERAL] +Key | Value & Range | Description + ---------------- | :----------- | :----------- +log_level | Text string. Can be 'crit', 'err', 'init', 'info', 'dbg'. (default: info) | Sets log level for all stack components +disable_component_log | Text string: 'avtp', 'avdecc', 'srp', 'gptp', 'common', 'os', 'api', or 'none'. (default : none) | Disable log for one or more components. Use a comma separated list to specify several components, eg: disable_component_log = avtp, avdecc +log_monotonic | Text string: 'disabled' or 'enabled'. (default: disabled) | Controls if monotonic timestamps are included in the logs output + +### Section [AVB_AVDECC] +Key | Value & Range | Description + ---------------| :----------- | :----------- +enabled | 0 or 1 (default 1) | Enables AVDECC stack component. If disabled, all other [AVB_AVDECC_xxx] sections parameters are unusued and the media application is responsible for stream creation/destruction, as well as MSRP stream declaration. 0 - disabled, 1 - enabled. +milan_mode | 0 or 1 (default 0) | Enables AVDECC stack to run according to AVnu MILAN specifications, otherwise it works according to IEEE 1722.1 specification. 0 - IEEE 1722.1 Mode, 1- AVnu Milan mode. +association_id | Unsigned, 64 bits (default 0) | Association ID to advertise for the local entities (see custom AEM parameters below). +max_entities_discovery | Unsigned (min 8, max 128, default 16) | Maximum number of discoverable AVDECC entities + + +**Below options are only available for milan_mode = 0** + +Key | Value & Range | Description + ---------------| :----------- | :----------- +srp_enabled | 0 or 1 (default 1) | Enables SRP API's to be called directly from AVDECC stack component (and stream reservations to be declared/redrawn by AVDECC/ACMP as streams are connected/disconnected). If disabled the media application is responsible for handling stream reservations. 0 - disabled, 1 - enabled. +fast_connect | 0 or 1 (default 0) | Enables fast connect mode (Listener entity will connect streams automatically, based on saved talker information). 0 - disabled, 1 - enabled. +btb_demo_mode | 0 or 1 (default 0) | Back to back demo mode: 0 - disabled, 1 - enabled, default: disabled. When enabled this entity will connect to the first talker advertised on the network. This is a demo mode (the other entity shouldn't have this mode enabled). This will force fast_connect to 1. 0 - disabled, 1 - enabled. + +### Section [AVB_AVDECC_ENTITY_1] +Key | Value & Range | Description + ---------------- | :----------- | :----------- +entity_id | \. \ is unsigned, 64bits (default: 0) | ID of the entity (see custom AEM parameters below). This is a EUI-64 identifier unique to the entity. +entity_file | Text string. Format: `/etc/genavb/\.aem` | File to be used as AVDECC AEM entity description. There are several pre-defined descriptions, which can be used to set the capabilities of the AVB node. Default : none. See chapter 4 below for more details about AEM entities. +channel_waitmask | bitmask, between 0 and 7 included. default: 0 | The stack waits for the specified control channels to be opened by the application before starting AVDECC for the entity: bit 0 => MEDIA_STACK channel, bit 1 => CONTROLLER channel, bit 2 => CONTROLLED channel. +max_listener_streams | Unsigned (min 1, max 64, default 8) | Maximum number of listener streams supported for this AVDECC entity. +max_talker_streams | Unsigned (min 1, max 64, default 8) | Maximum number of talker streams supported for this AVDECC entity. +max_inflights | Unsigned (min 5, max 128, default 5) | Maximum number of simultaneous inflight commands for this AVDECC entity. +max_unsolicited_registratons | Unsigned (min 1, max 64, default 8) | Maximum number of unsolicited notifications registration for this AVDECC entity. +max_ptlv_entries | Unsigned (min 1, max 179, default 16) | Maximum number of tracked clock ids in the path trace by AVDECC + + +**Below options are only available for milan_mode = 0** + +Key | Value & Range | Description + ---------------- | :----------- | :----------- +talker_entity_id_list | \[,\,\]. \ is unsigned, 64 bits (default : none) | Talker entity ID list. Used only in fast-connect mode. +talker_unique_id_list | \[,\,\]. \ is unsigned, 16 bits (default : none) | Talker unique ID list. Used only in fast-connect mode. +listener_unique_id_list | \[,\,\]. \ is unsigned, 16 bits (default : none) | Listener unique ID list. Used only in fast-connect mode. +valid_time = 60 | Unsigned (min 2, max 62, default 62) | The valid time period for the AVDECC entity in seconds. +max_listener_pairs | Unsigned (min 1, max 512, default 10) | Maximum number of connected listeners per talker for this AVDECC entity. + +### Section [AVB_AVDECC_ENTITY_2] +It is possible to define a second AEM entity on the same node. If used, the second entity can only be an AVDECC controller. +Key | Value & Range | Description + --------------- | :----------- | :----------- +entity_file | Text string. Format: `/etc/genavb/controller.aem` (default : none) | File to be used as AVDECC AEM entity description. +channel_waitmask | bitmask, between 0 and 7 included. default: 0 | The stack waits for the specified control channels to be opened by the application before starting AVDECC for the entity: bit 0 => MEDIA_STACK channel, bit 1 => CONTROLLER channel, bit 2 => CONTROLLED channel. + + +### Custom AEM parameters +Except for a few specific items, all the fields of an AEM entity take their value from the AEM +entity description file (see section 4 below).The parameters that behave differently are: +* the association ID +* the entity ID +* the entity model ID + +#### Association ID +If the association_id parameter of the [AVB_AVDECC] section of the configuration file is set to a +non-zero value, it will override any value set in the AEM definition files, for all entities on +the endpoint. If it is unset or 0, the value stored in each entity AEM definition file will be used +instead. + +> For each entity, a non-zero association_id value remains dependent on the +> ADP_ENTITY_ASSOCIATION_ID_SUPPORTED flag being set in the entity_capabilities field of the AEM +> definition file. If that flag isn't set, the association_id will be forced to 0 at init time. The +> ADP_ENTITY_ASSOCIATION_ID_VALID flag will also be set or cleared at init time, based on the value +> of the assocation_id field (independently of the initial value stored in the AEM definition file). + +#### Entity ID +The entity ID will be set according to the following, in decreasing order of priority: +* the entity_id parameter of the [AVB_AVDECC_ENTITY_n] section of the configuration file, +if it is set and non-zero, +* the entity ID value stored in the AEM definition file, if it is non-zero, +* a dynamic value computed from the Freescale OUI-24 (00:04:9f), the last 3 bytes of the eth0 MAC +address, and the entity index on the endpoint (0 or 1). + +(so the configuration file value, if present, will override any value which may have been set otherwise). + +> To ensure uniqueness, the entity ID shall be set by the entity manufacturer using an EUI block that it owns. + +#### Entity model ID + The entity model ID will be set to: + * the entity model ID stored in the AEM definition file, if it is non-zero, + * the entity ID value otherwise. + + +--- + + +# Defining an AVDECC entity description + +The current descriptions are using the following: + +Audio characteristics: +* AVTP Format IEC 61883-6 AM824 or AAF Format (when specified) +* 2 audio channels +* 48 KHz sample rate +* 24 bits + +Video characteristics: +* AVTP Format IEC 61883-4 +* MPEG2-TS compressed audio/video +* Maximum bit rate 24Mbps + +The pre-defined descriptions files are as follows: + +* Audio talker/listener, single stream + + AVDECC Entity configuration file: listener_talker_audio_single.aem + +* Audio Milan talker/listener, single stream + + AVDECC Entity configuration file: listener_talker_audio_single_milan.aem + +* Audio Milan listener, single stream + + AVDECC Entity configuration file: listener_audio_single_milan.aem + +* Audio Milan talker, single stream + + AVDECC Entity configuration file: talker_audio_single_milan.aem + +* Audio listener, single stream + + AVDECC Entity configuration file: listener_audio_single.aem + +* Audio talker, single stream + + AVDECC Entity configuration file: talker_audio_single.aem + +* Audio talker + listener, multiple streams (8 + 8 streams) + + AVDECC Entity configuration file: talker_listener_audio_multi.aem + +* Audio talker + listener, multiple streams, AAF format (8 + 8 streams) + + AVDECC Entity configuration file: talker_listener_audio_multi_aaf.aem + +* Audio/Video listener, single stream + + AVDECC Entity configuration file: listener_video_single.aem + +* Audio/Video talker, single stream + + AVDECC Entity configuration file: talker_video_single.aem + +* Raw Audio + MPEG2-TS talker, dual stream + + AVDECC Entity configuration file: talker_audio_video.aem + +* Controller + + AVDECC Entity configuration file: controller.aem + + +### AEM configuration file format +The AVDECC entity description is stored in some AEM configuration files, used to set the capabilities of the AVB node. The binary format of the AEM file is as follows. +The file starts with a fixed header with information on the number of each descriptor type, present in the file, and their size. All descriptors of a given type have the same size. There are 38 types of descriptors as specified in IEEE Std 1722.1-2013, section 7.2. + +offset (bytes) | width (bytes) | Descriptor type | name | endianess + --------------| :-----------: | :--------------: | :-------------: | :---------: +0 | 2 | 0 | size_0 (bytes) | LE +2 | 2 | 0 | total_0 | LE +4 | 2 | 1 | size_1 (bytes) | LE +6 | 2 | 1 | total_1 | LE +... | - | - | - |- +37 * 4 | 2 | 37 | size_37 (bytes) | LE +37 * 4 + 2 | 2 | 37 | total_37 | LE + +This header is followed by the actual descriptors. All descriptors follow the format described in IEEE Std 1722.1-2013 section 7.2. If the total number of descriptors is 0, for a given descriptor type, the descriptor is skipped. For variable sized descriptors there may be more space used in the file than required in memory. + +offset (bytes) | width (bytes) | Descriptor type | Descriptor Number + ---------------------------------------------------| :-----------: | :-------------: | :-----------------: +152 | size_0 | 0 | 0 +152 + size_0 | size_0 | 0 | 1 +... | - | - | - +152 + (total_0 - 1) * size_0 | size_0 | 0 | total_0 - 1 +152 + total_0 * size_0 | size_1 | 1 | 0 +152 + total_0 * size_0 + size_1 | size_1 | 1 | 0 +... | - | - | - +152 + total_0 * size_0 + (total_1 - 1) * size_1 | size_1 | 1 | total_1 - 1 + + +--- + + +# AVB Startup Options +The AVB stack accepts command line options which are documented in this section. + +* -v displays program version +* -f \ path and filename to read configuration from (eg: `/etc/genavb/genavb-listener.cfg`) +* -h prints help text + + +--- + + +# Endpoint, entity and application constraints +The AVB stack currently supports the following AVDECC roles: +* talker (both for streaming and control), +* listener(both for streaming and control), +* controller. + +A single entity can combine several of these roles. +On a Linux endpoint, those roles may be handled by one or several applications using the AVB stack API, but channel communication constraints between the stack and the applications impose the following rules: +* genavb_control_open(.., .., AVB_CTRL_AVDECC_CONTROLLER) may only be called once (there can be only a single open instance in the entire system), +* genavb_control_open(.., .., AVB_CTRL_AVDECC_CONTROLLED) may only be called once (there can be only a single open instance in the entire system), +* genavb_control_open(.., .., AVB_CTRL_AVDECC_MEDIA_STACK) may only be called once (there can be only a single open instance in the entire system), +* a single control channel instance (opened with ::genavb_control_open) may only handle a single entity. + +If we translate those rules in terms of entities, this means we are currently limited to at most 2 entities per endpoint: +* one being a streaming entity (talker, listener, or a single entity with both roles), +* one being a controller. +Having more than 2 is not possible, and having one talker and one listener or 2 talkers, or 2 listeners, or 2 controllers isn't either. + +Examples: +* one listener entity, 1 streaming application, 1 controlled application => OK +* one listener entity, one controller entity, 1 streaming application, 1 controller and controlled application => OK +* one talker/listener entity, 1 application handling listener and talker streams as well as the controlled role => OK +* one talker entity, 1 application handling streaming, 2 applications for control => INCORRECT +* one talker/listener entity, 1 application for talker streams, one for listener streams => INCORRECT +* one talker entity, one controller entity, 1 application for talker streams, 1 for talker control, 1 for controller => OK +* one talker entity, one listener entity, any application combination => INCORRECT +* one listener/talker entity, one controller entity, one application handling everything => OK +* one listener/talker/controller entity, one application handling everything => OK + +> Note: For extra correctness, one "application" in this context should actually be understood as one instance of the AVB library, as created by genavb_init. diff --git a/doc/control.md b/doc/control.md new file mode 100644 index 0000000..060472f --- /dev/null +++ b/doc/control.md @@ -0,0 +1,418 @@ +Control API usage {#control_usage} +============================ + +# Introduction + +The control API has been designed as a generic interface to enable the configuration and control of various features of the +GenAVB stack by an external application. + +It is a bi-directional message-passing interface, allowing the user-space application and the GenAVB stack to exchange +(send, receive) control messages through different control channels. Six channel types are currently defined (see @ref genavb_control_id_t): +* @ref GENAVB_CTRL_AVDECC_MEDIA_STACK : this channel can be used by a talker or listener application to be notified about +stream connections/disconnections done through AVDECC (either by an external controller, or through the fast-connect mechanism). +* @ref GENAVB_CTRL_AVDECC_CONTROLLER : to be used by an AVDECC controller application. +* @ref GENAVB_CTRL_AVDECC_CONTROLLED : to be used by an application running on a talker or listener, for AECP communication +(such as volume control, media track selection, play/stop control...). +* @ref GENAVB_CTRL_MSRP : this channel can be used by a talker or listener application to establish stream reservations and be notified +about the status of existing reservations on the network. If AVDECC is not being used, this API becomes mandatory to be able to +transmit and receive AVTP streams in an AVB network. +* @ref GENAVB_CTRL_MVRP : this channel can be used by a talker or listener application to establish VLAN registrations. +* @ref GENAVB_CTRL_CLOCK_DOMAIN : this channel can be used by a talker or listener application to set the clock source of a clock +domain and receive clock domain status indications. +* @ref GENAVB_CTRL_GPTP : this channel can be used by a talker or listener application to retrieve grand master information from GPTP +stack component. + + +Messages are represented by raw memory buffers, with an additional [message type](@ref genavb_msg_type_t) parameter to define their +type, and a msg_len parameter to specify their length. The following message types are currently available: +* @ref GENAVB_MSG_MEDIA_STACK_CONNECT and @ref GENAVB_MSG_MEDIA_STACK_DISCONNECT +* @ref GENAVB_MSG_AECP +* @ref GENAVB_MSG_ACMP_COMMAND and @ref GENAVB_MSG_ACMP_RESPONSE +* @ref GENAVB_MSG_ADP +* @ref GENAVB_MSG_LISTENER_REGISTER, @ref GENAVB_MSG_LISTENER_DEREGISTER, @ref GENAVB_MSG_LISTENER_RESPONSE, @ref GENAVB_MSG_LISTENER_STATUS, @ref GENAVB_MSG_TALKER_REGISTER, @ref GENAVB_MSG_TALKER_DEREGISTER, @ref GENAVB_MSG_TALKER_RESPONSE and @ref GENAVB_MSG_TALKER_STATUS, +* @ref GENAVB_MSG_VLAN_REGISTER, @ref GENAVB_MSG_VLAN_DEREGISTER and @ref GENAVB_MSG_VLAN_RESPONSE +* @ref GENAVB_MSG_ERROR_RESPONSE +* @ref GENAVB_MSG_CLOCK_DOMAIN_SET_SOURCE, @ref GENAVB_MSG_CLOCK_DOMAIN_RESPONSE, @ref GENAVB_MSG_CLOCK_DOMAIN_GET_STATUS and @ref GENAVB_MSG_CLOCK_DOMAIN_STATUS +* @ref GENAVB_MSG_GM_GET_STATUS, @ref GENAVB_MSG_GM_STATUS + +Each message type has a corresponding message C structure (defined in detail in the GenAVB public headers) and +is usually valid on one specific control channel (with some exceptions). Messages are further divided into commands, +responses and indications. Commands/responses are used in pairs (with one response always received after a command is sent) +while indications can be sent/received at any time. The following table describes the mapping of message types to control +channels and their properties. + +### Message types properties +Message type | Message structure | Control channel(s) | Direction | Type +:------------------- | ---------- | - | - | - +Generic message type (1)| genavb_media_stack_msg | GENAVB_CTRL_AVDECC_MEDIA_STACK | - | - +MEDIA_STACK_CONNECT | genavb_msg_media_stack_connect | GENAVB_CTRL_AVDECC_MEDIA_STACK | FROM stack | Indication +MEDIA_STACK_DISCONNECT | genavb_msg_media_stack_disconnect | GENAVB_CTRL_AVDECC_MEDIA_STACK | FROM stack | Indication +Generic message type (1)| genavb_controller_msg | GENAVB_CTRL_AVDECC_CONTROLLER | - | - +Generic message type (1)| genavb_controlled_msg | GENAVB_CTRL_AVDECC_CONTROLLED | - | - +AECP (2) | genavb_aecp_msg | GENAVB_CTRL_AVDECC_CONTROLLER, GENAVB_CTRL_AVDECC_CONTROLLED | TO/FROM stack | Any +ACMP_COMMAND (2) | genavb_acmp_command | GENAVB_CTRL_AVDECC_CONTROLLER | FROM stack | Command +ACMP_RESPONSE (2) | genavb_acmp_response | GENAVB_CTRL_AVDECC_CONTROLLER | TO stack | Response +ADP (2) | genavb_adp_msg | GENAVB_CTRL_AVDECC_CONTROLLER | TO/FROM stack | Any +Generic message type (1)| genavb_msg_msrp | GENAVB_CTRL_MSRP | - | - +LISTENER_REGISTER | genavb_msg_listener_register | GENAVB_CTRL_MSRP | TO stack | Command +LISTENER_DEREGISTER | genavb_msg_listener_deregister | GENAVB_CTRL_MSRP | TO stack | Command +LISTENER_RESPONSE | genavb_msg_listener_response | GENAVB_CTRL_MSRP | FROM stack | Response +LISTENER_STATUS | genavb_msg_listener_status | GENAVB_CTRL_MSRP | FROM stack | Indication +TALKER_REGISTER | genavb_msg_talker_register | GENAVB_CTRL_MSRP | TO stack | Command +TALKER_DEREGISTER | genavb_msg_talker_deregister | GENAVB_CTRL_MSRP | TO stack | Command +TALKER_RESPONSE | genavb_msg_talker_response | GENAVB_CTRL_MSRP | FROM stack | Response +TALKER_STATUS | genavb_msg_talker_status | GENAVB_CTRL_MSRP | FROM stack | Indication +Generic message type (1)| genavb_msg_mvrp | GENAVB_CTRL_MVRP | - | - +VLAN_REGISTER | genavb_msg_vlan_register | GENAVB_CTRL_MVRP | TO stack | Command +VLAN_DEREGISTER | genavb_msg_vlan_deregister | GENAVB_CTRL_MVRP | TO stack | Command +VLAN_RESPONSE | genavb_msg_vlan_response | GENAVB_CTRL_MVRP | FROM stack | Response +Generic message type (1)| genavb_msg_clock_domain | GENAVB_CTRL_CLOCK_DOMAIN | - | - +CLOCK_DOMAIN_SET_SOURCE | genavb_msg_clock_domain_set_source | GENAVB_CTRL_CLOCK_DOMAIN | TO stack | Command +CLOCK_DOMAIN_RESPONSE | genavb_msg_clock_domain_response | GENAVB_CTRL_CLOCK_DOMAIN | FROM stack | Response +CLOCK_DOMAIN_GET_STATUS | genavb_msg_clock_domain_get_status | GENAVB_CTRL_CLOCK_DOMAIN | TO stack | Command +CLOCK_DOMAIN_STATUS | genavb_msg_clock_domain_status | GENAVB_CTRL_CLOCK_DOMAIN | FROM stack | Response/Indication +GM_GET_STATUS | genavb_msg_gm_get_status | GENAVB_CTRL_GPTP | TO stack | Command +GM_STATUS | genavb_msg_gm_status | GENAVB_CTRL_GPTP | FROM stack | Response/Indication +ERROR_RESPONSE (3) | genavb_msg_error_response | all | FROM stack | Response + +(1) These message structures provide an easy way for the application to allocate a buffer large enough to receive any of the +supported message types for a given control channel. + +(2) These messages encapsulate PDU's as defined in IEEE 1722.1-2011 and need further decoding to identify their specific type. + +(3) This is a generic error response, valid in any control channel type. + +A control channel is opened using the @ref genavb_control_open API. The API returns a handle that must be used in all other control API's. + +Message reception by the application is done with the @ref genavb_control_receive function. This call is non-blocking, so +it will report an error if no message is available on the given control channel. + +\if LINUX + +On the Linux platform, a file descriptor +can be obtained for a control channel (using @ref genavb_control_rx_fd or @ref genavb_control_tx_fd), so regular system calls (poll, select, epoll...) may be used to make sure messages +are available. + +\else + +A callback can be registered using ::genavb_control_set_callback which is called when received data is available. The callback must not block and should notify another task which performs the actual data reception. After the callback has been called it must be re-armed by calling ::genavb_control_enable_callback. This should be done after the application has read all data available/written all data available (and never from the callback itself). + +\endif + +Sending a message can be performed in two different ways by the application: +* using @ref genavb_control_send : the application will post the message, and the call will return as soon as the message is sent without +waiting for any reply from the stack. If a response is expected, @ref genavb_control_receive should be called afterwards to fetch it. +There is no guarantee however that the first available message will be the response to the command just sent: other messages may +arrive in-between, so the application should make sure those are handled as well. +* using @ref genavb_control_send_sync : this call will post the message and wait for a response from the stack. This can be simpler +for the application when a response is needed and it doesn't need to do anything else in the mean time, since the stack will take +care of providing the response message matching the command. Other messages can still be fetched normally afterwards using +@ref genavb_control_receive. + +There are no restrictions on which channel types and message types can be used with each mode, so it is entirely up to the +application to use whichever mode makes more sense for its use cases. + +To close a control channel use @ref genavb_control_close. + +# Restrictions + +Each control channel may be opened by a single application in the system. Also, if multiple threads send/receive from the same +control channel, locking should be provided by the application. Typically, responses to commands sent from thread X, can be received +by thread Y. + +-- + +# Clock Domain control API + +This API uses a @ref GENAVB_CTRL_CLOCK_DOMAIN control channel and interacts with the AVTP stack component. It allows to configure the +clock domain (@ref genavb_clock_domain_t) in a way close to IEEE 1722.1-2011 description. + +### Clock domain + +The clock domain can refer to an audio or a video clock domain. Any member of a clock domain is synchronous to the domain clock source +and is able to exchange media data with the other members. + +The clock domain ID (@ref genavb_clock_domain_t) makes the link between the streams and the clock devices registered +to the media clock layer of genAVB stack. The clock device drivers are platform specific and documented in the platform specific +section. + +Note: the domain ID is used by the media clock platform specific layer to find clock device drivers which have been registered. + + +### Setting the clock domain source + +The application can set the clock source of the domain through the @ref GENAVB_MSG_CLOCK_DOMAIN_SET_SOURCE command with +@ref genavb_msg_clock_domain_set_source structure. This command receives the response @ref GENAVB_MSG_CLOCK_DOMAIN_RESPONSE with +@ref genavb_msg_clock_domain_response. The clock domain is considered configured and in a valid state only if status field of @ref +genavb_msg_clock_domain_response is equal to @ref GENAVB_SUCCESS. + +_Important_: The clock domain needs to be configured before creating streams. If @ref GENAVB_MSG_CLOCK_DOMAIN_SET_SOURCE command +is used while active streams are configured, only a valid status from @ref genavb_msg_clock_domain_response guarantees that all +configured streams are in a valid state. If the return status is not successful, the clock domain is considered to be in an invalid +state and streaming is disabled. It is the application responsability to send an other @ref GENAVB_MSG_CLOCK_DOMAIN_SET_SOURCE +until a succesful response is received. + +### Source types + +Two top-level source types are available @ref genavb_clock_source_type_t + +@ref GENAVB_CLOCK_SOURCE_TYPE_INTERNAL is used for an internal clock source. It is used for master endpoints which need to provide +their own timing information to the domain. +2 types of internal clocks are supported as defined in @ref genavb_clock_source_local_id_t : +* @ref GENAVB_CLOCK_SOURCE_AUDIO_CLK +The timing source is the physical audio clock of the audio codec associated to the domain. This source type requires a generation + clock device associated to the audio codec clock with HW support. The association between a domain ID and the audio codec is + described in the platform specific section. It is used when the media frames are played/captured to/from a HW audio device. +* @ref GENAVB_CLOCK_SOURCE_PTP_CLK +The timing source is a perfect gPTP based clock generated in software. It is generally used when the media frames are played/captured +to/from a file. This clock source has a clock device which is not associated to a given domain ID. + +@ref GENAVB_CLOCK_SOURCE_TYPE_INPUT_STREAM is used when the clock source is a remote stream. The remote stream can be any listener stream of +any format. It is used by slave endpoints which need to synchronise their clock domain to a remote master. This source type requires a +recovery clock device associated to the audio codec. The association between a domain ID and the audio codec is described in the +platform specific section. + + +### Clock source types summary +Clock source type | Clock source type local ID | Requirement | Typical use case +:------------------- | ---------- | ----------- |----------------- +GENAVB_CLOCK_SOURCE_TYPE_INTERNAL | GENAVB_CLOCK_SOURCE_AUDIO_CLK | Generation clock device | Master, audio codec +GENAVB_CLOCK_SOURCE_TYPE_INTERNAL | GENAVB_CLOCK_SOURCE_PTP_CLK | None (software) | Master, slave, file server +GENAVB_CLOCK_SOURCE_TYPE_STREAM | | Recovery clock device | Slave, audio codec + + +### Indications + +It is possible to get status on the clock domain by listening @ref GENAVB_MSG_CLOCK_DOMAIN_STATUS indications +(using @ref genavb_control_receive). + +Note: These messages are in beta state. + +-- + +# MSRP control API + +This API uses a @ref GENAVB_CTRL_MSRP control channel and interacts with the SRP stack component. It allows a Talker/Listener +application to make stream reservations on an AVB network (through SRP as specificed in 802.1Qat-2010). The API also allows +the application to be notified of existing reservations (made by other endpoints) and their status. If the AVDECC stack component +is enabled, stream reservations are done automatically (for streams connected through AVDECC) and it's not required for the +application to use this API. + +### Talker streams + +A Talker can register streams on the network through the @ref GENAVB_MSG_TALKER_REGISTER command. To deregister a stream the +@ref GENAVB_MSG_TALKER_DEREGISTER command is used. Both commands receive the same response @ref GENAVB_MSG_TALKER_RESPONSE. + +To learn about the existing Listeners for registered Talker streams, the application can listen to @ref GENAVB_MSG_TALKER_STATUS +indications (using @ref genavb_control_receive). + +### Listener streams + +A Listener can register the streams it wishes to receive through the @ref GENAVB_MSG_LISTENER_REGISTER command. To deregister +a stream the @ref GENAVB_MSG_LISTENER_DEREGISTER command is used. Both commands receive the same response @ref GENAVB_MSG_LISTENER_RESPONSE. + +To learn about the Talker status for registered Listener streams, the application can listen to @ref GENAVB_MSG_LISTENER_STATUS +indications (using @ref genavb_control_receive). + +-- + +# GPTP control API + +This API uses a @ref GENAVB_CTRL_GPTP control channel and interacts with the GPTP stack component. It allows a Talker/Listener +application to retrieve the status of the GPTP Grand Master. The API also allows the application to be notified of Grand Master status changes. + +-- + +# AVDECC control API + +The API has been designed to expose most of the AVDECC functionality to a user-space application, while hiding out protocol +interaction details. + +It can be used by an AVDECC controller to: +* connect and disconnect streams, +* get information about AVDECC entities, +* send AECP commands and receive responses. + +It can be used by an application on an AVDECC talker or listener entity to: +* receive stream connection/disconnection messages, +* receive AECP commands and send responses. + +![](avdecc_control.png) + +--- + +## Exchanges on an GENAVB_CTRL_AVDECC_MEDIA_STACK channel +This channel can be used by a media stack application to be notified about stream connections/disconnections. + +When a stream is connected, the stack will send a @ref GENAVB_MSG_MEDIA_STACK_CONNECT message, whose content maps to the @ref genavb_stream_params structure. +It normally contains all the necessary parameters to initialize the media stack. It is also the same structure that is required +by the @ref genavb_stream_create function to create an AVTP stream, so it may be passed as is by the media stack application. + +When a stream is disconnected, the stack will send a @ref GENAVB_MSG_MEDIA_STACK_DISCONNECT message, mapping the @ref genavb_msg_media_stack_disconnect structure. + +> The @ref GENAVB_CTRL_AVDECC_MEDIA_STACK channel should not be confused with the stream creation API: it is only used to receive stream connections/disconnections +messages received through the AVDECC protocol, and it is then the responsibility of the application to actually request creation/destruction of the stream +in the AVTP component of the stack, using the @ref genavb_stream_create and @ref genavb_stream_destroy functions. + + +--- + +## ACMP exchanges on an GENAVB_CTRL_AVDECC_CONTROLLER channel +ACMP messages can be used by a controller to request the connection or disconnection of AVTP streams on the AVB network, and to get information about the existing streams. + +ACMP commands may be sent by a controller application through the @ref GENAVB_CTRL_AVDECC_CONTROLLER channel, using the @ref GENAVB_MSG_ACMP_COMMAND message type and +the @ref genavb_acmp_command structure. Only commands that a controller entity may send (according the specification) are allowed: +* @ref ACMP_CONNECT_RX_COMMAND , +* @ref ACMP_DISCONNECT_RX_COMMAND , +* @ref ACMP_GET_TX_STATE_COMMAND , +* @ref ACMP_GET_RX_STATE_COMMAND , +* @ref ACMP_GET_TX_CONNECTION_COMMAND . + +The stack will post the ACMP responses on that same channel, using the @ref GENAVB_MSG_ACMP_RESPONSE message type and the @ref genavb_acmp_response structure. + +The content of the @ref genavb_acmp_command and @ref genavb_acmp_response structures closely matches the ACMP specification, so the AVDECC standard should be +referred to for additional details. To simplify the task of the application however, the GenAVB stack will handle all the network-related tasks, such as retransmission on timeout. + +> Since there are no notification mechanisms in the ACMP specification, the stack will never send any ACMP messages to the application without receiving an ACMP command from the application first. + + +--- + +## ADP exchanges on an GENAVB_CTRL_AVDECC_CONTROLLER channel +Handling ADP messages will allow a controller application to maintain an up-to-date view of the AVDECC entities discovered on the AVB network by the GenAVB stack. + +ADP messages use the @ref GENAVB_MSG_ADP message type and the @ref genavb_adp_msg structure. + +Each time an entity becomes available on or departs the network, the stack will send an ADP message to the application, with the msg_type field of the @ref genavb_adp_msg structure +set respectively to @ref ADP_ENTITY_AVAILABLE or @ref ADP_ENTITY_DEPARTING. The info field of that structure will contain additional details on the entity. ADP_ENTITY_AVAILABLE messages +will also be sent by the stack if the entity was updated in any way, but not for the simple refresh messages sent periodically by the remote entity. +The stack however will age the entities according to the (non)-received messages, and it remove entites that are no longer visible from its database. When this happens, the application will also be notified of the departing entity. + +The application may also request specific information by sending ADP messages to the stack: +* Messages from the application with the msg_type (of the @ref genavb_adp_msg structure) set to ADP_ENTITY_DISCOVER will trigger a dump of all currently discovered entities. + Each discovered entity will be notified to the application with its own @ref ADP_ENTITY_AVAILABLE message (one single entity per message). + This can be useful on startup for the application to sync its state with that of the stack. + The total field can be used by the application to determine when its view of the network is complete (i.e. when it has received information about total different entities). +* To get information about a specific entity, the application shall set msg_type to @ref ADP_ENTITY_AVAILABLE, info.entity_id to the AVDECC id of the requested entity, with the + other fields being unused. The stack will reply with the corresponding entity if it exists. + +If no entity was found or if an invalid msg_type was used, a reply will be sent by the stack to the application with msg_type set to @ref ADP_ENTITY_NOTFOUND. + + +--- + +## AECP exchanges +AECP communication may happen through either the @ref GENAVB_CTRL_AVDECC_CONTROLLER or @ref GENAVB_CTRL_AVDECC_CONTROLLED channels, depending on the type of +the entity sending/receiving the messages. +A controller can use AECP messages to request specific information about an entity, or request actions to be performed by the entity. +A talker or Listener application can use AECP messages to reply to commands from a controller (such as get/set volume, etc), and also notify the stack of changes +within the entity (such as a volume change event from outside AVDECC), so that the stack can then forward the message to +interested entities (usually controllers registered for unsolicited notifications). + +AECP messages use the @ref GENAVB_MSG_AECP message type and the @ref genavb_aecp_msg structure. Of all the AECP types, only the AEM ones are +currently supported by the stack. Because of the breadth of the AECP AEM protocol, the various AECP AEM PDUs are mapped as is into the buf +field of the @ref genavb_aecp_msg structure. Additional information such as the AECP msg_type and AECP status is also available in that structure. +Additional details about those fields and the format of AECP AEM PDUs may be found in the AVDECC standard. To simplify the task of the +application however, the GenAVB stack will handle all the network-related tasks, such as retransmission on timeout and controller locking/availability checks. +A controller application can therefore simply send commands (and possibly wait for a response), without having to worry about other details. +Similarly, a talker or listener may simply send messages to the stack when it needs to, without worrying about potential network protocol issues. + + +### AEM model support +The AEM models to be used by the stack are currently defined and generated at compile-time through the aem-manager host application, whose source code +is provided in the apps/linux folder.The GenAVB stack will load AEM model files at run-time (based on command-line parameters for the Linux platform). +More than one AEM model may be loaded, will the following constraints: +* at most one controller entity may be declared per endpoint, +* at most one non-controller entity (talker or listener) may be declared per endpoint. + +This means in practice, only up to 2 entities may be declared per endpoint. Those limitations might be lifted in future versions of the stack. + +Dynamically updating the AEM model at run-time is also not possible currently, but changing the value(s) stored in a CONTROL descriptor is. The stack maintains its own view of the whole AEM model, so +that ::AECP_AEM_CMD_GET_CONTROL commands can be replied to without involving the application. However, the application _has to_ notify the stack of changes in the values stored in a CONTROL descriptor, by +sending the relevant ::AECP_AEM_CMD_SET_CONTROL unsolicited response in reaction to a change made outside AVDECC. For consistency however the stack will never update the values immediately upon reception of +a ::AECP_AEM_CMD_SET_CONTROL command from a controller: this ensures the application can perform additional validation steps on the requested value and report any issue back to the stack.In conclusion, CONTROL +descriptor values are updated either by the value received from a controller in the ::AECP_AEM_CMD_SET_CONTROL command (once stack validate it on reception and application respond with ::AECP_AEM_SUCCESS +in ::genavb_aecp_msg.status) or by a valid value from a ::AECP_AEM_CMD_SET_CONTROL unsolicited response sent by application. + +> Only 2 value types are currently fully supported by the stack for CONTROL descriptors: LINEAR_UINT8 and UTF8. + + +### AECP exchanges on an GENAVB_CTRL_AVDECC_CONTROLLER channel +On such a channel: +* Only commands shall be sent by the application (::genavb_aecp_msg.msg_type == ::AECP_AEM_COMMAND), +* Only responses will be sent by the stack and received by the application (::genavb_aecp_msg.msg_type == ::AECP_AEM_RESPONSE). + +::aecp_pdu.controller_entity_id, ::aecp_pdu.sequence_id will be overwritten by the stack before sending the message on the network and can be safely ignored by the application. +The stack will check the entity ID (::aecp_pdu.entity_id) provided by the application, and: +* If it is 0, it will send the command to the first discovered entity (talker or listener), +* Otherwise, it will make sure the entity is currently available (based on the database of entities maintained by the ADP component of the stack), and send the command to it. +The stack will report an error if no entity could be found. + +Aside from the entity ID check and network protocol handling, the stack acts mostly as a pass-through in this case, and the commands/responses will be sent from/to the application without any modification. + + +### AECP exchanges on an GENAVB_CTRL_AVDECC_CONTROLLED channel +On such a channel: +* Only responses shall be sent by the application, +* Only commands will be sent by the stack and received by the application. + +Not all commands are supported by the stack for network reception yet. When possible, commands are also handled directly without disturbing the application. As a result, only a +limited set of commands may be received by the application. The table below lists the commands currently supported by the stack for reception from the network for talker or listener entities. +Command | Handled by the stack | Passed to the application + --------------------------- | :------------------: | :-----------: +AECP_AEM_CMD_READ_DESCRIPTOR | Y | N +AECP_AEM_CMD_ACQUIRE_ENTITY | Partial* | N +AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION | Y | N +AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION | Y | N +AECP_AEM_CMD_SET_CONTROL | Y | Y +AECP_AEM_CMD_GET_CONTROL | Y | N +AECP_AEM_CMD_START_STREAMING | Y | Y +AECP_AEM_CMD_STOP_STREAMING | Y | Y +Others | N | N + +> *The ACQUIRE_ENTITY command is implemented, with the following limitations: +> * No CONTROLLER_AVAILABLE checks are being performed in this case +> * The acquiring controller will not be sent unsolicited responses as mandated by the AVDECC specification, unless it did register for unsolicited notifications as well. +> * Only whole entities can be acquired, and not other descriptors. + + +#### Handling of SET_CONTROL command +When receiving a ::AECP_AEM_CMD_SET_CONTROL command from the network, the stack will (in that order): +* Make sure the requested CONTROL descriptor can be found in the AEM model loaded in memory and is not read-only, +* Make sure the requested value is valid, +* Pass the command to the application, +* Send IN_PROGRESS responses to the requesting controller until the application provides a response.In case the application does not respond with a proper msg after +sending AECP_CFG_MAX_AEM_IN_PROGRESS messages (A total of 10 seconds period) the stack will send a ::AECP_AEM_CMD_SET_CONTROL failure response. + +#### Handling of START_STREAMING and STOP_STREAMING commands +When receiving a START_STREAMING or STOP_STREAMING command from the network, the stack will (in that order): +* Make sure the requested STREAM descriptor can be found in the AEM model loaded in memory and is not read-only, +* Pass the command to the application, +* Send IN_PROGRESS responses to the requesting controller until the application provides a response. + + +#### Responses from the application +A talker or listener application may send responses through an @ref GENAVB_CTRL_AVDECC_CONTROLLED channel at any time. The entity ID field of the response message coming from +the application will be ignored: the stack will determine the entity the response should originate from on its own, under the assumption that only one non-controller entity is +declared on the endpoint (only one non-controller entity per endpoint is currently supported). If no non-controller entity has been declared on the endpoint, the message will +be ignored. If the response is not related to an AECP command previously received by the application, the U bit (in aecp_aem_pdu.u_command_type) shall be set to 1 by the application, and +the ::aecp_pdu.controller_entity_id, ::aecp_pdu.sequence_id fields will be ignored by the stack. Otherwise (the response is in reply to a received command), the U bit shall be set to 0 and +the ::aecp_pdu.controller_entity_id, ::aecp_pdu.sequence_id fields shall match the value from the command message. + +After receiving a (response) message from the application and finding the corresponding entity, the stack will (in that order): +* For ::AECP_AEM_CMD_SET_CONTROL commands only: + * make sure the CONTROL descriptor exists in the AEM model of the entity + * On a Response with ::AECP_AEM_SUCCESS in ::genavb_aecp_msg.status: + - If the U bit **is not** set in aecp_aem_pdu.u_command_type (A regular ::AECP_AEM_RESPONSE): update the CONTROL descriptor with the value received in the command message into the + descriptor and send back a response with it. + - If the U bit **is set** in aecp_aem_pdu.u_command_type (An unsolicited ::AECP_AEM_RESPONSE): validate the new value coming from the application and update the CONTROL descriptor with the new value. + * On a Response **with other than** ::AECP_AEM_SUCCESS in ::genavb_aecp_msg.status: Send response with the current value in the descriptor + +* If the U bit is **not** set in aecp_aem_pdu.u_command_type, try and find a match between the response and a previously received command, based on ::aecp_pdu.controller_entity_id and ::aecp_pdu.sequence_id. + If a previously received command is found, the response, with values from the descriptor, will be sent to the originating controller entity, and the stack will stop sending IN_PROGRESS responses for that command.Otherwise (no previously received command is found), the response is not sent and no update is performed. If the U bit is set, those checks will be skipped. +* If the command the response is for is subject to unsolicited notifications, loop through all controllers registered for unsolicited notifications, and send an unsolicited response to them. + + +--- + + diff --git a/doc/frame_preemption.md b/doc/frame_preemption.md new file mode 100644 index 0000000..ec6832c --- /dev/null +++ b/doc/frame_preemption.md @@ -0,0 +1,35 @@ +Frame Preemption API usage {#frame_preemption_usage} +====================================== + +This API is used to configure the frame preemption feature (as defined in IEEE 802.3br-2016 and IEEE 802.1Qbu-2016) of a given network port. +Frame preemption allows Express traffic to interrupt on-going Preemptable traffic. Preemption can be enabled or disabled at the port level. +Frame priorities can be configured as either Preemptable or Express. + +This feature requires specific hardware support and returns an error if the corresponding network port doesn't support it. + +The 802.1Q configuration is set using ::genavb_fp_set: +* port_id: the logical port ID. +* type: the ::genavb_fp_config_type_t configuration type. Must be set to ::GENAVB_FP_CONFIG_802_1Q. +* config: the ::genavb_fp_config configuration. Only the write members of the ::genavb_fp_config_802_1Q union should be set. + +Frame priorities mapped to the same Traffic class must have the same Preemptable/Express setting. + +The 802.1Q configuration is retrieved using ::genavb_fp_get: +* port_id: the logical port ID. +* type: the ::genavb_fp_config_type_t configuration type. Must be set to ::GENAVB_FP_CONFIG_802_1Q. +* config: the ::genavb_fp_config configuration. Only the read members of the ::genavb_fp_config_802_1Q union will be set when the function returns. + +The 802.3 configuration is set using ::genavb_fp_set: +* port_id: the logical port ID. +* type: the ::genavb_fp_config_type_t configuration type. Must be set to ::GENAVB_FP_CONFIG_802_3. +* config: the ::genavb_fp_config configuration. Only the write members of the ::genavb_fp_config_802_3 union should be set. + +If verification is enabled (verify_disable_tx == false), only after the verification process is complete successfully (status_verify == ::GENAVB_FP_STATUS_VERIFY_SUCCEEDED), is frame preemption enabled in the port. + +In the current release, Frame Preemption can not be enabled at the same time as Scheduled Traffic. + + +The 802.3 configuration is retrieved using ::genavb_fp_get: +* port_id: the logical port ID. +* type: the ::genavb_fp_config_type_t configuration type. Must be set to ::GENAVB_FP_CONFIG_802_3. +* config: the ::genavb_fp_config configuration. Only the read members of the ::genavb_fp_config_802_3 union will be set when the function returns. diff --git a/doc/gptp.md b/doc/gptp.md new file mode 100644 index 0000000..59e5dc9 --- /dev/null +++ b/doc/gptp.md @@ -0,0 +1,295 @@ +gPTP and SRP usage and configuration {#gptp_usage} +==================================== + +# Description + +The GenAVB/TSN stack includes support for a gPTP component which implements the Generalized Precision Time Protocol as per IEEE 802.1AS-2020 specification +(a profile of PTPv2 IEEE 1588 standard) in order to establish an accurate time reference among all network nodes. + +The gPTP component supports two different configurations: + * Endpoint package (AED-E) : gPTP endpoint (tsn) capabilities (Slave or Grand Master) + * Bridge package (AED-B): gPTP bridge (tsn -b) capabilities (Grand Master or Transparent clock) + +The following features are supported: + * Slave or GrandMaster capabilities + * Best master clock selection algorithm (BMCA) for dynamic selection of the highest quality clock (GrandMaster) + * Support for Automotive profiles per AVnu-AutoCDSFunctionalSpec v1.4 (static configuration, no BMCA, static pdelay, dynamic intervals) + * Multiple gPTP Domains and Common Link Delay Service (CMLDS) + +Also, the stack includes support for an SRP component which implements the Stream Reservation Protocol (SRP) as per IEEE Std 802.1Q-2018 Clause 35. +The SRP component supports two different configurations: + * Endpoint package (AED-E) : SRP endpoint (tsn) capabilities + * Bridge package (AED-B): SRP bridge (tsn -b) capabilities + +On Linux, the gPTP and SRP stacks are part of the tsn process including also the Management component: + +# Usage + + tsn [options] + Options: + -b starts bridge component + -v display program version + -f path and filename to read gPTP configuration from (eg: /etc/genavb/fgptp.cfg for endpoint stack or /etc/genavb/fgptp-br.cfg for bridge stack) + -s path and filename to read SRP configuration from (eg: /etc/genavb/srp.cfg for endpoint stack or /etc/genavb/srp-br.cfg for bridge stack) + -h print this help + + Notes: + The gPTP stack component will be launched once the first time the AVB stack is started. + Depending on the node configuration (endpoint/bridge) one or more tsn processes are automatically started during system startup process. + The default gptp configuration file (e.g.: fgptp.cfg) is for general gPTP parameters as well as domain 0 parameters. To enable other domains, new files must be created with '-N' appended to the filename (e.g.: 'fgptp.cfg-1' for domain 1). + The tsn applications can also be stopped and started manually at any time using the following set of commands: + tsn.sh stop + tsn.sh start + + +# SRP Configuration files: /etc/genavb/srp[-br].cfg + +### Section [SRP_GENERAL] + +Key | Value & Range | Description + ---------------- | :----------- | :----------- +log_level | Text string. Can be 'crit', 'err', 'init', 'info', 'dbg'. (default: info) | Sets log level for srp stack components +sr_class_enabled | Text string: Can be 'A', 'B', 'C', 'D' or 'E'. (default: A,B) | Select enabled SR classes, must be 2 different classes separated by a comma (order has no impact as alphabetical order will be applied) + +### Section [MSRP] +Key | Value & Range | Description + ---------------- | :----------- | :----------- +enabled | 0 or 1 (default 1) | Set this configuration to 0 to disable MSRP processing on all ports + +# GPTP Configuration files: /etc/genavb/fgptp.cfg[-N] + + +### Section [FGPTP_GENERAL] + +#### Profile +The gptp stack can operate in two different modes known as 'standard' or 'automotive' profiles. + +When the 'standard' profile is selected, the gptp stack operates following the specifications described in IEEE 802.1AS. +When the 'automotive' profile is selected, the gptp stack operates following the specifications described in the AVnu +AutoCDSFunctionalSpec_1.4 which is a subset of the IEEE 802.1AS specifications optimized for automotive applications. + +The automotive environment is unique in that it is a closed system. Every network device is known prior to +startup and devices do not enter or leave the network, except in the case of failures. Because of the closed nature +of the automotive network, it is possible to simplify and improve gPTP startup performance. Specifically, +functions like election of a grand master and calculations of wire delays are tasks that can be optimized for a +closed system. + +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +profile | "standard" or "automotive" (default "standard") | Set gptp main profile. "standard" - IEEE 802.1AS specs, "automotive" - AVnu automotive profile + +#### Grand master ID +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +gm_id | 64bits EUI format (default "0x0001f2fffe0025fe") | Set static grandmaster ID in host order (used by automotive profile, ignored in case of standard profile) + +#### GPTP domain number assignment +gPTP domain number param is a per-domain parameter (defined in the default and other domains config files). +At least one domain shall be supported: domain 0, with its domain_number equal to 0. + +(see IEEE 802.1AS-2020 - 8.1 gPTP domain) + +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +domain_number | -1 to 127 (default -1 for non-zero instances) | Disable (value -1) or assign a gPTP domain number to a domain instance. + +#### Log output level +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +log_level | 0 or 1 (default 0) | Set this configuration to 1 to enable debug mode + +#### Reverse sync feature control +The Reverse Sync feature (Avnu specification) should be used for test/evaluation purpose only. +Usually to measure the accuracy of the clock synchronization, the traditional approach is to use +a 1 Pulse Per Second (1PPS) physical output. While this is a good approach, there may be cases +where using a 1PPS output is not feasible. More flexible and fully relying on SW implementation +the Reverse Sync feature serves the same objective using the standard gPTP Sync/Follow-Up messages +to relay the timing information, from the Slave back to the GM. + +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +reverse_sync | 0 or 1 (default 0) | Set to 1 to enable reverse sync. + +#### Reverse sync feature interval +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +reverse_sync_interval | 32 to 10000 (default 112) | Reverse sync transmit interval in ms units + +#### Neighbor propagation delay threshold +neighborPropDelayThresh defines the propagation time threshold, above which a port is not considered +capable of participating in the IEEE 802.1AS protocol (see IEEE 802.1AS - 11.2.2 Determination of asCapable). +If a computed neighborPropDelay exceeds neighborPropDelayThresh, +then asCapable is set to FALSE for the port. This setting does not apply to Automotive profile where a +link is always considered to be capable or running IEEE 802.1AS. + +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +neighborPropDelayThresh | 32 to 10000000 (default 800) | Neighbor propagation delay threshold expressed in ns + + +#### Statistics output interval +Key | Value & Range | Description + ----------------| :-----------: | :----------- +statsInterval | 0 to 255 (default 10) | Statistics output interval expressed in seconds. Use 0 to disable statistics. + + +### Section [FGPTP_GM_PARAMS] +This section defines the native Grand Master capabilities of a time-aware system (see IEEE 802.1AS - 8.6.2 Time-aware system attributes). +Grandmaster parameters define per-domain values (defined in separate config files). + +gmCapable defines if the time-aware system is capable of being a grandmaster. By default gmCapable is set to 1 +as in standard profile operation the Grand Master is elected dynamically by the BMCA. In case of automotive +profile gmCapable must be set on each AED node to match the required network topology (i.e. within a given +gPTP domain only one node must have its gmCapable property set to 1). + +priority1, priority2, clockClass, clockAccuracy and offsetScaledLogVariance are parameters used by the +Best Master Clock algorithm to determine which of the Grand Master capable node whithin the gPTP domain +has the highest priority/quality. Note that the lowest value for these parameters matches the highest +priority/quality. + +#### Grandmaster capable setting +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +gmCapable |  0 or 1 (default 1) | Set to 1 if the device has grandmaster capability. Ignored in automotive profile if the port is SLAVE. + +#### Grandmaster priority1 value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +priority1 | 0 to 255 (default 248 for AED-E and 246 for AED-B) | Set the priority1 value of this clock + +#### Grandmaster priority2 value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +priority2 | 0 to 255 (default 248) | Set the priority2 value of this clock + +#### Grandmaster clock class value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +clockClass | 0 to 255 (default 248) | Set the class value of this clock + +#### Grandmaster clock accuracy value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +clockAccuracy | 0x0 to 0xff (default 0xfe) | Set the accuracy value of this clock + +#### Grandmaster variance value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +offsetScaledLogVariance | 0x0 to 0xffff (default 17258) | Set the offset scaled log variance value of this clock + + +### Section [FGPTP_AUTOMOTIVE_PARAMS] +The static pdelay feature is used only if the gPTP stack operates in automotive profile configuration. + +At init time the gPTP stack's configuration file is parsed and based on neighborPropDelay_mode the specified +initial_neighborPropDelay is applied to all ports and used for synchronization until a pdelay response from the peer is +received. This is done only if no previously stored pdelay is available from the nvram database specified by nvram_file. +As soon as a pdelay response from the peer is received the 'real' pdelay value is computed, and used for current synchronization. +An indication may then be sent via callback up to the OS-dependent layer. Upon new indication the Host may update its nvram database +and the stored value will be used at next restart for the corresponding port instead of the initial_neighborPropDelay. The granularity +at which pdelay change indications are sent to the Host is defined by the neighborPropDelay_sensitivity parameter. + +In the gPTP configuration file the neighborPropDelay_mode parameter is set to 'static' by default, meaning that +a predefined propagation delay is used as described above while pdelay requests are still sent to the network. + +The 'silent' mode behaves the same way as the 'static' mode except that pdelay requests are never sent at all to the network. + +Optionally the neighborPropDelay_mode parameter can be set to standard forcing the stack to operate propagation delay +measurements as specified in the 802.1AS specifications even if the automotive profile is selected. + +(see AutoCDSFunctionalSpec-1_4 - 6.2.2 Persistent gPTP Values) + +#### PDelay mode +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +neighborPropDelay_mode | 'static' 'silent' or 'standard' (default static) | Defines pdelay mechanism used + +#### Static pdelay value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +initial_neighborPropDelay | 0 to 10000 (default 250)| Predefined pdelay value applied to all ports. Expressed in ns. + +#### Static pdelay sensitivity +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +neighborPropDelay_sensitivity | 0 to 1000 (default 10) | Amount of ns between two pdelay measurements required to trigger a change indication. Expressed in ns. + +#### Path of the nvram file +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +nvram_file | (default /etc/genavb/fgptp.nvram) | Path and nvram file name. + + +### Section [FGPTP_PORTn] +Per port settings where n represents the port index starting at n=1. + +Pdelay requests and Sync messages sending intervals have a direct impact on the system synchronization performance. +To reduce synhronization time while optimizing overall system load, two levels of intervals are defined. +The first level called 'Initial', defines the messages intervals used until pdelay values have stabilized and synchronization is achieved. +The second level called 'Operational', defines the messages intervals used once the system is synchronized. + +initialLogPdelayReqInterval and operLogPdelayReqInterval define the intervals between the sending of successive Pdelay_Req messages. +initialLogSyncInterval and operLogSyncInterval define the intervals between the sending of successive Sync messages. +initialLogAnnounceInterval defines the interval between the sending of successive Announce messages + +(see AutoCDSFunctionalSpec-1_4 - 6.2.1 Static gPTP Values) + +#### Port role +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +portRole | 'slave', 'master', 'disabled' (default disabled) | Static port role (ref. 802.1AS-2011, section 14.6.3, Table 10-1), applies to "automotive" profile only. + +#### PTP port enabled +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +ptpPortEnabled | 0 or 1 (default 1) | Set to 1 if both time-synchronization and best master selection functions of the port should be used (ref. 802.1AS-2011, sections 14.6.4 and 10.2.4.12). + +#### Rx timestamping compensation delay +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +rxDelayCompensation | -1000000 to 1000000 (default 0) | Rx timestamp compensation substracted from receive timestamp. + +#### Tx timestamping compensation delay +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +txDelayCompensation | -1000000 to 1000000 (default 0) | Tx timestamp compensation added to transmit timestamp. + +#### Initial pdelay request interval value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +initialLogPdelayReqInterval | 0 to 3 (default 0) | Set pdelay request initial interval between the sending of successive Pdelay_Req messages. Expressed in log2 unit (default 0 -> 1s). + +#### Initial sync interval value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +initialLogSyncInterval | -5 to 0 (default -3) | Set sync transmit initial interval between the sending of successive Sync messages. Expressed in log2 unit (default -3 -> 125ms). + +#### Initial announce interval value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +initialLogAnnounceInterval | 0 to 3 (default 0) | Set initial announce transmit interval between the sending of successive Announce messages. Expressed in log2 unit (default 0 -> 1s). + +#### Operational pdelay request interval value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +operLogPddelayReqInterval | 0 to 3 (default 0) | Set pdelay request transmit interval used during normal operation state. Expressed in log2 unit (default 0 -> 1s). + +#### Operational sync interval value +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +operLogSyncInterval | -5 to 0 (default -3) | Set sync transmit interval used during normal operation state. Expressed in log2 unit (default -3 -> 125ms). + +#### Pdelay Mechanism +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +delayMechanism | 'P2P', 'COMMON_P2P', 'SPECIAL' (default 'P2P' for domain 0, 'COMMON_P2P' for domains > 0)| Set peer delay mechanism associated to this port. + +#### Allowed Lost Responses +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +allowedLostResponses | 1 to 255 (default 3 if force_2011 = yes, 9 otherwise) | Set the number of Pdelay_Req messages without valid responses above which this port is considered to be not exchanging peer delay messages with its neighbor. + +#### Allowed Faults +Key | Value & Range | Description + ----------------| :-----------: | :-----------: +allowedFaults | 1 to 255 (default 9) | Set the number of faults above which asCapableAcrossDomains is set to FALSE, i.e., the port is considered not capable of interoperating with its neighbor. The term faults refers to instances where the computed mean propagation delay exceeds the threshold and/or the computation of neighborRateRatio is invalid. diff --git a/doc/help_template/HTML_PageLayout.xml b/doc/help_template/HTML_PageLayout.xml new file mode 100644 index 0000000..ad30e00 --- /dev/null +++ b/doc/help_template/HTML_PageLayout.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/help_template/html_custom.css b/doc/help_template/html_custom.css new file mode 100644 index 0000000..c25513f --- /dev/null +++ b/doc/help_template/html_custom.css @@ -0,0 +1,1448 @@ +/* The standard CSS for doxygen 1.8.5 */ + +body{ + background: none repeat scroll 0 0 #FFFFFF; + color: #51626F; + font-family: arial,sans-serif !important; + font-size: 75% !important; + font-weight: normal !important; + margin-left: 10px; + font: 400 12px/15px arial,sans-serif; + counter-reset: cnt_h1; +} +.contents, .textblock { +font: 400 12px/15px arial,sans-serif; +} +.textblock ul, .contents ul{ + list-style: none outside url('http://www.freescale.com/shared/images/misc/bullet_square1.gif'); + margin: 5px 0; + padding: 0; +} +.textblockol { + margin:5px 15px; + padding:0; + list-style-image: none; +} +.textblock li, .contents li { + margin: 0 0 2px 15px; + padding: 0 0 2px 2px; +} + +/* For hiding RHS TOC +.toc{ +display:none; +}*/ +/* For hiding RHS TOC*/ + +/* For hiding Nav Bar +.navpath{ +display:none; +}*/ +/* For hiding Nav Bar*/ +/* For hiding extra navrows */ +#navrow2, #navrow3, #navrow4, #navrow5 { +display:none; +} +/* @group Heading Levels */ + +.title { + font: 400 14px/28px arial,sans-serif; + font-size: 200%; + font-weight: bold; + margin: 9px 2px; + text-align: left; +} + +h1 { + counter-reset: cnt_h2; +} +h2, h3 { + color: #E66A08 !important; + font-size: 1.4em !important; + line-height: 1.4em !important; + text-indent: 0 !important; + margin: 18px 0 5px 0px; +} +h4, h5{ + color: #627178 !important; + font-size: 1.2em !important; + line-height: 1.2em !important; + margin: 7px 0 2px 0px; +} +h5{ + font-size: 1.0em !important; + line-height: 1.0em !important; +} +h6{ + color: #E66A08 !important; + font-size: 1.0em !important; + line-height: 1.0em !important; +} +p { + padding-bottom: 0px !important; + font: 400 12px/15px arial,sans-serif; + margin: 0px 0 12px 0px; +} +li p { + padding-bottom: 0px !important; + margin: 0px; +} + +h1:before { + content: counter(cnt_h1) ". "; + counter-increment: cnt_h1; +} + +h2:before { + /*content: counter(cnt_h1) "." counter(cnt_h2) ". "; */ + counter-increment: cnt_h2; +} + +h1, h2.groupheader { + clear: both !important; + color: #E66A08 !important; + font-size: 1.8em !important; + font-weight: bold !important; + line-height: 28px !important; + margin-top: 0.6em !important; +} + +h1, h2, h3, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; + margin-right: 15px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px cyan; +} + +dt { + font-weight: bold; +} + + +/*Hiding Tabs - Tushar +div.tabs +{ + visibility: hidden; +} */ + +div.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; +} + +p.startli, p.startdd, p.starttd { + margin-top: 2px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.qindex, div.navtab{ + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; +} + +div.qindex, div.navpath { + width: 100%; + line-height: 140%; +} + +div.navtab { + margin-right: 15px; +} + +/* @group Link Styling */ + +a { + color: #017bba; + font-weight: normal; + text-decoration: none; + /*font: 400 12px/15px;*/ +} + +.contents a:visited { + color: #4665A2; +} + +a:hover { + text-decoration: underline; +} + +a.qindex { + font-weight: bold; +} + +a.qindexHL { + font-weight: bold; + background-color: #9CAFD4; + color: #ffffff; + border: 1px double #869DCA; +} + +.contents a.qindexHL:visited { + color: #ffffff; +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.code, a.code:visited, a.line, a.line:visited { + color: #4665A2; +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: #4665A2; + /*font: 400 12px/15px;*/ +} + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +pre.fragment { + border: 0px solid #C4CFE5; + background-color: #FFFFFF; + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + word-wrap: break-word; + font-size: 9pt; + line-height: 125%; + font-family: monospace, fixed; + font-size: 105%; +} + +div.fragment { + padding: 0px; + margin: 0px; + background-color: #FFFFFF; + border: 0px solid #C4CFE5; +} + +div.line { + font-family: monospace, fixed; + font-size: 13px; + min-height: 13px; + line-height: 1.0; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line.glow { + background-color: cyan; + box-shadow: 0 0 10px cyan; +} + + +span.lineno { + padding-right: 4px; + text-align: right; + border-right: 2px solid #0F0; + background-color: #E8E8E8; + white-space: pre; +} +span.lineno a { + background-color: #D8D8D8; +} + +span.lineno a:hover { + background-color: #C8C8C8; +} + +div.ah { + background-color: black; + font-weight: bold; + color: #ffffff; + margin-bottom: 3px; + margin-top: 3px; + padding: 0.2em; + border: solid thin #333; + border-radius: 0.5em; + -webkit-border-radius: .5em; + -moz-border-radius: .5em; + box-shadow: 2px 2px 3px #999; + -webkit-box-shadow: 2px 2px 3px #999; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444)); + background-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000); +} + +div.groupHeader { + margin-left: 16px; + margin-top: 2px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + background-color: white; + margin: 0; +} + +div.contents { + margin-top: 10px; + margin-left: 12px; + margin-right: 8px; +} + +td.indexkey { + background-color: #EBEFF6; + font-weight: bold; + border: 1px solid #C4CFE5; + margin: 2px 0px 2px 0; + padding: 2px 10px; + white-space: nowrap; + vertical-align: top; +} + +td.indexvalue { + background-color: #EBEFF6; + border: 1px solid #C4CFE5; + padding: 2px 10px; + margin: 2px 0px; +} + +tr.memlist { + background-color: #EEF1F7; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaDsp { + +} + +img.formulaInl { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; +} + +/* @group Code Colorization */ + +span.keyword { + color: #008000 +} + +span.keywordtype { + color: #604020 +} + +span.keywordflow { + color: #e08000 +} + +span.comment { + color: #51626F +} + +span.preprocessor { + color: #806020 +} + +span.stringliteral { + color: #002080 +} + +span.charliteral { + color: #008080 +} + +span.vhdldigit { + color: #ff00ff +} + +span.vhdlchar { + color: #000000 +} + +span.vhdlkeyword { + color: #700070 +} + +span.vhdllogic { + color: #ff0000 +} + +blockquote { + background-color: #F7F8FB; + border-left: 2px solid #9CAFD4; + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +/* +.search { + color: #003399; + font-weight: bold; +} + +form.search { + margin-bottom: 0px; + margin-top: 0px; +} + +input.search { + font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #e8eef2; +} +*/ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #A3B4D7; +} + +th.dirtab { + background: #EBEFF6; + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid #4A6AAA; +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.memberdecls td, .fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: cyan; + box-shadow: 0 0 15px cyan; +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: #F9FAFC; + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: #555; +} + +.memSeparator { + border-bottom: 1px solid #DEE4F0; + line-height: 1px; + margin: 0px; + padding: 0px; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight { + width: 100%; +} + +.memTemplParams { + color: #4665A2; + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtemplate { + font-size: 80%; + color: #4665A2; + font-weight: normal; + margin-left: 9px; +} + +.memnav { + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; +} + +.memitem.glow { + box-shadow: 0 0 15px cyan; +} + +.memname { + font-weight: bold; + margin-left: 6px; +} + +.memname td { + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border-top: 1px solid #A8B8D9; + border-left: 1px solid #A8B8D9; + border-right: 1px solid #A8B8D9; + padding: 6px 0px 6px 0px; + color: #E66A08 !important; + font-weight: bold; + text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); + background-image:url('nav_f.png'); + background-repeat:repeat-x; + background-color: #E2E8F2; + /* opera specific markup */ + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 4px; + border-top-left-radius: 4px; + /* firefox specific markup */ + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + -moz-border-radius-topright: 4px; + -moz-border-radius-topleft: 4px; + /* webkit specific markup */ + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + -webkit-border-top-right-radius: 4px; + -webkit-border-top-left-radius: 4px; + +} + +.memdoc, dl.reflist dd { + border-bottom: 1px solid #A8B8D9; + border-left: 1px solid #A8B8D9; + border-right: 1px solid #A8B8D9; + padding: 6px 10px 2px 10px; + background-color: #FBFCFD; + border-top-width: 0; + background-image:url('nav_g.png'); + background-repeat:repeat-x; + background-color: #FFFFFF; + /* opera specific markup */ + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + /* firefox specific markup */ + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: #602020; + white-space: nowrap; +} +.paramname em { + font-style: normal; +} +.paramname code { + line-height: 14px; +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; + font: 400 12px/15px arial,sans-serif; +} + +.params .paramname, .retval .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir { + font-family: "courier new",courier,monospace; + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: #728DC1; + border-top:1px solid #5373B4; + border-left:1px solid #5373B4; + border-right:1px solid #C4CFE5; + border-bottom:1px solid #C4CFE5; + text-shadow: none; + color: white; + margin-right: 4px; + padding: 2px 3px; + border-radius: 3px; + font-size: 7pt; + white-space: nowrap; + vertical-align: middle; +} + + + +/* @end */ + +/* these are for tree view when not used as main index */ + +div.directory { + margin: 10px 0px; + border-top: 1px solid #A8B8D9; + border-bottom: 1px solid #A8B8D9; + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 0px; + vertical-align: top; +} + +.directory td.entry { + white-space: nowrap; + padding-right: 6px; + padding-top: 3px; +} + +.directory td.entry a { + outline:none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding-left: 6px; + padding-right: 6px; + padding-top: 3px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.even { + padding-left: 6px; + background-color: #F7F8FB; +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; +} + +.directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: #3D578C; +} + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: #2A3D61; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; + font: 400 12px/15px arial,sans-serif; +} + +table.doxtable td, table.doxtable th { + border: 1px solid #2D4068; + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: #CCCCCC; + color: #51626F; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} +table.fieldtable { + /*width: 100%;*/ + margin-bottom: 10px; + border: 1px solid #A8B8D9; + border-spacing: 0px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid #A8B8D9; + border-bottom: 1px solid #A8B8D9; + vertical-align: top; +} + +.fieldtable td.fieldname { + padding-top: 3px; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid #A8B8D9; + /*width: 100%;*/ +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image:url('nav_f.png'); + background-repeat:repeat-x; + background-color: #E2E8F2; + font-size: 90%; + color: #253555; + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid #A8B8D9; +} + + +.tabsearch { + top: 5px; + left: 10px; + height: 36px; + background-image: url('tab_b.png'); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul +{ + font-size: 11px; + background-image:url('tab_b.png'); + background-repeat:repeat-x; + background-position: 0 -5px; + height:30px; + line-height:30px; + color:#8AA0CC; + border:solid 1px #C2CDE4; + overflow:hidden; + margin:0px; + padding:0px; +} + +.navpath li +{ + list-style-type:none; + float:left; + padding-left:10px; + padding-right:15px; + background-image:url('bc_s.png'); + background-repeat:no-repeat; + background-position:right; + color:#364D7C; +} + +.navpath li.navelem a +{ + height:32px; + display:block; + text-decoration: none; + outline: none; + color: #283A5D; + font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; + text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); + text-decoration: none; +} + +.navpath li.navelem a:hover +{ + color:#6884BD; +} + +.navpath li.footer +{ + list-style-type:none; + float:right; + padding-left:10px; + padding-right:15px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + color:#364D7C; + font-size: 8pt; +} + + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +div.ingroups +{ + font-size: 8pt; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + background-image:url(''); + background-repeat:repeat-x; + background-color: #FFFFFF; + margin: 0px; + border-bottom: 0px solid #C4CFE5; +} + +div.headertitle +{ + padding: 5px 5px 5px 10px; +} + +dl +{ + padding: 0 0 0 10px; + font: 400 12px/15px arial,sans-serif; +} + +/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug */ +dl.section +{ + margin-left: 0px; + padding-left: 0px; +} + +dl.note +{ + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #D0C000; +} + +dl.warning, dl.attention +{ + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #FF0000; +} + +dl.pre, dl.post, dl.invariant +{ + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #00D000; +} + +dl.deprecated +{ + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #505050; +} + +dl.todo +{ + margin-left:-7px; + padding-left: 12px; + border-left:4px solid; + border-color: #00C0E0; +} + +dl.test +{ + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #3030E0; +} + +dl.bug +{ + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #C08050; +} + +dl.section dd { + margin-bottom: 6px; +} + + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; + padding: 12px; + +} + +#projectlogo img +{ + border: 0px none; +} + +#projectname +{ + font: 200% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 5px 0px; + text-align: right; + width: 100%; +} + +#projectbrief +{ + font: 120% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 5px; + text-align: left; + width: 100%; +} + +#projectnumber +{ + font: 50% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 0px; +} + +#titlearea +{ + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: 11px solid #5373B4; +} + +.image +{ + text-align: left; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +div.zoom +{ + border: 1px solid #90A5CE; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:#334975; + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; +} + +dl.citelist dd { + margin:2px 0; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: #F4F6FA; + border: 1px solid #D8DFEE; + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 20px 10px 10px; + width: 200px; +} + +div.toc li { + background: url("bdwn.png") no-repeat scroll 0 5px transparent; + font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif; + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 Arial,FreeSans,sans-serif; + color: #4665A2; + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.level2 { + margin-left: 15px; +} + +div.toc li.level3 { + margin-left: 30px; +} + +div.toc li.level4 { + margin-left: 45px; +} + +.inherit_header { + font-weight: bold; + color: gray; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + margin-top: 12px; + margin-bottom: 4px; +} + +/* tooltip related style info */ + +.ttc { + position: absolute; + display: none; +} + +#powerTip { + cursor: default; + white-space: nowrap; + background-color: white; + border: 1px solid gray; + border-radius: 4px 4px 4px 4px; + box-shadow: 1px 1px 7px gray; + display: none; + font-size: smaller; + max-width: 80%; + opacity: 0.9; + padding: 1ex 1em 1em; + position: absolute; + z-index: 2147483647; +} + +#powerTip div.ttdoc { + color: grey; + font-style: italic; +} + +#powerTip div.ttname a { + font-weight: bold; +} + +#powerTip div.ttname { + font-weight: bold; +} + +#powerTip div.ttdeci { + color: #006318; +} + +#powerTip div { + margin: 0px; + padding: 0px; + font: 12px/16px arial,sans-serif; +} + +#powerTip:before, #powerTip:after { + content: ""; + position: absolute; + margin: 0px; +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.s:after, #powerTip.s:before, +#powerTip.w:after, #powerTip.w:before, +#powerTip.e:after, #powerTip.e:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.nw:after, #powerTip.nw:before, +#powerTip.sw:after, #powerTip.sw:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; +} + +#powerTip.n:after, #powerTip.s:after, +#powerTip.w:after, #powerTip.e:after, +#powerTip.nw:after, #powerTip.ne:after, +#powerTip.sw:after, #powerTip.se:after { + border-color: rgba(255, 255, 255, 0); +} + +#powerTip.n:before, #powerTip.s:before, +#powerTip.w:before, #powerTip.e:before, +#powerTip.nw:before, #powerTip.ne:before, +#powerTip.sw:before, #powerTip.se:before { + border-color: rgba(128, 128, 128, 0); +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.nw:after, #powerTip.nw:before { + top: 100%; +} + +#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after { + border-top-color: #ffffff; + border-width: 10px; + margin: 0px -10px; +} +#powerTip.n:before { + border-top-color: #808080; + border-width: 11px; + margin: 0px -11px; +} +#powerTip.n:after, #powerTip.n:before { + left: 50%; +} + +#powerTip.nw:after, #powerTip.nw:before { + right: 14px; +} + +#powerTip.ne:after, #powerTip.ne:before { + left: 14px; +} + +#powerTip.s:after, #powerTip.s:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.sw:after, #powerTip.sw:before { + bottom: 100%; +} + +#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after { + border-bottom-color: #ffffff; + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before { + border-bottom-color: #808080; + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.s:after, #powerTip.s:before { + left: 50%; +} + +#powerTip.sw:after, #powerTip.sw:before { + right: 14px; +} + +#powerTip.se:after, #powerTip.se:before { + left: 14px; +} + +#powerTip.e:after, #powerTip.e:before { + left: 100%; +} +#powerTip.e:after { + border-left-color: #ffffff; + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.e:before { + border-left-color: #808080; + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +#powerTip.w:after, #powerTip.w:before { + right: 100%; +} +#powerTip.w:after { + border-right-color: #ffffff; + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.w:before { + border-right-color: #808080; + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + diff --git a/doc/help_template/html_footer.html b/doc/help_template/html_footer.html new file mode 100644 index 0000000..6680c1c --- /dev/null +++ b/doc/help_template/html_footer.html @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/doc/help_template/html_header.html b/doc/help_template/html_header.html new file mode 100644 index 0000000..70ecfbf --- /dev/null +++ b/doc/help_template/html_header.html @@ -0,0 +1,56 @@ + + + + + + + +$projectname: $title +$title + + + + +$treeview +$search +$mathjax + +$extrastylesheet + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
$projectname +  $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
+
+ + diff --git a/doc/init.md b/doc/init.md new file mode 100644 index 0000000..76ea892 --- /dev/null +++ b/doc/init.md @@ -0,0 +1,16 @@ +Initialization API usage {#init_usage} +======================= + +\if LINUX + +The initialization API's allow to initialize the GenAVB/TSN stack library. To initialize the library call ::genavb_init, to exit ::genavb_exit. The handle returned by ::genavb_init is +required to call most other API's. + +\else + +The initialization API's allow to initialize the GenAVB/TSN stack. To initialize the stack call ::genavb_init, to exit ::genavb_exit. The handle returned by ::genavb_init is +required to call most other API's. + +It's possible to change the default stack configuration, before calling ::genavb_init. To retrieve the default configuration use ::genavb_get_default_config (with a genavb_config structure allocated in the stack or heap). Then, override the required settings in genavb_config and call ::genavb_set_config. After calling ::genavb_init, genavb_config structure may be discarded. + +\endif diff --git a/doc/mainpage_linux.md b/doc/mainpage_linux.md new file mode 100644 index 0000000..6488e12 --- /dev/null +++ b/doc/mainpage_linux.md @@ -0,0 +1,19 @@ +Introduction {#mainpage} +============ + +This document provides user and programmer informations related to the GenAVB/TSN stack, covering the following topics: + +* Configuration and usage of the [gPTP functionality](@ref gptp_usage) part of the GenAVB stack. +* Description of the [GenAVB configuration](@ref genavb_config) parameters used by the GenAVB stack. +* Usage description of the [Initialization](@ref init_usage) API. +* Usage description of the [Control](@ref control_usage) API. +* Usage description of the [Clock](@ref clock_usage) API. +* A reference description of the [public API](@ref library) provided by the GenAVB library. +* Information on some [additional definitions](@ref other), typically network protocol headers... + +# AVB +* Supported AVTP stream formats [Stream Formats](@ref stream_formats) +* Usage description of the [Streaming](@ref streaming_usage) API. + +# TSN +* Usage description of the [Socket](@ref socket_usage) API. diff --git a/doc/mainpage_rtos.md b/doc/mainpage_rtos.md new file mode 100644 index 0000000..63892e3 --- /dev/null +++ b/doc/mainpage_rtos.md @@ -0,0 +1,20 @@ +Introduction {#mainpage} +============ + +This document provides user and programmer informations related to the GenAVB/TSN stack, covering the following topics: + +* Usage description of the [Initialization](@ref init_usage) API. +* Usage description of the [Control](@ref control_usage) API. +* Usage description of the [Scheduled Traffic](@ref scheduled_traffic_usage) API. +* Usage description of the [Frame Preemption](@ref frame_preemption_usage) API. +* Usage description of the [Clock](@ref clock_usage) API. +* Usage description of the [Timer](@ref timer_usage) API. +* A reference description of the [public API](@ref library) provided by the GenAVB library. +* Information on some [additional definitions](@ref other), typically network protocol headers... + +# AVB +* Supported AVTP stream formats [Stream Formats](@ref stream_formats) +* Usage description of the [Streaming](@ref streaming_usage) API. + +# TSN +* Usage description of the [Socket](@ref socket_usage) API. diff --git a/doc/platform_linux.md b/doc/platform_linux.md new file mode 100644 index 0000000..9486d2b --- /dev/null +++ b/doc/platform_linux.md @@ -0,0 +1,118 @@ +Linux platform specific {#platform_linux} +========================================= + +# Media clock driver + +The media clock driver provides media clock generation and recovery mechanisms for the AVB stack. +Media clock generation generates timestamps and wake-up events whereas the media clock recovery consumes +timestamps to synchronize an audio clock. + +The HW devices supported by the media clock driver are configured through the Linux device tree. +Currently, the stack supports only media clock recovery with the internal audio PLL tuning. + +## Internal audio PLL recovery + +Available on most of the recent i.MX SoCs (i.MX 6ULL, i.MX 8MM, i.MX8 MP and i.MX 93). On these platforms, the audio PLL has the capability to be tuned on-the-fly which provides a more +integrated solution that doesn't require additional external devices. The audio clock needs to be generated by the +SoC and measured using gPTP based events. + +Current implementation uses a timer block (GPT or TPM) to measure the audio clock. The capture is triggered by timestamps coming from +the remote stream and therefore provides measurements in the remote clock domain. These measurements are fed to a PID loop +used to tune the audio PLL. + +#### AVB internal recovery node + +Required properties: +- compatible: should be "fsl,avb-gpt" (for GPT node) or "fsl,avb-tpm" (for TPM) +- domain: domain ID. +- rec-channel: array of 3 elements. GPT capture channel (capture channel 1 or 2), ethernet port and ENET Timer Compare ID. +- prescale: pre-divider of GPT Timer IP. +- clocks: references to "ipg" , "per" , "audio_pll" and optionally "clk_in" clocks. +- clock-names: should be "ipg", "per", "audio_pll and "clk_in". + - "ipg" is the block clock gate + - "per" is the peripheral clock : in case of internal routing of the root clock to be derived from the audio pll, this + clock will be the counter clock. In that case, no need for the (external) clk_in clock for GPT. + - "clk_in" (only for GPT): is the clock connected to the GPT in case of no internal routing is possible and external clock is needed to feed + the gpt clock from the audio pll + - "audio_pll": The Audio PLL to be tuned + +Example with an external clk source: + + &gpt2 { + compatible = "fsl,avb-gpt"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_avb>; + prescale = <1>; + + rec-channel = <1 0 0>; /* capture channel, eth port, ENET TC id */ + domain = <0>; + + clocks = <&clks IMX6UL_CLK_GPT2_BUS>, <&clks IMX6UL_CLK_GPT2_SERIAL>, + <&clks IMX6UL_CLK_SAI2>, <&clks IMX6UL_CLK_PLL4>; + clock-names = "ipg", "per", "clk_in", "audio_pll"; + + status = "okay"; + }; + +Example with the gpt root clock derived from the audio pll internally: + + &gpt1 { + compatible = "fsl,avb-gpt"; + rec-channel = <1 0 1>; // capture channel, eth port, ENET TC id + prescale = <1>; + domain = <0>; + + clocks = <&clk IMX8MP_CLK_GPT1_ROOT>, + <&clk IMX8MP_CLK_GPT1_ROOT>, + <&clk IMX8MP_AUDIO_PLL1>; + + clock-names = "ipg", "per", "audio_pll"; + + /* Make the GPT clk root derive from the audio PLL*/ + assigned-clocks = <&clk IMX8MP_CLK_GPT1>; + assigned-clock-parents = <&clk IMX8MP_AUDIO_PLL1_OUT>; + + status = "okay"; + }; + +# AVB HW timer Interrupt + +The AVB kernel module uses a HW timer to generate a periodic interrupt (currently every 125us). +This interrupt is used to poll the FEC driver for RX frames, clean the FEC TX descriptors, perform TX software scheduling and generate an interrupt for +the media clock driver. + +#### AVB HW timer node + +Required properties: +- compatible: should be "fsl,avb-gpt" (for GPT node) or "fsl,avb-tpm" (for TPM) +- timer-channel: output compare channel (output channel 1, 2 or 3) +- prescale: pre-divider of Timer IP. +- clocks: references to "ipg" and "per" clocks. +- clock-names: should be "ipg" and "per". "ipg" is the clock gate, "per" is the peripheral clock used as counter clock + + +Example of TPM used for HW timer rooted internally from the audio pll: + + &tpm4 { + compatible = "fsl,avb-tpm"; + timer-channel = <1>; /* Use output compare channel 1*/ + prescale = <1>; + domain = <0>; + + clocks = <&clk IMX93_CLK_BUS_WAKEUP>, <&clk IMX93_CLK_TPM4_GATE>, <&clk IMX93_CLK_AUDIO_PLL>; + clock-names = "ipg", "per", "audio_pll"; + + /* Set TPM4 clock root to Audio PLL. */ + assigned-clocks = <&clk IMX93_CLK_TPM4>; + assigned-clock-parents = <&clk IMX93_CLK_AUDIO_PLL>; + + status = "okay"; + }; + +--- +**NOTE** + +The GPT and TPM drivers supports both functioning modes : Internal recovery and HW timer. +Thus, specifying both properties "rec-channel" and "timer-channel" in the AVB specific node will enable both modes. + +--- diff --git a/doc/scheduled_traffic.md b/doc/scheduled_traffic.md new file mode 100644 index 0000000..044cc6b --- /dev/null +++ b/doc/scheduled_traffic.md @@ -0,0 +1,24 @@ +Scheduled Traffic API usage {#scheduled_traffic_usage} +====================================== + +This API is used to configure the scheduled traffic feature (as defined in IEEE 802.1Q-2018 Section 8.6.9) of a given network port. +Scheduled traffic allows the configuration of a sequence of gate states per port which determine the set of traffic classes that are allowed to transmit at any given time. + +This feature requires specific hardware support and returns an error if the corresponding network port doesn't support it. + +The administrative configuration is set using ::genavb_st_set_admin_config. +Parameters: +* port_id: the logical port ID. +* clk_id: the ::genavb_clock_id_t clock ID domain. The times provided in the configuration are based on this clock reference. +* config: the ::genavb_st_get_config configuration + + enable: 0 or 1, if 0 scheduled traffic is disabled + + base_time (nanoseconds): the instant defining the time when the scheduling starts. If base_time is in the past, the scheduling will start when base_time + (N * cycle_time_p / cycle_time_q) is greater than "now". (N being the smallest integer making the equation true) + + cycle_time_p and cycle_time_q (seconds): the scheduling cycle time in rational format. It is the time when the list should be repeated. If the provided list is longer than the cycle time, the list will be truncated. + + cycle_time_ext (nanoseconds): the amount of time that the current gating cycle can be extended when a new cycle configuration is configured. + + control_list: the ::genavb_st_gate_control_entry control list (see description below) + +Control list description: +* it is an array of ::genavb_st_gate_control_entry elements of list_length length. +* operation: the gate operation ::genavb_st_operations_t. (Note: only ::GENAVB_ST_SET_GATE_STATES is supported) +* gate_states: a bit mask in which the bit in position N refers to the traffic class N. If the bit is set, the traffic class is allowed to transmit, if the bit is not set the traffic class is not allowed to transmit. +* time_interval (nanoseconds): the duration of the state defined by operation and gate_states before moving to the next entry. diff --git a/doc/socket.md b/doc/socket.md new file mode 100644 index 0000000..043e7f6 --- /dev/null +++ b/doc/socket.md @@ -0,0 +1,70 @@ +Socket API usage {#socket_usage} +====================================== + +The socket API allows the creation of a network endpoint and exchanging layer 2 network frames. +Transmit and receive sockets are handled separately and each of them requires its own (unidirectional) socket. +Parameters are set when the socket is opened and cannot be changed afterward. + +# Socket transmit {#sock_tx} + +Socket is created using ::genavb_socket_tx_open. + +Frames are transmitted using ::genavb_socket_tx function. +The function expects a continuous buffer including layer 2 header if ::GENAVB_SOCKF_RAW is set and excluding layer 2 header if not. The frame is copied before the function returns so that the provided buffer can be freed. A success return code means that the frame has been correctly queued. + +Two transmit modes are available and are set using ::genavb_sock_f_t flag: +* If ::GENAVB_SOCKF_RAW is set, it's the caller responsability to set the layer 2 header. The l2 field of ::net_address is not used. +* If ::GENAVB_SOCKF_RAW isn't set, the layer 2 header is add internally based l2 field of ::net_address and inserted automatically. + +Whatever the transmit mode, source MAC address and VLAN are overwritten by the stack. +The socket networking parameters are set in ::genavb_socket_tx_params addr field. See @ref net_addr for a complete description. Only ::PTYPE_L2 protocol type is supported in transmit. + +The socket can be closed and its associated resources freed using ::genavb_socket_tx_close. + +# Socket receive {#sock_rx} + +Socket is created using ::genavb_socket_rx_open. + +Frames are received by calling ::genavb_socket_rx, the buffer length must be at least the interface MTU size. The frames are copied to the provided buffer which needs to be continous. + +Two receive modes are available and are set using ::genavb_sock_f_t flag: +* If ::GENAVB_SOCKF_NONBLOCK is set, the socket is configured in non-blocking mode and the call to ::genavb_socket_rx will never block. If no frame is available for reading, the API returns ::GENAVB_ERR_SOCKET_AGAIN. +* If ::GENAVB_SOCKF_NONBLOCK is not set, the socket is configured in blocking mode and the call to ::genavb_socket_rx only returns if a frame has been received or because an error occured. To use the blocking mode a task needs to be dedicated to receive handling. (RTOS only) + + +The socket networking parameters are set in ::genavb_socket_rx_params addr field. See @ref net_addr for a complete description. +The following receive protocol types are available: +* ::PTYPE_L2: the frame matching is performed using the VLAN ID and the destination MAC address. The other fields of ::net_address are not used. +* ::PTYPE_OTHER: the socket will receive all the frames which have not been received by another genAVB socket. This protocol type is generally used to connect a generic TCP/IP protocol stack. + +The socket can be closed and its associated resources freed using ::genavb_socket_rx_close. + +# Flow control (non-blocking mode) {#flow_control_sock} + +In receive, two approaches are possible: +* the stream is periodic like isochronous time-sensitive traffic. The application is aware of the frames timing and therefore can just "poll" the socket at the expected time. + +* the stream has no known timing and the application needs to be notified when frames are available for read. + +In transmit, a time sensitive application expects being able to send its frames at a defined timing. If not it's generally a critical error and the send will be retried. Nonetheless, for some OS, some APIs may be available to be notified when buffer space is available for transmit. + +\if LINUX + +Using the file descriptor returned by the ::genavb_socket_rx_fd and ::genavb_socket_rx_fd functions, an application may call poll/epoll/select system calls to sleep and be woken up only when received data is available or when buffer space is available for data to transmit. + +\else + +A callback can be registered using ::genavb_socket_rx_set_callback which is called when received data is available. The callback must not block and should notify another task which performs the actual data reception. After the callback has been called it must be re-armed by calling ::genavb_socket_rx_enable_callback. This should be done after the application has read all data available/written all data available (and never from the callback itself). + +\endif + +# Network address {#net_addr} + +The ::net_address addr field is used to configure the socket networking parameters: +* ptype: the protocol type. Needs to be set to ::PTYPE_L2 or ::PTYPE_OTHER +* port: the logical port number +* vlan_id: the VLAN ID in network order (can be set to ::VLAN_VID_NONE if no vlan is required) +* priority: traffic priority (range 0 to 7) (only used in transmit) +* u.l2: the l2 address + + dst_mac: destination MAC address + + protocol: the ether type (only used in transmit) diff --git a/doc/stream_formats.md b/doc/stream_formats.md new file mode 100644 index 0000000..a55ba0e --- /dev/null +++ b/doc/stream_formats.md @@ -0,0 +1,324 @@ +Stream formats {#stream_formats} +==================================== + +The GenAVB stack supports several AVTP encapsulation formats for the transport +of Audio, Video and Control information. +At the API level, when creating streams, stream formats are specified according to +the AVDECC format defined in IEEE 1722-2016, Annex I. + +# Supported stream formats {#str_formats_0} +Format | Direction | AVTP Subtype | Media type | Media Format +:--------- |--------- |------- | ------ | ---- +IEC 61883-4 |Talker + Listener | 0x0 | Audio + Video | MPEG2-TS +IEC 61883-6 AM824 MBLA |Talker + Listener | 0x0 | Audio | AM824 24bit PCM Audio +IEC 61883-6 FLOAT32 |Talker + Listener | 0x0 | Audio | 32bit Floating point PCM Audio +IEC 61883-6 INT32 |Talker + Listener | 0x0 | Audio | 32bit Fixed point PCM Audio +AAF PCM FLOAT32 |Talker + Listener | 0x2 | Audio | 32bit Floating point PCM Audio +AAF PCM INT32 |Talker + Listener | 0x2 | Audio | 32bit Integer PCM Audio +AAF PCM INT24 |Talker + Listener | 0x2 | Audio | 24bit Integer PCM Audio +AAF PCM INT16 |Talker + Listener | 0x2 | Audio | 16bit Integer PCM Audio +AAF AES3 |Talker + Listener | 0x2 | Audio | AES3 Audio +CVF MJPEG |Listener | 0x3 | Video | MJPEG Video +CVF H264 |Talker + Listener | 0x3 | Video | H264 Video +CRF AUDIO SAMPLE |Talker + Listener | 0x4 | Audio Sample Clock | 64 bit timestamps +ACF TSCF |Talker + Listener | 0x5 | Control | ACF Control Message +ACF NTSCF |Talker + Listener | 0x82 | Control | ACF Control Message + +The following sections describe the full range of valid parameters for each of the +supported formats. + +-- +# IEC 61883-4 {#str_formats_1} + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_61883_IIDC, sf = @ref IEC_61883_SF_61883, fmt = @ref IEC_61883_CIP_FMT_4 + +Any valid MPEG2-TS stream (with one or more elementary streams) can be transmitted and received. + +-- +# IEC 61883-6 AM824 {#str_formats_2} + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_61883_IIDC, sf = @ref IEC_61883_SF_61883, fmt = @ref IEC_61883_CIP_FMT_6, +fdf_evt = @ref IEC_61883_6_FDF_EVT_AM824 + +All audio rates are supported: + +fdf_sfc = @ref IEC_61883_6_FDF_SFC_32000, @ref IEC_61883_6_FDF_SFC_44100, @ref IEC_61883_6_FDF_SFC_48000, @ref IEC_61883_6_FDF_SFC_88200, +@ref IEC_61883_6_FDF_SFC_96000, @ref IEC_61883_6_FDF_SFC_192000 + +Up to 32 channels are supported: + +dbs = [1, 32] + +Only MBLA samples are supported: + +label_mbla_cnt = \[1, 32\] (must be equal to dbs) + +label_iec_60958_cnt = 0 + +label_smpte_cnt = 0 + +label_midi_cnt = 0 + +Only non-blocking mode supported (no empty packets transmitted): + +b = 0 + +nb = 1 + +Packetization is synchronous to media clock (packets contain fixed number of samples, expect for last packet in stream): + +sc = x (ignored) + +ut = x (ignored) + +-- +# IEC 61883-6 FLOAT32 {#str_formats_3} + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_61883_IIDC, sf = @ref IEC_61883_SF_61883, fmt = @ref IEC_61883_CIP_FMT_6, +fdf_evt = @ref IEC_61883_6_FDF_EVT_FLOATING + +All audio rates are supported: + +fdf_sfc = @ref IEC_61883_6_FDF_SFC_32000, @ref IEC_61883_6_FDF_SFC_44100, @ref IEC_61883_6_FDF_SFC_48000, @ref IEC_61883_6_FDF_SFC_88200, +@ref IEC_61883_6_FDF_SFC_96000, @ref IEC_61883_6_FDF_SFC_192000 + +Up to 32 channels are supported: + +dbs = [1, 32] + +Only non-blocking mode supported (no empty packets transmitted): + +b = 0 + +nb = 1 + +Packetization is synchronous to media clock (packets contain fixed number of samples, expect for last packet in stream): + +sc = x (ignored) + +ut = x (ignored) + +-- +# IEC 61883-6 INT32 {#str_formats_4} + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_61883_IIDC, sf = @ref IEC_61883_SF_61883, fmt = @ref IEC_61883_CIP_FMT_6, +fdf_evt = @ref IEC_61883_6_FDF_EVT_INT32 + +All audio rates are supported: + +fdf_sfc = @ref IEC_61883_6_FDF_SFC_32000, @ref IEC_61883_6_FDF_SFC_44100, @ref IEC_61883_6_FDF_SFC_48000, @ref IEC_61883_6_FDF_SFC_88200, +@ref IEC_61883_6_FDF_SFC_96000, @ref IEC_61883_6_FDF_SFC_192000 + +Up to 32 channels are supported: + +dbs = [1, 32] + +Only non-blocking mode supported (no empty packets transmitted): + +b = 0 + +nb = 1 + +Packetization is synchronous to media clock (packets contain fixed number of samples, expect for last packet in stream): + +sc = x (ignored) + +ut = x (ignored) + +-- +# AAF PCM {#str_formats_5} +AVTP audio format with a PCM payload + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_AAF, format = @ref AAF_FORMAT_FLOAT_32BIT, @ref AAF_FORMAT_INT_32BIT, @ref AAF_FORMAT_INT_24BIT, @ref AAF_FORMAT_INT_16BIT + +ut = 0, 1 + +All audio rates are supported: + +nsr = @ref AAF_NSR_8000, @ref AAF_NSR_16000, @ref AAF_NSR_32000, @ref AAF_NSR_44100, +@ref AAF_NSR_48000, @ref AAF_NSR_88200, @ref AAF_NSR_96000, @ref AAF_NSR_176400, @ref AAF_NSR_192000, @ref AAF_NSR_24000 + +All bit depths are supported: + +bit_depth = 0 (for @ref AAF_FORMAT_FLOAT_32BIT format) + +bit_depth = 1, .., 32 (for @ref AAF_FORMAT_INT_32BIT format) + +bit_depth = 1, .., 24 (for @ref AAF_FORMAT_INT_24BIT format) + +bit_depth = 1, .., 16 (for @ref AAF_FORMAT_INT_16BIT format) + + +Up to 32 channels supported: + +channels_per_frame = [1, 32] + +Up to 256 samples per frame: + +samples_per_frame = [1, 256] + +-- +# AAF AES3 {#str_formats_6} +AVTP audio format with AES3 encoded audio + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_AAF, format = @ref AAF_FORMAT_AES3_32BIT + +ut = 0, 1 + +Up to 256 frames per channel are supported: + +frames_per_frame = [1, 256] + +Up to 10 streams per frame are supported: + +streams_per_frame = 1, ..., 10 + +All stream data types and frames types are supported: + +aes3_dt_ref = x (ignored) + +aes3_data_type = x (ignored) + +-- +# CVF MJPEG {#str_formats_7} + +Compressed Video format with MJPEG encoded Video + +Only Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_CVF, format = @ref CVF_FORMAT_RFC, format_subtype = @ref CVF_FORMAT_SUBTYPE_MJPEG + +Both interlaced and progressive scan types are supported: + +p = @ref CVF_MJPEG_P_INTERLACE, @ref CVF_MJPEG_P_PROGRESSIVE + +All types are supported: + +type = @ref CVF_MJPEG_TYPE_YUV422, @ref CVF_MJPEG_TYPE_YUV420 + +All valid widths/heights are supported: + +width > 0 + +height > 0 + +-- +# CVF H264 {#str_formats_8} + +Compressed Video format with H264 encoded Video + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_CVF, format = @ref CVF_FORMAT_RFC, format_subtype = @ref CVF_FORMAT_SUBTYPE_H264 + +Custom format parameters: + +For H264 Listener, a custom field (rsvd1) enables a proprietary format, always set it to 0. +Default value is 0 for IEEE1722-2016 format specification. + +-- +# CRF AUDIO SAMPLE {#str_formats_9} + +Clock Reference format with audio sample clock events + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_CRF, type = @ref CRF_TYPE_AUDIO_SAMPLE + +timestamp_interval = 1, ..., 600 + +timestamps_per_pdu = 1, ..., 8 + +pull = @ref CRF_PULL_1_1, @ref CRF_PULL_1000_1001, @ref CRF_PULL_1001_1000, @ref CRF_PULL_24_25, @ref CRF_PULL_25_24, @ref CRF_PULL_1_8 + +base_frequency = 1, ..., 536870911 Hz + +The timestamp frequency (base_frequency x pull / timestamp_interval) must respect: + +300 Hz < frequency < 48000 Hz + +-- +# ACF TSCF {#str_formats_11} + +AVTP Control Format support for Time Synchronous Control Format + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_TSCF + +m = 0 + +type_0,1,2,3 = @ref ACF_MSG_TYPE_FLEXRAY, @ref ACF_MSG_TYPE_CAN, @ref ACF_MSG_TYPE_CAN_BRIEF, @ref ACF_MSG_TYPE_LIN, @ref ACF_MSG_TYPE_MOST, @ref ACF_MSG_TYPE_GPC +@ref ACF_MSG_TYPE_SERIAL, @ref ACF_MSG_TYPE_PARALLEL, @ref ACF_MSG_TYPE_SENSOR, @ref ACF_MSG_TYPE_SENSOR_BRIEF, @ref ACF_MSG_TYPE_AECP, @ref ACF_MSG_TYPE_ANCILLARY, @ref ACF_MSG_TYPE_USER + +t0v = 0, 1 + +t1v = 0, 1 + +t2v = 0, 1 + +t3v = 0, 1 + +-- +# ACF NTSCF {#str_formats_12} + +AVTP Control Format support for Non Time Synchronous Control Format + +Both Talker and Listener endpoints are supported: + +direction = @ref AVTP_DIRECTION_LISTENER, @ref AVTP_DIRECTION_TALKER + +Base format parameters: + +v = @ref AVTP_VERSION_0, subtype = @ref AVTP_SUBTYPE_NTSCF + diff --git a/doc/streaming.md b/doc/streaming.md new file mode 100644 index 0000000..783b630 --- /dev/null +++ b/doc/streaming.md @@ -0,0 +1,295 @@ +Streaming API usage {#streaming_usage} +====================================== + +The streaming API makes it possible to create, destroy AVTP streams, and to send and receive data +to/from those streams. + +Although the AVTP protocol is inherently packet-oriented, the GenAVB stack +hides the encapsulation/decapsulation work from the application, and instead exposes a +stream-oriented API: the data is viewed as a continuous stream of bytes, in order to relieve the +application from most of the low-level protocol details. Additional information, if present (lost +data, end of frames for video streams, etc), is conveyed "out-of-band", as an array of ::genavb_event +provided alongside the data bytestream. + +Streams are created using the ::genavb_stream_create function (see below). Following the AVTP logic, +a stream represents a unidirectional flow of data between a Talker (the entity sending data over +the AVB network) and a Listener (the entity receiving that data). When data is ready to be sent or +received (see below @ref flow_control), the +application may use ::genavb_stream_send, \if LINUX ::genavb_stream_send_iov, \endif ::genavb_stream_receive \if LINUX, +::genavb_stream_receive_iov \endif functions to send or receive data, depending on the stream direction. When +a stream is no longer needed, it may be destroyed with the ::genavb_stream_destroy function. + + + +# Stream creation {#stream_creation} +Creation of an AVTP stream on an endpoint can be done using the ::genavb_stream_create function. This +function must be provided with an ::genavb_stream_params structure, which may be initialized in two +different ways: +* Static mode +* AVDECC mode + +### Static mode +The application must set all fields of ::genavb_stream_params by itself. Some helpers macros are +provided in the genavb header files to ease that process, in particular for the +::genavb_stream_params.format. + +### AVDECC mode +The :genavb_stream_params structure is provided to the application by the AVDECC component of the +GenAVB stack, using an ::GENAVB_MSG_MEDIA_STACK_CONNECT message sent to the application on a +::GENAVB_CTRL_AVDECC_MEDIA_STACK channel. The application can receive the message (which maps to the +::genavb_stream_params structure) using the ::genavb_control_receive call. See @ref control_usage for +details on how to receive such messages. Macros are also available to help extract information +from the ::genavb_stream_params.format field. +When relying on AVDECC, an ::GENAVB_MSG_MEDIA_STACK_CONNECT message is triggered in several + +situations: +* Fast-connect mode: this mode is described in section 8.2.2.1.1 of the IEEE 1722.1-2013 standard. +A listener sends an ::ACMP_CONNECT_TX_COMMAND to a talker once it discovers a talker that +matches the saved state on the listener. On the Linux platform, that saved state (entity id, +stream unique id) can be provided to the GenAVB stack through run-time options. +* Back-to-Back (BTB) mode: this is a non-standard variation of Fast-connect, where the listener +tries to connect to the first talker discovered on the network. This mode is used in several +GenAVB demos where it is known in advance the setup has only one talker advertising itself +on the network. +* Controller-connect mode: this mode is described in section 8.2.2.1.3 of the IEEE 1722.1-2013 +spec. A controller initiates the stream connection by sending a ::ACMP_CONNECT_RX_COMMAND to a +listener, and receives a ::ACMP_CONNECT_RX_RESPONSE from that listener once the stream has +been successfully connected. + +Once a listener decides to connect a stream (based on one of the previous situations) the sequence +of events remains the same in all cases, and is described by the solid black arrows in the +following diagram. + +![ACMP controller-connect and fast-connect modes](acmp_connect.png) +Diagram legend: +* Gray events are only present in controller-connect mode. +* Black events are present in all modes (fast-connect, BTB, controller-connect). +* single-line arrows: network packets. +* dual-line arrows: messages/function calls between customer application and GenAVB stack. + + +# Flow control {#flow_control} + +\if LINUX + +Using the file descriptor returned by the ::genavb_stream_fd function, an application may call +poll/epoll/select system calls to sleep and be woken up only when received data is available or +when buffer space is available for data to transmit. The amount of data that triggers a +wake-up is configured through the batch_size argument of ::genavb_stream_create. The GenAVB stack +only processes data one AVTP packet at a time, so the configured batch size is always a +multiple of the payload size of an AVTP packet for the given stream. +With VBR streams (such as compressed video formats like 61883-4, CVF/MJPEG), using a timeout with +poll/epoll/select system calls is strongly recommended, to ensure that no stale data remains in +the stack because the batch size hasn't been reached yet (see below for a more detailed +discussion). + +\else + +A callback can be registered using ::genavb_stream_set_callback which is called when received data is available or when buffer space is available for transmit. The callback must not block and should notify another task which performs the actual data reception/transmission. After the callback has been called it must be re-armed by calling ::genavb_stream_enable_callback. This should be done after the application has read all data available/written all data available (and never from the callback itself). +The amount of data that triggers a wake-up is configured through the batch_size argument of ::genavb_stream_create. The GenAVB stack only processes data one AVTP packet at a time, so the configured batch size is always a multiple of the payload size of an AVTP packet for the given stream. + +\endif + +# Receiving data (listener stream) {#rx_data} +Reception of stream data can be done through ::genavb_stream_receive \if LINUX and ::genavb_stream_receive_iov. +These functions behave the same, except for the way the data buffers are passed along: +\else + + +\endif +* ::genavb_stream_receive accepts a single pointer to the memory area where data should be copied, +along with the buffer length, + +\if LINUX +* ::genavb_stream_receive_iov uses an array of ::genavb_iovec, making it possible to use a +scatter-gather scheme and have data be copied directly into various memory buffers. + +Both functions are non-blocking, so they may return less data than requested if not enough is +available. + +\else + +This function is non-blocking, so it may return less data than requested if not enough is +available. + +\endif + +If the event* arguments are set, events that may have occurred in the stream are returned as +well. Common events may be timestamping information, end-of-frames, but also non-recurring +events/errors, such as packet loss. To ease the processing on the application side, some events +force "short reads", where the stack returns less data than requested even if more was +available. When that happens, reading the last returned event provides an easy way for the +application to determine what happened and take appropriate action. + +Different stream formats behave in different ways, so some types of events may or may not be +relevant in all cases. The following sections list the various events available for each +stream format and their meaning. + +### Audio +#### 61883-6 +TBD + +#### AAF +TBD + +### Video +#### 61883-4 +* AVTP timestamps are normally present at the beginning of every MPEG Transport Packet (188 +bytes), unless the ::AVTP_TIMESTAMP_INVALID flag is set in the event mask. +* ::AVTP_PACKET_LOST is set when data was lost. The GenAVB stack considers data was lost +when the AVTP sequence numbers of successive packets are not contiguous. This event forces a short +read. + +Because the GenAVB stack only wakes up the application after at least batch_size bytes are +available, an application that doesn't use a timeout with poll/epoll/select system calls may +receive some data after its presentation time has expired, because the data remained in the GenAVB +stack as part of a partial batch. This may for example happen: +* when reaching the end of a video, +* with sparse streams consisting of long periods of silence between bursts of data +It is therefore recommended to always set a timeout with poll/epoll/select, and when a timeout +occurs, to read the pending data (whose amount will be less than batch_size). The timeout value +can be determined based on the additional buffering that is done within the application to process +the data: for example, with a Gstreamer pipeline that adds several hundreds of ms latency, a value +of 100ms would be a reasonable value to make sure data is received in time without starving the +Gstreamer pipeline, while also preventing the timeout from happening all the time (which would +negate the advantages of using poll/epoll/select). + + +#### CVF/MJPEG +* ::AVTP_PACKET_LOST is set when data was lost. This event forces a short read. The GenAVB stack +considers data was lost in several situations: + * when the AVTP sequence numbers of successive packets are not contiguous, + * when the fragment_offset fields of the MJPEG headers of successive packets show a discontinuity + or an overlap. +* ::AVTP_END_OF_FRAME is set on the last byte of a video frame. This is determined by the +GenAVB stack based on the M bit of the AVTP header. That event also forces a short read, to make +it easier for the application to group together data for a single NALU. + + +#### CVF/H264 +* ::AVTP_PACKET_LOST is set when data was lost. This event forces a short read. The GenAVB stack +considers data was lost when the AVTP sequence numbers of successive packets are not contiguous. +* ::AVTP_END_OF_FRAME is set on the last byte of a NALU. This is determined by the +GenAVB stack based on: + * The last FU-A packet. + * A single NALU packet + * The Marker bit. + * The last NALU of a STAP packet. If a STAP packet contains several NALUs, only the last one will have an event. The application needs to do custom h264 parsing to get NALUs seperated. +That event also forces a short read, to make it easier for the application to group together data for a single frame. +For H264, the timestamps passed to the application layer correspond to H264 timestamps as defined in IEEE1722-2016 Section 8.5.3.1 (and not avtp timestamps) +Thus, ::AVTP_TIMESTAMP_INVALID is used to indicate the validity of the h264 timestamp. + +### Control + +#### ACF/NTSCF +The following events can be propagated to the media interface: +* ::AVTP_PACKET_LOST is set when data was lost. + +#### ACF/TSCF +The following events can be propagated to the media interface: +* ::AVTP_TIMESTAMP_INVALID/::AVTP_TIMESTAMP_UNCERTAIN are set to indicate the validity of the avtp timestamp +* ::AVTP_PACKET_LOST is set when data was lost + +> Note: if none of the ::AVTP_TIMESTAMP_INVALID/::AVTP_TIMESTAMP_UNCERTAIN/::AVTP_PACKET_LOST events are set, +a valid timestamp can be read from the events array. + +# Sending data (talker stream) {#tx_data} +Sending stream data can be done through ::genavb_stream_send \if LINUX and ::genavb_stream_send_iov. +These functions behave the same as their receive counterparts, except they are used for sending +\else + + +This function behave the same as its receive counterparts, except it's used for sending +\endif +data instead of receiving data. + +Compared to the receive direction, another set of events is used to reflect the different +requirements of the talker side. + + +### Audio +#### 61883-6 +TBD + +#### AAF +TBD + +### Video +#### 61883-4 +AVB stream reservations (using SRP) usually result in fairly small packets being sent at a +fairly high rate. For example, a 24Mbps Class A SRP reservation for a 61883-4 stream would be done +by using a max AVTP payload size of 384 bytes (2 MPEG TS packets, (188+4)*2), and a rate of 8kpps. +However, because a 61883-4 stream is usually VBR, the actual rate is most of the time lower. +As a consequence, the GenAVB stack needs to have timing information for each packet in order +to respect that variable rate (and to provide accurate timestamps inside the packets). + +This packetization scheme results in the following constraints: +* ::AVTP_SYNC events with valid timestamps should be added to mark the beginning of every MPEG +Transport Packet, to ensure the stack has the information it needs to send AVTP packets at an +accurate time and with valid AVTP timestamps. +* ::AVTP_FLUSH events should be added as the first event when calling +::genavb_stream_send \if LINUX / ::genavb_stream_send_iov\endif, if the associated data should be sent immediately even if +it results in a partial packet being sent. This is useful for example: + * when reaching the end of a video, to ensure data for the last frame is sent without delay, + * or with sparse streams when the amount of data is often small and the bitrate very + irregular (such as when sending display updates for a remote GUI) + +> Note: because of current GenAVB API limitations, an application cannot set the rate to use with +> a given stream, and as a result all 61883-4 streams have a hard-coded 24 Mbps bitrate. Since it +> is only a maximum, a lot of use cases are expected to be covered by that value, but customers with +> more specific needs are invited to get in touch with the GenAVB team to discuss possible +> solutions. This constraint will likely be removed in a future release. + +\if LINUX +#### CVF/H264 + +the H264 stream has different packetization modes (Single Nal Unit, Fragmentation unit FU, STAP and MTAP) +defined in RFC6184. +The first constraint is that the header size for different modes is not the same and the NALU +sizes is not fixed. That's why a parsing process is needed to prepare the packets with proper header regions +before sending them to the stack. + +A special function is dedicated for that : ::genavb_stream_h264_send +Constraints for this function are: +- Data buffer should only contain a partial NALU (the beginning, a middle section or just the end) but the order + of the byte stream must be respected. +- The start of an NALU must always be at the start of the data buffer. +- The last bytes of the NALU should be sent with an ::AVTP_FRAME_END event + +The AVB stream reservation will specify the maximum single nal unit size (equal to max AVTP payload size). +Thus, NAL units bigger than the max AVTP payload size are sent as fragmentation units, otherwise it will be +sent as a single packet. +The event.ts field is used to pass, presentation timestamps that will be set to the h264_timestamp field +of the AVTP H264 packet. + +The following events should be used for described use cases: + +* ::AVTP_SYNC events with valid presentation timestamps should be added with the beginning of every NALU + to be passed later as h264_timestamp + +* ::AVTP_FRAME_END event should be added to mark the end of a NALU. The associated data should + be sent immediately. + +> Note: because of current GenAVB API limitations, an application cannot set the rate to use with +> a given stream, and as a result all H264 streams have a hard-coded 24 Mbps bitrate. Since it +is only a maximum, a lot of use cases are expected to be covered by that value. + +\endif + +### Control + +AVTP Control streams are relying on a Datagram mode by creating the stream using the ::AVTP_DGRAM flag. In this mode +the framing task is under the application responsability, with the AVB stack simply adding/removing +AVTP and Ethernet headers to packets coming from/going to the application. +The flag ::GENAVB_STREAM_FLAGS_CUSTOM_TSPEC shall be set in the stream's parameters flag meaning that the application +defines the stream's properties which are defined by IEEE-1722 stream traffic specification +(TSpec): payload size of the packets and number of packets per class interval. + +#### ACF/NTSCF +Asynchronous format which does not require any presentation timestamp event to be filled. Any +timestamp event received by the stack for this mode is silently ignored. + +#### ACF/TSCF +Synchronous format requiring a presentation timestamp event for each packet. +Any call to the stream send API without a timestamp will be treated as an error. + diff --git a/doc/timer.md b/doc/timer.md new file mode 100644 index 0000000..9ca5d80 --- /dev/null +++ b/doc/timer.md @@ -0,0 +1,19 @@ +Timer API usage {#timer_usage} +====================================== + +The timer API provides lightweight and responsive timers which are designed for real-time use cases. +Each timer has a dedicated hardware ressource and hence should only be used when precise timing is required. For low resolution timers it's preferable to use OS timers. + +First the timer needs to be created using ::genavb_timer_create function. This guarantees that the needed hardware ressources are available and reserved. The callback needs to be registered separately using ::genavb_timer_set_callback. The callback is called in interrupt context when the timer reaches its expiration time and can be called in task context when errors are reported. + +Then the timer is started using ::genavb_timer_start and can be stopped using ::genavb_timer_stop. The timer API supports one-shot and periodic operations. + +If discontinuities happen for gPTP clocks, the callback returns immediately (with negative count argument). In this case it's needed to restart the timer. + +Finally, timer can be fully freed using ::genavb_timer_destroy. + +# PPS support {#pps} + +Pulse Per Second feature (PPS) is supported by the timer API by allowing requesting of a timer which has been identified at a lower level to have an available signal output. A PPS timer triggers a pulse signal at each expiration, but otherwise has the same behavior as a regular timer. Currently only one PPS timer can be available. Note that there is no restriction for the timer period contrary to what the PPS term may suggest. + +The PPS timer can be requested by using the ::GENAVB_TIMERF_PPS flag. A succesful call to ::genavb_timer_create using this flag guarantee the PPS timer availability and ownership. Then the flag needs to be set again in ::genavb_timer_start to trigger the output signal. If not set, the timer is started as a regular timer. diff --git a/environment-genavb b/environment-genavb new file mode 100644 index 0000000..572f43a --- /dev/null +++ b/environment-genavb @@ -0,0 +1,165 @@ +# +# To be sourced in build shell, AFTER toolchain environment +# +# +_make_genavb_complete() +{ + COMPREPLY=() + local curr_word="${COMP_WORDS[COMP_CWORD]}" + + case $COMP_CWORD in + 1) + local first_arg=("freertos_imx8mm_ca53" "freertos_imx8mn_ca53" \ + "freertos_imx8mp_ca53" "freertos_rt1052" "freertos_rt1176" \ + "freertos_rt1187_cm33" "freertos_rt1187_cm7" "freertos_rt1189_cm33" "freertos_rt1189_cm7" \ + "linux_imx6" "linux_imx6ull" "linux_imx8" "linux_ls1028") + + if [ $1 = "clean_genavb" ]; then + first_arg+=("all") + fi + + COMPREPLY=( $(compgen -W "${first_arg[*]}" $curr_word) ) + ;; + *) + if [ ${COMP_WORDS[1]} != "all" ]; then + proc_args "${COMP_WORDS[1]}" + COMPREPLY=( $(compgen -W "${CONFIG_ARR[*]}" $curr_word) ) + fi + ;; + esac +} + +complete -F _make_genavb_complete make_genavb +complete -F _make_genavb_complete make_genavb_doc +complete -F _make_genavb_complete make_genavb_doc_test +complete -F _make_genavb_complete clean_genavb + +proc_args() +{ + TARGET=$1 + shift + local config_arr=("$@") + + if [ -z "$TARGET" ]; then + TARGET=linux_imx6 + fi + + if [ ${#config_arr[@]} -eq 0 ]; then + case $TARGET in + "linux_imx6") + CONFIG_ARR=("endpoint_avb") + ;; + "linux_imx6ull") + CONFIG_ARR=("endpoint_avb") + ;; + "linux_imx8") + CONFIG_ARR=("endpoint_avb" "endpoint_tsn" "endpoint_avb_tsn" "endpoint_avb_tsn_bridge" "hybrid_avb" "endpoint_avb_tsn_hybrid") + ;; + "linux_ls1028") + CONFIG_ARR=("bridge") + ;; + "freertos_rt1052") + CONFIG_ARR=("endpoint_avb" "endpoint_tsn") + ;; + "freertos_rt1176") + CONFIG_ARR=("endpoint_avb" "endpoint_tsn") + ;; + "freertos_imx8mm_ca53") + CONFIG_ARR=("endpoint_avb" "endpoint_tsn") + ;; + "freertos_imx8mn_ca53") + CONFIG_ARR=("endpoint_avb" "endpoint_tsn") + ;; + "freertos_imx8mp_ca53") + CONFIG_ARR=("endpoint_avb" "endpoint_tsn") + ;; + "freertos_rt1187_cm33") + CONFIG_ARR=("hybrid_tsn") + ;; + "freertos_rt1187_cm7") + CONFIG_ARR=("endpoint_tsn_no_gptp") + ;; + "freertos_rt1189_cm33") + CONFIG_ARR=("hybrid_tsn") + ;; + "freertos_rt1189_cm7") + CONFIG_ARR=("endpoint_tsn_no_gptp") + ;; + *) + echo "unknown target" + return -1 + ;; + esac + else + CONFIG_ARR=("${config_arr[@]}") + fi +} + +# Usage: _make_genavb [target] [config_list] +_make_genavb() +{ + MAKE_TARGET=$1 + shift + proc_args $@ + + local cmake_doc_options + + if [ $MAKE_TARGET = "doc_doxygen" ]; then + cmake_doc_options=-DBUILD_DOC=ON + elif [ $MAKE_TARGET = "doc_doxygen_test" ]; then + MAKE_TARGET="doc_doxygen" + cmake_doc_options=-DBUILD_DOC_STRICT=ON + fi + + if [ -z "$SDKTARGETSYSROOT" ]; then + CMAKE_TOOLCHAIN=-DCMAKE_TOOLCHAIN_FILE=./config_armgcc.cmake + fi + + for config in "${CONFIG_ARR[@]}" + do + local BUILD_PATH=build/$TARGET/$config + + cmake . -B$BUILD_PATH -DCONFIG=$config -DTARGET=$TARGET $cmake_doc_options $CMAKE_TOOLCHAIN || return -1 + make -j`nproc` -C $BUILD_PATH $MAKE_TARGET || return -1 + done +} + +# Usage: make_genavb [target] [config_list] +make_genavb() +{ + _make_genavb "install" $@ +} + +# Usage: make_genavb_doc [target] [config_list] +make_genavb_doc() +{ + _make_genavb "doc_doxygen" $@ +} + +# Usage: make_genavb_doc_test [target] [config_list] +make_genavb_doc_test() +{ + _make_genavb "doc_doxygen_test" $@ +} + +# Usage: clean_genavb [target] [config_list] or clean_genavb all +clean_genavb() +{ + if [ $# -eq 0 ] || [ $1 = "all" ]; then + if [ -d ./build ]; then + rm ./common/version.h + rm -r ./build + echo "Deleted $(pwd)/build" + fi + else + proc_args $@ + + for config in "${CONFIG_ARR[@]}" + do + if [ -d ./build/$TARGET/$config ]; then + rm -r ./build/$TARGET/$config + echo "Deleted $(pwd)/build/$TARGET/$config" + fi + done + fi +} diff --git a/extensions.cmake b/extensions.cmake new file mode 100644 index 0000000..ee39a71 --- /dev/null +++ b/extensions.cmake @@ -0,0 +1,163 @@ +include(${TARGET_OS}/extensions.cmake) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -iquote ${TOPDIR}") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -iquote ${TOPDIR}/${TARGET_OS}") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -iquote ${TOPDIR}/common") + +include_directories("${TOPDIR}/include") +include_directories("${TOPDIR}/include/${TARGET_OS}") + +if(CONFIG_API) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -iquote ${TOPDIR}/api/${TARGET_OS}") +endif() + +# genavb_add_library(NAME SRCS ) +function(genavb_add_library) + cmake_parse_arguments(ARG "" "NAME" "SRCS" ${ARGN}) + if(NOT DEFINED ARG_NAME) + return() + endif() + + foreach(src IN LISTS ARG_SRCS) + list(APPEND srcs "${CMAKE_CURRENT_LIST_DIR}/${src}") + endforeach() + + add_library(${ARG_NAME} STATIC ${srcs}) + + if(ARCHIVE_OUTPUT_DIR) + set_target_properties(${ARG_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${ARCHIVE_OUTPUT_DIR}/${ARG_NAME}) + set_target_properties(${ARG_NAME} PROPERTIES OUTPUT_NAME "${ARG_NAME}-${TARGET_ARCH}") + endif() + + target_compile_definitions(${ARG_NAME} PRIVATE _COMPONENT_ID_=${ARG_NAME}_COMPONENT_ID) + target_compile_definitions(${ARG_NAME} PRIVATE _COMPONENT_STR_=\"${ARG_NAME}\") + target_compile_definitions(${ARG_NAME} PRIVATE _COMPONENT_=${ARG_NAME}_) + + target_compile_options(${ARG_NAME} PRIVATE -include ${CMAKE_CURRENT_LIST_DIR}/config.h) + + # Force build as non PIE code and propagate the option to all its dependants. + target_compile_options(${ARG_NAME} PUBLIC -fno-pie) + + add_custom_command(TARGET ${ARG_NAME} POST_BUILD COMMAND + ${CMAKE_OBJCOPY} $ + --rename-section .text=.text.${ARG_NAME} + --rename-section .data=.data.${ARG_NAME} + --rename-section .rodata=.rodata.${ARG_NAME} + --rename-section .bss=.bss.${ARG_NAME} + ) +endfunction() + +# genavb_add_prebuilt_library(LIB ) +function(genavb_add_prebuilt_library) + cmake_parse_arguments(ARG "" "LIB" "" ${ARGN}) + if(NOT DEFINED ARG_LIB) + return() + endif() + + if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/${ARG_LIB}) + message(FATAL_ERROR "Prebuilt library ${ARG_LIB} does not exist at ${CMAKE_CURRENT_LIST_DIR}") + endif() + + message(STATUS "Importing prebuilt static library ${ARG_LIB}") + + add_library(${ARG_LIB} STATIC IMPORTED) + set_property(TARGET ${ARG_LIB} PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/${ARG_LIB}) + + # All dependants on static prebuilt archives should compile as non-PIE code. + target_compile_options(${ARG_LIB} INTERFACE -fno-pie) + +endfunction() + +# genavb_link_libraries(TARGET LIB ) +function(genavb_link_libraries) + cmake_parse_arguments(ARG "" "TARGET" "LIB" ${ARGN}) + if(NOT DEFINED ARG_TARGET OR NOT TARGET ${ARG_TARGET}) + return() + endif() + + target_link_libraries(${ARG_TARGET} PUBLIC ${ARG_LIB}) + + # Propagate the usage requirements of the target (stack component archive) to all dependants: + # link as non position-independant code + target_link_libraries(${ARG_LIB} INTERFACE -no-pie) + + # Store all libraries used in current build + if(NOT ${ARG_LIB} IN_LIST genavb_used_libs) + set(genavb_used_libs ${genavb_used_libs} ${ARG_LIB} PARENT_SCOPE) + endif() +endfunction() + +# genavb_target_add_srcs(TARGET SRCS ) +function(genavb_target_add_srcs) + cmake_parse_arguments(ARG "" "TARGET" "SRCS" ${ARGN}) + if(NOT DEFINED ARG_TARGET OR NOT TARGET ${ARG_TARGET}) + return() + endif() + + foreach(src IN LISTS ARG_SRCS) + target_sources(${ARG_TARGET} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/${src}) + endforeach() +endfunction() + +# genavb_target_add_linker_script(TARGET LINKER_SCRIPT