diff options
author | andrew-elder <aelder@audioscience.com> | 2018-04-03 13:18:36 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-04-03 13:18:36 -0400 |
commit | c253bd9204ed0fe85619266c0da1a47fbfdaab0a (patch) | |
tree | f256df34cf5062956ac28b4894044d3fa685f5de | |
parent | 5e43fa5ae27fbd2b89c320670a32143ce93e0e16 (diff) | |
parent | 7d0f6655b04ef9af9a74319997ead5e5a1dcc2be (diff) | |
download | Open-AVB-c253bd9204ed0fe85619266c0da1a47fbfdaab0a.tar.gz |
Merge pull request #773 from AVnu/open-avb-next
Maintenance pull request
57 files changed, 5322 insertions, 540 deletions
diff --git a/.travis.yml b/.travis.yml index ecc2b9d6..9e599f08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,21 @@ install: - sudo apt-get update -qq - sudo apt-get install -y libpcap-dev libpci-dev libsndfile1-dev libjack-dev linux-headers-4.4.0-75-generic - sudo apt-get install -y libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev libasound2-dev + - sudo apt-get install -y python3-pip + - curl -L "https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip" -o ninja-linux.zip + - sudo unzip ninja-linux.zip -d /usr/local/bin + - sudo chmod 755 /usr/local/bin/ninja + - sudo pip3 install meson==0.44.0 + - curl -L "https://cmocka.org/files/1.1/cmocka-1.1.1.tar.xz" -o cmocka-1.1.1.tar.xz + - tar -xf cmocka-1.1.1.tar.xz + - pushd cmocka-1.1.1 + - mkdir build + - cd build + - cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug .. + - make + - sudo make install + - popd + - sudo cp /usr/src/linux-headers-4.4.0-75/include/uapi/linux/if_ether.h /usr/include/linux - sudo cp /usr/src/linux-headers-4.4.0-75/include/uapi/linux/ethtool.h /usr/include/linux - sudo cp /usr/src/linux-headers-4.4.0-75/include/uapi/linux/ptp_clock.h /usr/include/linux - CMAKE_URL="https://cmake.org/files/v3.9/cmake-3.9.4-Linux-x86_64.tar.gz" @@ -98,6 +98,16 @@ Starting from the OpenAvnu dir, one would go:: to build in OpenAvnu/tmp +Apple Vendor PTP Profile +======================== + +Support for the Apple Vendor PTP Profile can be found on the +feature-aptp-experimental branch of the OpenAvnu repository. + +These changes allow interaction with Apple proprietary PTP clocks. This +implementation has been tested with the Apple AirPlay SDK on a Raspberry Pi 3 +running within a group of devices playing the same music stream. + RELATED OPEN SOURCE PROJECTS ============================ @@ -119,11 +129,3 @@ XMOS XMOS is a semiconductor company providing a reference design for AVB/TSN endpoints in pro audio and automotive. XMOS endpoint source code is open source and available on Github - https://github.com/xcore/sw_avb - -Apple Vendor PTP Profile ------------------------- -Support for the Apple Vendor PTP Profile can be found on this fork of the OpenAvnu code within the branch ArtAndLogic-aPTP-changes. - -+ https://github.com/rroussel/OpenAvnu/tree/ArtAndLogic-aPTP-changes - -These changes allow interaction with Apple proprietary PTP clocks. This implementation has been tested with the Apple AirPlay SDK on a Raspberry Pi 3 running within a group of devices playing the same music stream. diff --git a/daemons/gptp/README.rst b/daemons/gptp/README.rst index f56fd719..1e26543d 100644 --- a/daemons/gptp/README.rst +++ b/daemons/gptp/README.rst @@ -94,6 +94,35 @@ To run from the command line: where xx-xx-xx-xx-xx-xx is the mac address of the local interface +Windows Wireless Specific ++++++++++++++++++++++++++ + +Additional Driver/Hardware Requirements: + +* Intel(R) 8260 Adapter + +* Intel(R) PROSet/Wireless Software + + +The wireless software can be downloaded from: + +https://downloadcenter.intel.com/ (Search) + +Running the daemon: + +Currently, the driver only works with peer-to-peer wireless connections. +The connection must be established prior to running the daemon. + +./gptp.exe -w <local hw device MAC> <local P2P MAC> <remove P2P MAC> + +Other limitations: + +Some versions of Windows(R) 10 do not allow WinPcap(R) to inject frames and +the BMCA algorithm can't complete. The result is both peers assume the master +role. To fix this, force one peer to be slave with the following command line: + +./gptp.exe -w -R 255 <local hw device MAC> <local P2P MAC> <remove P2P MAC> + Other Available PTP Daemons --------------------------- There are a number of existing ptp daemon projects. Some of the other known diff --git a/daemons/gptp/common/avbts_clock.hpp b/daemons/gptp/common/avbts_clock.hpp index feb8d9d1..614464c2 100644 --- a/daemons/gptp/common/avbts_clock.hpp +++ b/daemons/gptp/common/avbts_clock.hpp @@ -550,6 +550,15 @@ public: FrequencyRatio local_system_freq_offset, unsigned sync_count, unsigned pdelay_count, PortState port_state, bool asCapable ); + /** + * @brief Get local:system frequency ratio + * @return clock ratio + */ + FrequencyRatio getLocalSystemFreqOffset() + { + return _local_system_freq_offset; + } + /** * @brief Get the IEEE1588Clock identity value * @return clock identity diff --git a/daemons/gptp/common/avbts_message.hpp b/daemons/gptp/common/avbts_message.hpp index f18d1213..f5ccd9a1 100644 --- a/daemons/gptp/common/avbts_message.hpp +++ b/daemons/gptp/common/avbts_message.hpp @@ -204,6 +204,17 @@ public: }; /** + * @brief Builds PTP message from buffer + * @param buf [in] byte buffer containing PTP message + * @param size [in] length of buffer in bytes + * @param remote [in] address from where message was received + * @param port [in] port object that message was recieved on + * @return PTP message object + */ +PTPMessageCommon *buildPTPMessage +( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); + +/** * @brief Provides the PTPMessage common interface used during building of * PTP messages. */ @@ -373,10 +384,10 @@ protected: /** * @brief Generic interface for processing PTP message - * @param port IEEE1588 port + * @param port CommonPort object * @return void */ - virtual void processMessage( EtherPort *port ); + virtual void processMessage( CommonPort *port ); /** * @brief Builds PTP common header @@ -386,8 +397,7 @@ protected: void buildCommonHeader(uint8_t * buf); friend PTPMessageCommon *buildPTPMessage - ( char *buf, int size, LinkLayerAddress * remote, - EtherPort *port ); + ( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); }; /*Exact fit. No padding*/ @@ -589,12 +599,7 @@ class PTPMessageAnnounce:public PTPMessageCommon { return ret; } - /** - * @brief Processes PTP message - * @param port EtherPort - * @return void - */ - void processMessage( EtherPort *port ); + void processMessage( CommonPort *port ); /** * @brief Assembles PTPMessageAnnounce message on the @@ -608,8 +613,7 @@ class PTPMessageAnnounce:public PTPMessageCommon { ( CommonPort *port, PortIdentity *destIdentity); friend PTPMessageCommon *buildPTPMessage - ( char *buf, int size, LinkLayerAddress *remote, - EtherPort *port ); + ( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); }; /** @@ -632,12 +636,7 @@ class PTPMessageSync : public PTPMessageCommon { */ ~PTPMessageSync(); - /** - * @brief Processes PTP messages - * @param port [in] EtherPort - * @return void - */ - void processMessage( EtherPort *port ); + void processMessage( CommonPort *port ); /** * @brief Gets origin timestamp value @@ -659,8 +658,7 @@ class PTPMessageSync : public PTPMessageCommon { (EtherPort *port, PortIdentity *destIdentity ); friend PTPMessageCommon *buildPTPMessage - ( char *buf, int size, LinkLayerAddress * remote, - EtherPort *port ); + ( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); }; /* Exact fit. No padding*/ @@ -866,7 +864,15 @@ public: /** * @brief Builds the PTPMessageFollowUP object */ - PTPMessageFollowUp( EtherPort *port ); + PTPMessageFollowUp( CommonPort *port ); + + /** + * @brief write followup message into buffer + * @param port [in] associated CommonPort object + * @param buf_ptr [out] buffer to write data to + * @return number of bytes written to buffer + */ + size_t buildMessage(CommonPort *port, uint8_t *buf_ptr); /** * @brief Assembles PTPMessageFollowUp message on the @@ -879,12 +885,16 @@ public: bool sendPort ( EtherPort *port, PortIdentity *destIdentity ); + void processMessage( CommonPort *port ); + /** - * @brief Processes PTP messages - * @param port [in] EtherPort - * @return void - */ - void processMessage( EtherPort *port ); + * @brief Processes PTP messages + * @param port [in] CommonPort + * @param receipt [in] local time message was received + * @param delay + * @return void + */ + void processMessage( CommonPort *port, Timestamp receipt ); /** * @brief Gets the precise origin timestamp value @@ -916,8 +926,7 @@ public: } friend PTPMessageCommon *buildPTPMessage - ( char *buf, int size, LinkLayerAddress *remote, - EtherPort *port ); + ( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); }; /** @@ -953,12 +962,7 @@ class PTPMessagePathDelayReq : public PTPMessageCommon { bool sendPort ( EtherPort *port, PortIdentity *destIdentity ); - /** - * @brief Processes PTP messages - * @param port [in] EtherPort - * @return void - */ - void processMessage( EtherPort *port ); + void processMessage( CommonPort *port ); /** * @brief Gets origin timestamp value @@ -969,8 +973,7 @@ class PTPMessagePathDelayReq : public PTPMessageCommon { } friend PTPMessageCommon *buildPTPMessage - ( char *buf, int size, LinkLayerAddress *remote, - EtherPort *port ); + ( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); }; /** @@ -1004,12 +1007,7 @@ public: bool sendPort ( EtherPort *port, PortIdentity *destIdentity ); - /** - * @brief Processes PTP messages - * @param port [in] EtherPort - * @return void - */ - void processMessage( EtherPort *port ); + void processMessage( CommonPort *port ); /** * @brief Sets the request receipt timestamp @@ -1042,8 +1040,7 @@ public: } friend PTPMessageCommon *buildPTPMessage - ( char *buf, int size, LinkLayerAddress *remote, - EtherPort *port ); + ( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); }; /** @@ -1078,12 +1075,7 @@ public: bool sendPort ( EtherPort *port, PortIdentity *destIdentity ); - /** - * @brief Processes PTP messages - * @param port [in] EtherPort - * @return void - */ - void processMessage( EtherPort *port ); + void processMessage( CommonPort *port ); /** * @brief Sets the response origin timestamp @@ -1116,8 +1108,7 @@ public: } friend PTPMessageCommon *buildPTPMessage - ( char *buf, int size, LinkLayerAddress *remote, - EtherPort *port ); + ( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); }; /*Exact fit. No padding*/ @@ -1267,16 +1258,10 @@ public: bool sendPort ( EtherPort *port, PortIdentity *destIdentity ); - /** - * @brief Processes PTP messages - * @param port [in] EtherPort - * @return void - */ - void processMessage( EtherPort *port ); + void processMessage( CommonPort *port ); friend PTPMessageCommon *buildPTPMessage - ( char *buf, int size, LinkLayerAddress *remote, - EtherPort * port); + ( char *buf, int size, LinkLayerAddress *remote, CommonPort *port ); }; #endif diff --git a/daemons/gptp/common/avbts_osipc.hpp b/daemons/gptp/common/avbts_osipc.hpp index 00bac5dd..2adb3dbd 100644 --- a/daemons/gptp/common/avbts_osipc.hpp +++ b/daemons/gptp/common/avbts_osipc.hpp @@ -36,7 +36,6 @@ #include <stdint.h> #include <ptptypes.hpp> -#include <ether_port.hpp> /**@file*/ diff --git a/daemons/gptp/common/common_port.cpp b/daemons/gptp/common/common_port.cpp index d8ba54f6..268bc62d 100644 --- a/daemons/gptp/common/common_port.cpp +++ b/daemons/gptp/common/common_port.cpp @@ -61,9 +61,11 @@ CommonPort::CommonPort( PortInit_t *portInit ) : port_state = PTP_INITIALIZING; clock->registerPort(this, ifindex); qualified_announce = NULL; + automotive_profile = portInit->automotive_profile; announce_sequence_id = 0; signal_sequence_id = 0; sync_sequence_id = 0; + initialLogPdelayReqInterval = portInit->initialLogPdelayReqInterval; initialLogSyncInterval = portInit->initialLogSyncInterval; log_mean_announce_interval = 0; pdelay_count = 0; @@ -563,7 +565,7 @@ bool CommonPort::processEvent( Event e ) else { clock->addEventTimerLocked(this, ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES, - ANNOUNCE_RECEIPT_TIMEOUT_MULTIPLIER * pow(2.0, getAnnounceInterval()) * 1000000000.0); + (uint64_t) ( ANNOUNCE_RECEIPT_TIMEOUT_MULTIPLIER * pow(2.0, getAnnounceInterval()) * 1000000000.0 )); } // Do any media specific initialization @@ -732,6 +734,11 @@ bool CommonPort::adjustClockPhase( int64_t phase_adjust ) return false; } +FrequencyRatio CommonPort::getLocalSystemFreqOffset() +{ + return clock->getLocalSystemFreqOffset(); +} + Timestamp CommonPort::getTxPhyDelay( uint32_t link_speed ) const { if( phy_delay->count( link_speed ) != 0 ) diff --git a/daemons/gptp/common/common_port.hpp b/daemons/gptp/common/common_port.hpp index d5d0adda..4f195836 100644 --- a/daemons/gptp/common/common_port.hpp +++ b/daemons/gptp/common/common_port.hpp @@ -45,6 +45,10 @@ #include <math.h> +#define SYNC_RECEIPT_TIMEOUT_MULTIPLIER 3 /*!< Sync rcpt timeout multiplier */ +#define ANNOUNCE_RECEIPT_TIMEOUT_MULTIPLIER 3 /*!< Annc rcpt timeout mult */ +#define LOG2_INTERVAL_INVALID -127 /* Invalid Log base 2 interval value */ + class IEEE1588Clock; /** @@ -214,6 +218,9 @@ typedef struct { /* net_label Network label */ InterfaceLabel *net_label; + /* Virtual Network label (e.g. WiFi Direct network MAC) */ + InterfaceLabel *virtual_label; + /* automotive_profile set the AVnu automotive profile */ bool automotive_profile; @@ -313,6 +320,7 @@ private: PortState port_state; bool testMode; + bool automotive_profile; signed char log_mean_sync_interval; signed char log_mean_announce_interval; @@ -336,6 +344,9 @@ private: * received as slave */ unsigned pdelay_count; + signed char initialLogPdelayReqInterval; + signed char log_min_mean_pdelay_req_interval; + PTPMessageAnnounce *qualified_announce; uint16_t announce_sequence_id; @@ -436,6 +447,13 @@ public: } /** + * @brief Return frequency offset between local timestamp clock + * system clock + * @return local:system ratio + */ + FrequencyRatio getLocalSystemFreqOffset(); + + /** * @brief Gets a pointer to IEEE1588Clock * @return Pointer to clock */ @@ -1115,6 +1133,63 @@ public: virtual void becomeSlave( bool restart_syntonization ) = 0; /** + * @brief Gets the AVnu automotive profile flag + * @return automotive_profile flag + */ + bool getAutomotiveProfile() { return(automotive_profile); } + + /** + * @brief Sets the pDelay minimum interval + * @param val time interval + * @return none + */ + void setPDelayInterval(signed char val) { + log_min_mean_pdelay_req_interval = val; + } + + /** + * @brief Gets the pDelay minimum interval + * @return PDelay interval + */ + signed char getPDelayInterval(void) { + return log_min_mean_pdelay_req_interval; + } + + /** + * @brief Sets the pDelay minimum interval back to initial + * value + * @return none + */ + void resetInitPDelayInterval(void) { + log_min_mean_pdelay_req_interval = initialLogPdelayReqInterval; + } + + /** + * @brief set initial pdelay interval + * @param interval [in] log base 2 pdelay rate + */ + void setInitPDelayInterval( int8_t interval ) + { + initialLogPdelayReqInterval = interval; + } + + /** + * @brief get initial pdelay interval + * @return log base 2 pdelay rate + */ + int8_t getInitPDelayInterval(void) + { + return initialLogPdelayReqInterval; + } + + /** + * @brief Start pDelay interval timer + * @param waitTime time interval + * @return none + */ + virtual void startPDelayIntervalTimer( unsigned long long waitTime ) {} + + /** * @brief Sets current sync count value. * @param cnt [in] sync count value * @return void @@ -1336,20 +1411,24 @@ public: */ virtual bool _processEvent( Event e ) = 0; + /** + * @brief Performs media specific setup after start sync is completed + * @return void + */ virtual void syncDone() = 0; /** - * @brief Sends a general message to a port. No timestamps - * @param buf [in] Pointer to the data buffer - * @param len Size of the message - * @param mcast_type Enumeration - * MulticastType (pdelay, none or other). Depracated. - * @param destIdentity Destination port identity - * @return void - */ + * @brief Sends a general message to a port. No timestamps + * @param buf [in] Pointer to the data buffer + * @param len Size of the message + * @param mcast_type Enumeration + * MulticastType (pdelay, none or other). Depracated. + * @param destIdentity Destination port identity + * @return void + */ virtual void sendGeneralPort (uint16_t etherType, uint8_t * buf, int len, MulticastType mcast_type, - PortIdentity * destIdentity) = 0; + PortIdentity * destIdentity) = 0; /** * @brief Sets link speed diff --git a/daemons/gptp/common/ether_port.cpp b/daemons/gptp/common/ether_port.cpp index 2325fb99..56db3d22 100644 --- a/daemons/gptp/common/ether_port.cpp +++ b/daemons/gptp/common/ether_port.cpp @@ -85,7 +85,6 @@ EtherPort::~EtherPort() EtherPort::EtherPort( PortInit_t *portInit ) : CommonPort( portInit ) { - automotive_profile = portInit->automotive_profile; linkUp = portInit->linkUp; setTestMode( portInit->testMode ); @@ -98,29 +97,29 @@ EtherPort::EtherPort( PortInit_t *portInit ) : duplicate_resp_counter = 0; last_invalid_seqid = 0; - initialLogPdelayReqInterval = portInit->initialLogPdelayReqInterval; operLogPdelayReqInterval = portInit->operLogPdelayReqInterval; operLogSyncInterval = portInit->operLogSyncInterval; - if (automotive_profile) { + if (getAutomotiveProfile()) + { setAsCapable( true ); if (getInitSyncInterval() == LOG2_INTERVAL_INVALID) setInitSyncInterval( -5 ); // 31.25 ms - if (initialLogPdelayReqInterval == LOG2_INTERVAL_INVALID) - initialLogPdelayReqInterval = 0; // 1 second + if (getInitPDelayInterval() == LOG2_INTERVAL_INVALID) + setInitPDelayInterval( 0 ); // 1 second if (operLogPdelayReqInterval == LOG2_INTERVAL_INVALID) operLogPdelayReqInterval = 0; // 1 second if (operLogSyncInterval == LOG2_INTERVAL_INVALID) operLogSyncInterval = 0; // 1 second - } - else { + } else + { setAsCapable( false ); if ( getInitSyncInterval() == LOG2_INTERVAL_INVALID ) setInitSyncInterval( -3 ); // 125 ms - if (initialLogPdelayReqInterval == LOG2_INTERVAL_INVALID) - initialLogPdelayReqInterval = 0; // 1 second + if (getInitPDelayInterval() == LOG2_INTERVAL_INVALID) + setInitPDelayInterval( 0 ); // 1 second if (operLogPdelayReqInterval == LOG2_INTERVAL_INVALID) operLogPdelayReqInterval = 0; // 1 second if (operLogSyncInterval == LOG2_INTERVAL_INVALID) @@ -128,7 +127,7 @@ EtherPort::EtherPort( PortInit_t *portInit ) : } /*TODO: Add intervals below to a config interface*/ - log_min_mean_pdelay_req_interval = initialLogPdelayReqInterval; + resetInitPDelayInterval(); last_sync = NULL; last_pdelay_req = NULL; @@ -138,7 +137,8 @@ EtherPort::EtherPort( PortInit_t *portInit ) : setPdelayCount(0); setSyncCount(0); - if (automotive_profile) { + if( getAutomotiveProfile( )) + { if (isGM) { avbSyncState = 1; } @@ -169,13 +169,13 @@ bool EtherPort::_init_port( void ) void EtherPort::startPDelay() { if(!pdelayHalted()) { - if (automotive_profile) { - if (log_min_mean_pdelay_req_interval != PTPMessageSignalling::sigMsgInterval_NoSend) { - long long unsigned int waitTime; - waitTime = ((long long) (pow((double)2, log_min_mean_pdelay_req_interval) * 1000000000.0)); - waitTime = waitTime > EVENT_TIMER_GRANULARITY ? waitTime : EVENT_TIMER_GRANULARITY; + if( getAutomotiveProfile( )) + { + if( getPDelayInterval() != + PTPMessageSignalling::sigMsgInterval_NoSend) + { pdelay_started = true; - startPDelayIntervalTimer(waitTime); + startPDelayIntervalTimer(EVENT_TIMER_GRANULARITY); } } else { @@ -194,7 +194,8 @@ void EtherPort::stopPDelay() void EtherPort::startSyncRateIntervalTimer() { - if (automotive_profile) { + if( getAutomotiveProfile( )) + { sync_rate_interval_timer_started = true; if (isGM) { // GM will wait up to 8 seconds for signaling rate @@ -325,7 +326,8 @@ bool EtherPort::_processEvent( Event e ) switch (e) { case POWERUP: case INITIALIZE: - if (!automotive_profile) { + if( !getAutomotiveProfile( )) + { if ( getPortState() != PTP_SLAVE && getPortState() != PTP_MASTER ) { @@ -355,7 +357,8 @@ bool EtherPort::_processEvent( Event e ) port_ready_condition->wait(); - if (automotive_profile) { + if( getAutomotiveProfile( )) + { setStationState(STATION_STATE_ETHERNET_READY); if (getTestMode()) { @@ -387,7 +390,7 @@ bool EtherPort::_processEvent( Event e ) // If the automotive profile is enabled, handle the event by // doing nothing and returning true, preventing the default // action from executing - if( automotive_profile ) + if( getAutomotiveProfile( )) ret = true; else ret = false; @@ -396,7 +399,8 @@ bool EtherPort::_processEvent( Event e ) case LINKUP: haltPdelay(false); startPDelay(); - if (automotive_profile) { + if( getAutomotiveProfile( )) + { GPTP_LOG_EXCEPTION("LINKUP"); } else { @@ -408,11 +412,16 @@ bool EtherPort::_processEvent( Event e ) } else if( getPortState() == PTP_MASTER ) { becomeMaster( true ); } else { - clock->addEventTimerLocked(this, ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES, - ANNOUNCE_RECEIPT_TIMEOUT_MULTIPLIER * pow(2.0, getAnnounceInterval()) * 1000000000.0); + clock->addEventTimerLocked + ( this, ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES, + (uint64_t) + ( ANNOUNCE_RECEIPT_TIMEOUT_MULTIPLIER * + pow( 2.0, getAnnounceInterval( )) * + 1000000000.0 )); } - if (automotive_profile) { + if( getAutomotiveProfile( )) + { setAsCapable( true ); setStationState(STATION_STATE_ETHERNET_READY); @@ -427,7 +436,7 @@ bool EtherPort::_processEvent( Event e ) resetInitSyncInterval(); setAnnounceInterval( 0 ); - log_min_mean_pdelay_req_interval = initialLogPdelayReqInterval; + resetInitPDelayInterval(); if (!isGM) { // Send an initial signaling message @@ -468,7 +477,8 @@ bool EtherPort::_processEvent( Event e ) break; case LINKDOWN: stopPDelay(); - if (automotive_profile) { + if( getAutomotiveProfile( )) + { GPTP_LOG_EXCEPTION("LINK DOWN"); } else { @@ -484,7 +494,7 @@ bool EtherPort::_processEvent( Event e ) break; case ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: case SYNC_RECEIPT_TIMEOUT_EXPIRES: - if( !automotive_profile ) + if( !getAutomotiveProfile( )) { ret = false; break; @@ -568,8 +578,8 @@ bool EtherPort::_processEvent( Event e ) tx_succeed = sync->sendPort(this, NULL); GPTP_LOG_DEBUG("Sent SYNC message"); - if ( automotive_profile && - getPortState() == PTP_MASTER ) + if( getAutomotiveProfile() && + getPortState() == PTP_MASTER ) { if (avbSyncState > 0) { avbSyncState--; @@ -618,7 +628,8 @@ bool EtherPort::_processEvent( Event e ) break; case FAULT_DETECTED: GPTP_LOG_ERROR("Received FAULT_DETECTED event"); - if (!automotive_profile) { + if( !getAutomotiveProfile( )) + { setAsCapable(false); } break; @@ -637,7 +648,8 @@ bool EtherPort::_processEvent( Event e ) pdelay_rx_lock->unlock(); break; case PDELAY_RESP_RECEIPT_TIMEOUT_EXPIRES: - if (!automotive_profile) { + if( !getAutomotiveProfile( )) + { GPTP_LOG_EXCEPTION("PDelay Response Receipt Timeout"); setAsCapable(false); } @@ -668,8 +680,9 @@ bool EtherPort::_processEvent( Event e ) sendSignalMessage = true; } - if (log_min_mean_pdelay_req_interval != operLogPdelayReqInterval) { - log_min_mean_pdelay_req_interval = operLogPdelayReqInterval; + if( getPDelayInterval() != operLogPdelayReqInterval) + { + setPDelayInterval( operLogPdelayReqInterval ); sendSignalMessage = true; } @@ -678,10 +691,10 @@ bool EtherPort::_processEvent( Event e ) // Send operational signalling message PTPMessageSignalling *sigMsg = new PTPMessageSignalling(this); if (sigMsg) { - if (automotive_profile) + if( getAutomotiveProfile( )) sigMsg->setintervals(PTPMessageSignalling::sigMsgInterval_NoChange, getSyncInterval(), PTPMessageSignalling::sigMsgInterval_NoChange); else - sigMsg->setintervals(log_min_mean_pdelay_req_interval, getSyncInterval(), PTPMessageSignalling::sigMsgInterval_NoChange); + sigMsg->setintervals(getPDelayInterval(), getSyncInterval(), PTPMessageSignalling::sigMsgInterval_NoChange); sigMsg->sendPort(this, NULL); delete sigMsg; } @@ -720,7 +733,8 @@ void EtherPort::becomeMaster( bool annc ) { stopSyncReceiptTimer(); if( annc ) { - if (!automotive_profile) { + if( !getAutomotiveProfile( )) + { startAnnounce(); } } @@ -738,7 +752,8 @@ void EtherPort::becomeSlave( bool restart_syntonization ) { setPortState( PTP_SLAVE ); - if (!automotive_profile) { + if( !getAutomotiveProfile( )) + { clock->addEventTimerLocked (this, ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES, (ANNOUNCE_RECEIPT_TIMEOUT_MULTIPLIER* @@ -832,7 +847,8 @@ void EtherPort::startPDelayIntervalTimer void EtherPort::syncDone() { GPTP_LOG_VERBOSE("Sync complete"); - if (automotive_profile && getPortState() == PTP_SLAVE) { + if( getAutomotiveProfile() && getPortState() == PTP_SLAVE ) + { if (avbSyncState > 0) { avbSyncState--; if (avbSyncState == 0) { @@ -849,7 +865,8 @@ void EtherPort::syncDone() { } } - if (automotive_profile) { + if( getAutomotiveProfile( )) + { if (!sync_rate_interval_timer_started) { if ( getSyncInterval() != operLogSyncInterval ) { diff --git a/daemons/gptp/common/ether_port.hpp b/daemons/gptp/common/ether_port.hpp index 32968dc4..4c878951 100644 --- a/daemons/gptp/common/ether_port.hpp +++ b/daemons/gptp/common/ether_port.hpp @@ -60,10 +60,6 @@ #define TEST_STATUS_MULTICAST 0x011BC50AC000ULL /*!< AVnu Automotive profile test status msg Multicast value */ #define PDELAY_RESP_RECEIPT_TIMEOUT_MULTIPLIER 3 /*!< PDelay timeout multiplier*/ -#define SYNC_RECEIPT_TIMEOUT_MULTIPLIER 3 /*!< Sync receipt timeout multiplier*/ -#define ANNOUNCE_RECEIPT_TIMEOUT_MULTIPLIER 3 /*!< Announce receipt timeout multiplier*/ - -#define LOG2_INTERVAL_INVALID -127 /* Simple out of range Log base 2 value used for Sync and PDelay msg internvals */ /** * @brief PortType enumeration. Selects between delay request-response (E2E) mechanism @@ -98,7 +94,6 @@ class EtherPort : public CommonPort /* Port Configuration */ signed char log_mean_unicast_sync_interval; signed char log_min_mean_delay_req_interval; - signed char log_min_mean_pdelay_req_interval; unsigned int duplicate_resp_counter; uint16_t last_invalid_seqid; @@ -109,8 +104,6 @@ class EtherPort : public CommonPort // asCapable : already defined as asCapable signed char operLogPdelayReqInterval; signed char operLogSyncInterval; - signed char initialLogPdelayReqInterval; - bool automotive_profile; // Test Status variables uint32_t linkUpCount; @@ -201,12 +194,6 @@ protected: void syncDone(); /** - * @brief Gets the AVnu automotive profile flag - * @return automotive_profile flag - */ - bool getAutomotiveProfile() { return( automotive_profile ); } - - /** * @brief Destroys a EtherPort */ ~EtherPort(); @@ -303,31 +290,6 @@ protected: void removeForeignMasterAll(void); /** - * @brief Gets the pDelay minimum interval - * @return PDelay interval - */ - signed char getPDelayInterval(void) { - return log_min_mean_pdelay_req_interval; - } - - /** - * @brief Sets the pDelay minimum interval - * @param val time interval - * @return none - */ - void setPDelayInterval(signed char val) { - log_min_mean_pdelay_req_interval = val; - } - - /** - * @brief Sets the pDelay minimum interval back to initial - * value - * @return none */ - void setInitPDelayInterval(void) { - log_min_mean_pdelay_req_interval = initialLogPdelayReqInterval; - } - - /** * @brief Start pDelay interval timer * @param waitTime time interval * @return none diff --git a/daemons/gptp/common/ieee1588.hpp b/daemons/gptp/common/ieee1588.hpp index bd95a5fa..cf3d59e4 100644 --- a/daemons/gptp/common/ieee1588.hpp +++ b/daemons/gptp/common/ieee1588.hpp @@ -452,16 +452,4 @@ static inline void TIMESTAMP_ADD_NS( Timestamp &ts, uint64_t ns ) { ts.nanoseconds = (uint32_t)nanos; } -/** - * @brief Builds a PTP message - * @param buf [in] message buffer to send - * @param size message length - * @param remote Destination link layer address - * @param port [in] IEEE1588 port - * @return PTP message instance of PTPMessageCommon - */ -PTPMessageCommon *buildPTPMessage -( char *buf, int size, LinkLayerAddress *remote, - EtherPort *port ); - #endif diff --git a/daemons/gptp/common/ptp_message.cpp b/daemons/gptp/common/ptp_message.cpp index 9219338b..e8835570 100644 --- a/daemons/gptp/common/ptp_message.cpp +++ b/daemons/gptp/common/ptp_message.cpp @@ -67,7 +67,7 @@ bool PTPMessageCommon::isSenderEqual(PortIdentity portIdentity) PTPMessageCommon *buildPTPMessage ( char *buf, int size, LinkLayerAddress *remote, - EtherPort *port ) + CommonPort *port ) { OSTimer *timer = port->getTimerFactory()->createTimer(); PTPMessageCommon *msg = NULL; @@ -80,6 +80,7 @@ PTPMessageCommon *buildPTPMessage PortIdentity *sourcePortIdentity; Timestamp timestamp(0, 0, 0); unsigned counter_value = 0; + EtherPort *eport = NULL; #if PTP_DEBUG { @@ -123,8 +124,18 @@ PTPMessageCommon *buildPTPMessage if (!(messageType >> 3)) { int iter = 5; long req = 4000; // = 1 ms + + eport = dynamic_cast <EtherPort *> ( port ); + if (eport == NULL) + { + GPTP_LOG_ERROR + ( "Received Event Message, but port type " + "doesn't support timestamping\n" ); + goto abort; + } + int ts_good = - port->getRxTimestamp + eport->getRxTimestamp (sourcePortIdentity, messageId, timestamp, counter_value, false); while (ts_good != GPTP_EC_SUCCESS && iter-- != 0) { // Waits at least 1 time slice regardless of size of 'req' @@ -134,7 +145,7 @@ PTPMessageCommon *buildPTPMessage "Error (RX) timestamping RX event packet (Retrying), error=%d", ts_good ); ts_good = - port->getRxTimestamp(sourcePortIdentity, messageId, + eport->getRxTimestamp(sourcePortIdentity, messageId, timestamp, counter_value, iter == 0); req *= 2; @@ -527,7 +538,8 @@ PTPMessageCommon *buildPTPMessage buf + PTP_COMMON_HDR_LOG_MSG_INTRVL(PTP_COMMON_HDR_OFFSET), sizeof(msg->logMeanMessageInterval)); - port->addSockAddrMap(msg->sourcePortIdentity, remote); + if( eport != NULL ) + eport->addSockAddrMap( msg->sourcePortIdentity, remote ); msg->_timestamp = timestamp; msg->_timestamp_counter_value = counter_value; @@ -587,7 +599,7 @@ bool PTPMessageCommon::getTxTimestamp( EtherPort *port, uint32_t link_speed ) return ts_good == GPTP_EC_SUCCESS; } -void PTPMessageCommon::processMessage( EtherPort *port ) +void PTPMessageCommon::processMessage( CommonPort *port ) { _gc = true; return; @@ -838,7 +850,7 @@ bool PTPMessageAnnounce::sendPort return true; } -void PTPMessageAnnounce::processMessage( EtherPort *port ) +void PTPMessageAnnounce::processMessage( CommonPort *port ) { ClockIdentity my_clock_identity; @@ -875,15 +887,25 @@ void PTPMessageAnnounce::processMessage( EtherPort *port ) 1000000000.0))); } -void PTPMessageSync::processMessage( EtherPort *port ) +void PTPMessageSync::processMessage( CommonPort *port ) { + EtherPort *eport = dynamic_cast <EtherPort *> (port); + PTPMessageSync *old_sync; + + if (eport == NULL) + { + GPTP_LOG_ERROR( "Discarding sync message on wrong port type" ); + _gc = true; + goto done; + } + if (port->getPortState() == PTP_DISABLED ) { - // Do nothing Sync messages should be ignored when in this state + // Do nothing Sync messages should be ignored in this state return; } if (port->getPortState() == PTP_FAULTY) { // According to spec recovery is implementation specific - port->recoverPort(); + eport->recoverPort(); return; } @@ -892,12 +914,12 @@ void PTPMessageSync::processMessage( EtherPort *port ) #if CHECK_ASSIST_BIT if( flags[PTP_ASSIST_BYTE] & (0x1<<PTP_ASSIST_BIT)) { #endif - PTPMessageSync *old_sync = port->getLastSync(); + old_sync = eport->getLastSync(); if (old_sync != NULL) { delete old_sync; } - port->setLastSync(this); + eport->setLastSync(this); _gc = false; goto done; #if CHECK_ASSIST_BIT @@ -912,7 +934,7 @@ void PTPMessageSync::processMessage( EtherPort *port ) return; } -PTPMessageFollowUp::PTPMessageFollowUp( EtherPort *port ) : +PTPMessageFollowUp::PTPMessageFollowUp( CommonPort *port ) : PTPMessageCommon( port ) { messageType = FOLLOWUP_MESSAGE; /* This is an event message */ @@ -923,52 +945,65 @@ PTPMessageFollowUp::PTPMessageFollowUp( EtherPort *port ) : return; } -bool PTPMessageFollowUp::sendPort -( EtherPort *port, PortIdentity *destIdentity ) +size_t PTPMessageFollowUp::buildMessage( CommonPort *port, uint8_t *buf_ptr ) { - uint8_t buf_t[256]; - uint8_t *buf_ptr = buf_t + port->getPayloadOffset(); - unsigned char tspec_msg_t = 0x0; - Timestamp preciseOriginTimestamp_BE; - memset(buf_t, 0, 256); /* Create packet in buf - Copy in common header */ + Copy in common header */ messageLength = - PTP_COMMON_HDR_LENGTH + PTP_FOLLOWUP_LENGTH + sizeof(tlv); + PTP_COMMON_HDR_LENGTH + PTP_FOLLOWUP_LENGTH + sizeof(tlv); + unsigned char tspec_msg_t = 0; + Timestamp preciseOriginTimestamp_BE; + tspec_msg_t |= messageType & 0xF; buildCommonHeader(buf_ptr); preciseOriginTimestamp_BE.seconds_ms = - PLAT_htons(preciseOriginTimestamp.seconds_ms); + PLAT_htons(preciseOriginTimestamp.seconds_ms); preciseOriginTimestamp_BE.seconds_ls = - PLAT_htonl(preciseOriginTimestamp.seconds_ls); + PLAT_htonl(preciseOriginTimestamp.seconds_ls); preciseOriginTimestamp_BE.nanoseconds = - PLAT_htonl(preciseOriginTimestamp.nanoseconds); + PLAT_htonl(preciseOriginTimestamp.nanoseconds); /* Copy in v2 sync specific fields */ memcpy(buf_ptr + PTP_FOLLOWUP_SEC_MS(PTP_FOLLOWUP_OFFSET), - &(preciseOriginTimestamp_BE.seconds_ms), - sizeof(preciseOriginTimestamp.seconds_ms)); + &(preciseOriginTimestamp_BE.seconds_ms), + sizeof(preciseOriginTimestamp.seconds_ms)); memcpy(buf_ptr + PTP_FOLLOWUP_SEC_LS(PTP_FOLLOWUP_OFFSET), - &(preciseOriginTimestamp_BE.seconds_ls), - sizeof(preciseOriginTimestamp.seconds_ls)); + &(preciseOriginTimestamp_BE.seconds_ls), + sizeof(preciseOriginTimestamp.seconds_ls)); memcpy(buf_ptr + PTP_FOLLOWUP_NSEC(PTP_FOLLOWUP_OFFSET), - &(preciseOriginTimestamp_BE.nanoseconds), - sizeof(preciseOriginTimestamp.nanoseconds)); + &(preciseOriginTimestamp_BE.nanoseconds), + sizeof(preciseOriginTimestamp.nanoseconds)); /*Change time base indicator to Network Order before sending it*/ uint16_t tbi_NO = PLAT_htonl(tlv.getGMTimeBaseIndicator()); tlv.setGMTimeBaseIndicator(tbi_NO); - tlv.toByteString(buf_ptr + PTP_COMMON_HDR_LENGTH + PTP_FOLLOWUP_LENGTH); + tlv.toByteString(buf_ptr + PTP_COMMON_HDR_LENGTH + + PTP_FOLLOWUP_LENGTH); - GPTP_LOG_VERBOSE - ("Follow-Up Time: %u seconds(hi)", preciseOriginTimestamp.seconds_ms); - GPTP_LOG_VERBOSE - ("Follow-Up Time: %u seconds", preciseOriginTimestamp.seconds_ls); - GPTP_LOG_VERBOSE - ("FW-UP Time: %u nanoseconds", preciseOriginTimestamp.nanoseconds); - GPTP_LOG_VERBOSE - ("FW-UP Time: %x seconds", preciseOriginTimestamp.seconds_ls); - GPTP_LOG_VERBOSE - ("FW-UP Time: %x nanoseconds", preciseOriginTimestamp.nanoseconds); + port->incCounter_ieee8021AsPortStatTxFollowUpCount(); + + return PTP_COMMON_HDR_LENGTH + PTP_FOLLOWUP_LENGTH + sizeof(tlv); +} + +bool PTPMessageFollowUp::sendPort +( EtherPort *port, PortIdentity *destIdentity ) +{ + uint8_t buf_t[256]; + uint8_t *buf_ptr = buf_t + port->getPayloadOffset(); + memset(buf_t, 0, 256); + /* Create packet in buf + Copy in common header */ + buildMessage(port, buf_ptr); + + GPTP_LOG_VERBOSE( "Follow-Up Time: %u seconds(hi)", + preciseOriginTimestamp.seconds_ms); + GPTP_LOG_VERBOSE( "Follow-Up Time: %u seconds", + preciseOriginTimestamp.seconds_ls); + GPTP_LOG_VERBOSE( "FW-UP Time: %u nanoseconds", + preciseOriginTimestamp.nanoseconds); + GPTP_LOG_VERBOSE( "FW-UP Time: %x seconds", + preciseOriginTimestamp.seconds_ls); + GPTP_LOG_VERBOSE( "FW-UP Time: %x nanoseconds", + preciseOriginTimestamp.nanoseconds); #ifdef DEBUG GPTP_LOG_VERBOSE("Follow-up Dump:"); for (int i = 0; i < messageLength; ++i) { @@ -976,17 +1011,16 @@ bool PTPMessageFollowUp::sendPort } #endif - port->sendGeneralPort(PTP_ETHERTYPE, buf_t, messageLength, MCAST_OTHER, destIdentity); - - port->incCounter_ieee8021AsPortStatTxFollowUpCount(); + port->sendGeneralPort( PTP_ETHERTYPE, buf_t, messageLength, + MCAST_OTHER, destIdentity ); return true; } -void PTPMessageFollowUp::processMessage( EtherPort *port ) +void PTPMessageFollowUp::processMessage +( CommonPort *port, Timestamp sync_arrival ) { uint64_t delay; - Timestamp sync_arrival; Timestamp system_time(0, 0, 0); Timestamp device_time(0, 0, 0); @@ -1000,166 +1034,205 @@ void PTPMessageFollowUp::processMessage( EtherPort *port ) int32_t scaledLastGmFreqChange = 0; scaledNs scaledLastGmPhaseChange; - GPTP_LOG_DEBUG("Processing a follow-up message"); - - // Expire any SYNC_RECEIPT timers that exist - port->stopSyncReceiptTimer(); - - if (port->getPortState() == PTP_DISABLED ) { - // Do nothing Sync messages should be ignored when in this state - return; - } - if (port->getPortState() == PTP_FAULTY) { - // According to spec recovery is implementation specific - port->recoverPort(); - return; - } - port->incCounter_ieee8021AsPortStatRxFollowUpCount(); - PortIdentity sync_id; - PTPMessageSync *sync = port->getLastSync(); - if (sync == NULL) { - GPTP_LOG_ERROR("Received Follow Up but there is no sync message"); - return; - } - sync->getPortIdentity(&sync_id); - - if (sync->getSequenceId() != sequenceId || sync_id != *sourcePortIdentity) - { - unsigned int cnt = 0; - - if( !port->incWrongSeqIDCounter(&cnt) ) - { - port->becomeMaster( true ); - port->setWrongSeqIDCounter(0); - } - GPTP_LOG_ERROR - ("Received Follow Up %d times but cannot find corresponding Sync", cnt); - goto done; - } - - if (sync->getTimestamp()._version != port->getTimestampVersion()) + if (!port->getLinkDelay(&delay)) { - GPTP_LOG_ERROR("Received Follow Up but timestamp version indicates Sync is out of date"); + GPTP_LOG_ERROR( "Received Follow up but " + "there is no valid link delay" ); goto done; } - sync_arrival = sync->getTimestamp(); - - if( !port->getLinkDelay(&delay) ) { - goto done; - } - - master_local_freq_offset = tlv.getRateOffset(); + master_local_freq_offset = tlv.getRateOffset(); master_local_freq_offset /= 1ULL << 41; master_local_freq_offset += 1.0; master_local_freq_offset /= port->getPeerRateOffset(); correctionField /= 1 << 16; - correction = (int64_t)((delay * master_local_freq_offset) + correctionField ); + correction = (int64_t) + ((delay * master_local_freq_offset) + correctionField); - if( correction > 0 ) - TIMESTAMP_ADD_NS( preciseOriginTimestamp, correction ); - else TIMESTAMP_SUB_NS( preciseOriginTimestamp, -correction ); + if (correction > 0) + TIMESTAMP_ADD_NS(preciseOriginTimestamp, correction); + else TIMESTAMP_SUB_NS(preciseOriginTimestamp, -correction); local_clock_adjustment = - port->getClock()-> - calcMasterLocalClockRateDifference - ( preciseOriginTimestamp, sync_arrival ); + port->getClock()-> + calcMasterLocalClockRateDifference + (preciseOriginTimestamp, sync_arrival); if( local_clock_adjustment == NEGATIVE_TIME_JUMP ) { - GPTP_LOG_VERBOSE("Received Follow Up but preciseOrigintimestamp indicates negative time jump"); + GPTP_LOG_VERBOSE + ( "Received Follow Up but preciseOrigintimestamp " + "indicates negative time jump" ); goto done; } - scalar_offset = TIMESTAMP_TO_NS( sync_arrival ); + scalar_offset = TIMESTAMP_TO_NS( sync_arrival ); scalar_offset -= TIMESTAMP_TO_NS( preciseOriginTimestamp ); - GPTP_LOG_VERBOSE - ("Followup Correction Field: %Ld, Link Delay: %lu", correctionField, - delay); - GPTP_LOG_VERBOSE - ("FollowUp Scalar = %lld", scalar_offset); + GPTP_LOG_VERBOSE( "Followup Correction Field: %lld, Link Delay: %lu", + correctionField, delay ); + GPTP_LOG_VERBOSE( "FollowUp Scalar = %lld", + scalar_offset ); - /* Otherwise synchronize clock with approximate time from Sync message */ + /* Otherwise synchronize clock with approximate Sync time */ uint32_t local_clock, nominal_clock_rate; uint32_t device_sync_time_offset; port->getDeviceTime(system_time, device_time, local_clock, - nominal_clock_rate); - GPTP_LOG_VERBOSE - ( "Device Time = %llu,System Time = %llu", - TIMESTAMP_TO_NS(device_time), TIMESTAMP_TO_NS(system_time)); + nominal_clock_rate); + GPTP_LOG_VERBOSE( "Device Time = %llu,System Time = %llu", + TIMESTAMP_TO_NS( device_time ), + TIMESTAMP_TO_NS( system_time )); /* Adjust local_clock to correspond to sync_arrival */ - device_sync_time_offset = - (uint32_t) (TIMESTAMP_TO_NS(device_time) - TIMESTAMP_TO_NS(sync_arrival)); + device_sync_time_offset = (uint32_t) + ( TIMESTAMP_TO_NS( device_time ) - + TIMESTAMP_TO_NS( sync_arrival )); GPTP_LOG_VERBOSE - ("ptp_message::FollowUp::processMessage System time: %u,%u " - "Device Time: %u,%u", - system_time.seconds_ls, system_time.nanoseconds, - device_time.seconds_ls, device_time.nanoseconds); + ( "ptp_message::FollowUp::processMessage System time: %u,%u " + "Device Time: %u,%u", + system_time.seconds_ls, system_time.nanoseconds, + device_time.seconds_ls, device_time.nanoseconds); /*Update information on local status structure.*/ - scaledLastGmFreqChange = (int32_t)((1.0/local_clock_adjustment -1.0) * (1ULL << 41)); - scaledLastGmPhaseChange.setLSB( tlv.getRateOffset() ); - port->getClock()->getFUPStatus()->setScaledLastGmFreqChange( scaledLastGmFreqChange ); - port->getClock()->getFUPStatus()->setScaledLastGmPhaseChange( scaledLastGmPhaseChange ); + scaledLastGmFreqChange = (int32_t) + ((1.0 / local_clock_adjustment - 1.0) * (1ULL << 41)); + scaledLastGmPhaseChange.setLSB(tlv.getRateOffset( )); + port->getClock()->getFUPStatus()->setScaledLastGmFreqChange + ( scaledLastGmFreqChange ); + port->getClock()->getFUPStatus()->setScaledLastGmPhaseChange + ( scaledLastGmPhaseChange ); if( port->getPortState() == PTP_SLAVE ) { - /* The sync_count counts the number of sync messages received - that influence the time on the device. Since adjustments are only - made in the PTP_SLAVE state, increment it here */ + /* + * The sync_count counts the number of sync messages received + * that influence the time on the device. Since adjustments are + * only made in the PTP_SLAVE state, increment it here + */ port->incSyncCount(); - /* Do not call calcLocalSystemClockRateDifference it updates state - global to the clock object and if we are master then the network - is transitioning to us not being master but the master process - is still running locally */ - local_system_freq_offset = - port->getClock() + /* + * Do not call calcLocalSystemClockRateDifference it updates + * state global to the clock object and if we are master then + * the network is transitioning to us not being master but + * the master process is still running locally + */ + local_system_freq_offset = port->getClock() ->calcLocalSystemClockRateDifference ( device_time, system_time ); TIMESTAMP_SUB_NS - ( system_time, (uint64_t) - (((FrequencyRatio) device_sync_time_offset)/ - local_system_freq_offset) ); + (system_time, (uint64_t) + (((FrequencyRatio)device_sync_time_offset) / + local_system_freq_offset)); local_system_offset = - TIMESTAMP_TO_NS(system_time) - TIMESTAMP_TO_NS(sync_arrival); + TIMESTAMP_TO_NS( system_time ) - + TIMESTAMP_TO_NS( sync_arrival ); port->getClock()->setMasterOffset - ( port, scalar_offset, sync_arrival, local_clock_adjustment, - local_system_offset, system_time, local_system_freq_offset, - port->getSyncCount(), port->getPdelayCount(), - port->getPortState(), port->getAsCapable() ); + ( port, scalar_offset, sync_arrival, local_clock_adjustment, + local_system_offset, system_time, local_system_freq_offset, + port->getSyncCount(), port->getPdelayCount(), + port->getPortState(), port->getAsCapable( )); + port->syncDone(); // Restart the SYNC_RECEIPT timer port->startSyncReceiptTimer((unsigned long long) - (SYNC_RECEIPT_TIMEOUT_MULTIPLIER * - ((double) pow((double)2, port->getSyncInterval()) * - 1000000000.0))); + (SYNC_RECEIPT_TIMEOUT_MULTIPLIER * + ((double)pow((double)2, port->getSyncInterval()) * + 1000000000.0))); } uint16_t lastGmTimeBaseIndicator; lastGmTimeBaseIndicator = port->getLastGmTimeBaseIndicator(); - if ((lastGmTimeBaseIndicator > 0) && (tlv.getGmTimeBaseIndicator() != lastGmTimeBaseIndicator)) { - GPTP_LOG_EXCEPTION("Sync discontinuity"); + if (( lastGmTimeBaseIndicator > 0 ) && + ( tlv.getGmTimeBaseIndicator( ) != lastGmTimeBaseIndicator )) + { + GPTP_LOG_EXCEPTION( "Sync discontinuity" ); } - port->setLastGmTimeBaseIndicator(tlv.getGmTimeBaseIndicator()); + port->setLastGmTimeBaseIndicator( tlv.getGmTimeBaseIndicator( )); done: _gc = true; - port->setLastSync(NULL); - delete sync; return; } +void PTPMessageFollowUp::processMessage( CommonPort *port ) +{ + Timestamp sync_arrival; + EtherPort *eport = dynamic_cast <EtherPort *> (port); + if (eport == NULL) + { + GPTP_LOG_ERROR + ( "Discarding followup message on wrong port type" ); + return; + } + + GPTP_LOG_DEBUG("Processing a follow-up message"); + + // Expire any SYNC_RECEIPT timers that exist + port->stopSyncReceiptTimer(); + + if (port->getPortState() == PTP_DISABLED ) { + // Do nothing Sync messages should be ignored when in this state + return; + } + if (port->getPortState() == PTP_FAULTY) { + // According to spec recovery is implementation specific + eport->recoverPort(); + return; + } + + PTPMessageSync *sync = eport->getLastSync(); + { + PortIdentity sync_id; + if( sync == NULL ) + { + GPTP_LOG_ERROR("Received Follow Up but there is no " + "sync message"); + return; + } + sync->getPortIdentity(&sync_id); + + if( sync->getSequenceId() != sequenceId || + sync_id != *sourcePortIdentity ) + { + unsigned int cnt = 0; + + if( !port->incWrongSeqIDCounter( &cnt )) + { + port->becomeMaster( true ); + port->setWrongSeqIDCounter(0); + } + GPTP_LOG_ERROR + ( "Received Follow Up %d times but cannot " + "find corresponding Sync", cnt ); + goto done; + } + } + + if( sync->getTimestamp()._version != port->getTimestampVersion( )) + { + GPTP_LOG_ERROR( "Received Follow Up but timestamp version " + "indicates Sync is out of date" ); + goto done; + } + + sync_arrival = sync->getTimestamp(); + + processMessage(port, sync_arrival); + +done: + eport->setLastSync(NULL); + delete sync; +} + PTPMessagePathDelayReq::PTPMessagePathDelayReq ( EtherPort *port ) : PTPMessageCommon( port ) { @@ -1170,7 +1243,7 @@ PTPMessagePathDelayReq::PTPMessagePathDelayReq return; } -void PTPMessagePathDelayReq::processMessage( EtherPort *port ) +void PTPMessagePathDelayReq::processMessage( CommonPort *port ) { OSTimer *timer = port->getTimerFactory()->createTimer(); PortIdentity resp_fwup_id; @@ -1179,6 +1252,13 @@ void PTPMessagePathDelayReq::processMessage( EtherPort *port ) PortIdentity resp_id; PTPMessagePathDelayRespFollowUp *resp_fwup; + EtherPort *eport = dynamic_cast <EtherPort *> (port); + if (eport == NULL) + { + GPTP_LOG_ERROR( "Received Pdelay Request on wrong port type" ); + goto done; + } + if (port->getPortState() == PTP_DISABLED) { // Do nothing all messages should be ignored when in this state goto done; @@ -1186,14 +1266,14 @@ void PTPMessagePathDelayReq::processMessage( EtherPort *port ) if (port->getPortState() == PTP_FAULTY) { // According to spec recovery is implementation specific - port->recoverPort(); + eport->recoverPort(); goto done; } port->incCounter_ieee8021AsPortStatRxPdelayRequest(); /* Generate and send message */ - resp = new PTPMessagePathDelayResp(port); + resp = new PTPMessagePathDelayResp(eport); port->getPortIdentity(resp_id); resp->setPortIdentity(&resp_id); resp->setSequenceId(sequenceId); @@ -1211,7 +1291,7 @@ void PTPMessagePathDelayReq::processMessage( EtherPort *port ) resp->setRequestReceiptTimestamp(_timestamp); port->getTxLock(); - resp->sendPort(port, sourcePortIdentity); + resp->sendPort(eport, sourcePortIdentity); GPTP_LOG_DEBUG("*** Sent PDelay Response message"); port->putTxLock(); @@ -1224,7 +1304,7 @@ void PTPMessagePathDelayReq::processMessage( EtherPort *port ) #endif } - resp_fwup = new PTPMessagePathDelayRespFollowUp(port); + resp_fwup = new PTPMessagePathDelayRespFollowUp(eport); port->getPortIdentity(resp_fwup_id); resp_fwup->setPortIdentity(&resp_fwup_id); resp_fwup->setSequenceId(sequenceId); @@ -1248,7 +1328,7 @@ void PTPMessagePathDelayReq::processMessage( EtherPort *port ) GPTP_LOG_VERBOSE("#3 Correction Field: %Ld", turnaround); resp_fwup->setCorrectionField(0); - resp_fwup->sendPort(port, sourcePortIdentity); + resp_fwup->sendPort(eport, sourcePortIdentity); GPTP_LOG_DEBUG("*** Sent PDelay Response FollowUp message"); @@ -1306,21 +1386,29 @@ PTPMessagePathDelayResp::~PTPMessagePathDelayResp() delete requestingPortIdentity; } -void PTPMessagePathDelayResp::processMessage( EtherPort *port ) +void PTPMessagePathDelayResp::processMessage( CommonPort *port ) { + EtherPort *eport = dynamic_cast <EtherPort *> (port); + if (eport == NULL) + { + GPTP_LOG_ERROR( "Received Pdelay Resp on wrong port type" ); + _gc = true; + return; + } + if (port->getPortState() == PTP_DISABLED) { // Do nothing all messages should be ignored when in this state return; } if (port->getPortState() == PTP_FAULTY) { // According to spec recovery is implementation specific - port->recoverPort(); + eport->recoverPort(); return; } port->incCounter_ieee8021AsPortStatRxPdelayResponse(); - if (port->tryPDelayRxLock() != true) { + if (eport->tryPDelayRxLock() != true) { GPTP_LOG_ERROR("Failed to get PDelay RX Lock"); return; } @@ -1330,7 +1418,7 @@ void PTPMessagePathDelayResp::processMessage( EtherPort *port ) uint16_t resp_port_number; uint16_t oldresp_port_number; - PTPMessagePathDelayResp *old_pdelay_resp = port->getLastPDelayResp(); + PTPMessagePathDelayResp *old_pdelay_resp = eport->getLastPDelayResp(); if( old_pdelay_resp == NULL ) { goto bypass_verify_duplicate; } @@ -1349,36 +1437,36 @@ void PTPMessagePathDelayResp::processMessage( EtherPort *port ) { /*If the duplicates are in sequence and from different sources*/ if( (resp_port_number != oldresp_port_number ) && ( - (port->getLastInvalidSeqID() + 1 ) == getSequenceId() || - port->getDuplicateRespCounter() == 0 ) ){ + (eport->getLastInvalidSeqID() + 1 ) == getSequenceId() || + eport->getDuplicateRespCounter() == 0 ) ){ GPTP_LOG_ERROR("Two responses for same Request. seqID %d. First Response Port# %hu. Second Port# %hu. Counter %d", - getSequenceId(), oldresp_port_number, resp_port_number, port->getDuplicateRespCounter()); + getSequenceId(), oldresp_port_number, resp_port_number, eport->getDuplicateRespCounter()); - if( port->incrementDuplicateRespCounter() ) { + if( eport->incrementDuplicateRespCounter() ) { GPTP_LOG_ERROR("Remote misbehaving. Stopping PDelay Requests for 5 minutes."); - port->stopPDelay(); - port->getClock()->addEventTimerLocked + eport->stopPDelay(); + eport->getClock()->addEventTimerLocked (port, PDELAY_RESP_PEER_MISBEHAVING_TIMEOUT_EXPIRES, (int64_t)(300 * 1000000000.0)); } } else { - port->setDuplicateRespCounter(0); + eport->setDuplicateRespCounter(0); } - port->setLastInvalidSeqID(getSequenceId()); + eport->setLastInvalidSeqID(getSequenceId()); } else { - port->setDuplicateRespCounter(0); + eport->setDuplicateRespCounter(0); } bypass_verify_duplicate: - port->setLastPDelayResp(this); + eport->setLastPDelayResp(this); if (old_pdelay_resp != NULL) { delete old_pdelay_resp; } - port->putPDelayRxLock(); + eport->putPDelayRxLock(); _gc = false; return; @@ -1467,40 +1555,41 @@ PTPMessagePathDelayRespFollowUp::~PTPMessagePathDelayRespFollowUp() #define US_PER_SEC 1000000 void PTPMessagePathDelayRespFollowUp::processMessage -( EtherPort *port ) +( CommonPort *port ) { + PTPMessagePathDelayReq *req; + PTPMessagePathDelayResp *resp; + Timestamp remote_resp_tx_timestamp(0, 0, 0); Timestamp request_tx_timestamp(0, 0, 0); Timestamp remote_req_rx_timestamp(0, 0, 0); Timestamp response_rx_timestamp(0, 0, 0); + EtherPort *eport = dynamic_cast <EtherPort *> (port); + if (eport == NULL) + { + GPTP_LOG_ERROR( "Received Pdelay Response FollowUp on wrong " + "port type" ); + goto abort; + } + if (port->getPortState() == PTP_DISABLED) { // Do nothing all messages should be ignored when in this state return; } if (port->getPortState() == PTP_FAULTY) { // According to spec recovery is implementation specific - port->recoverPort(); + eport->recoverPort(); return; } port->incCounter_ieee8021AsPortStatRxPdelayResponseFollowUp(); - if (port->tryPDelayRxLock() != true) + if (eport->tryPDelayRxLock() != true) return; - PTPMessagePathDelayReq *req = port->getLastPDelayReq(); - PTPMessagePathDelayResp *resp = port->getLastPDelayResp(); - - PortIdentity req_id; - PortIdentity resp_id; - PortIdentity fup_sourcePortIdentity; - PortIdentity resp_sourcePortIdentity; - ClockIdentity req_clkId; - ClockIdentity resp_clkId; - - uint16_t resp_port_number; - uint16_t req_port_number; + req = eport->getLastPDelayReq(); + resp = eport->getLastPDelayResp(); if (req == NULL) { /* Shouldn't happen */ @@ -1517,63 +1606,81 @@ void PTPMessagePathDelayRespFollowUp::processMessage goto abort; } - req->getPortIdentity(&req_id); - resp->getRequestingPortIdentity(&resp_id); - req_clkId = req_id.getClockIdentity(); - resp_clkId = resp_id.getClockIdentity(); - resp_id.getPortNumber(&resp_port_number); - requestingPortIdentity->getPortNumber(&req_port_number); - resp->getPortIdentity(&resp_sourcePortIdentity); - getPortIdentity(&fup_sourcePortIdentity); - if( req->getSequenceId() != sequenceId ) { GPTP_LOG_ERROR - (">>> Received PDelay FUP has different seqID than the PDelay request (%d/%d)", - sequenceId, req->getSequenceId() ); + ( "Received PDelay FUP has different seqID than the " + "PDelay request (%d/%d)", + sequenceId, req->getSequenceId() ); goto abort; } - /* - * IEEE 802.1AS, Figure 11-8, subclause 11.2.15.3 - */ - if (resp->getSequenceId() != sequenceId) { - GPTP_LOG_ERROR + { + PortIdentity req_id; + PortIdentity resp_id; + uint16_t resp_port_number; + uint16_t req_port_number; + + ClockIdentity resp_clkId = resp_id.getClockIdentity(); + ClockIdentity req_clkId = req_id.getClockIdentity(); + PortIdentity fup_sourcePortIdentity; + PortIdentity resp_sourcePortIdentity; + + req->getPortIdentity(&req_id); + resp->getRequestingPortIdentity(&resp_id); + + resp_id.getPortNumber(&resp_port_number); + requestingPortIdentity->getPortNumber(&req_port_number); + + resp->getPortIdentity(&resp_sourcePortIdentity); + getPortIdentity(&fup_sourcePortIdentity); + + /* + * IEEE 802.1AS, Figure 11-8, subclause 11.2.15.3 + */ + if (resp->getSequenceId() != sequenceId) { + GPTP_LOG_ERROR ("Received PDelay Response Follow Up but cannot find " - "corresponding response"); - GPTP_LOG_ERROR("%hu, %hu, %hu, %hu", resp->getSequenceId(), - sequenceId, resp_port_number, req_port_number); + "corresponding response"); + GPTP_LOG_ERROR( "%hu, %hu, %hu, %hu", + resp->getSequenceId(), sequenceId, + resp_port_number, req_port_number ); - goto abort; - } + goto abort; + } - /* - * IEEE 802.1AS, Figure 11-8, subclause 11.2.15.3 - */ - if (req_clkId != resp_clkId ) { - GPTP_LOG_ERROR - ("ClockID Resp/Req differs. PDelay Response ClockID: %s PDelay Request ClockID: %s", - req_clkId.getIdentityString().c_str(), resp_clkId.getIdentityString().c_str() ); - goto abort; - } + /* + * IEEE 802.1AS, Figure 11-8, subclause 11.2.15.3 + */ + if (req_clkId != resp_clkId) { + GPTP_LOG_ERROR + ( "ClockID Resp/Req differs. PDelay Response ClockID: " + "%s PDelay Request ClockID: %s", + req_clkId.getIdentityString().c_str(), + resp_clkId.getIdentityString().c_str( )); + goto abort; + } - /* - * IEEE 802.1AS, Figure 11-8, subclause 11.2.15.3 - */ - if ( resp_port_number != req_port_number ) { - GPTP_LOG_ERROR - ("Request port number (%hu) is different from Response port number (%hu)", - resp_port_number, req_port_number); + /* + * IEEE 802.1AS, Figure 11-8, subclause 11.2.15.3 + */ + if (resp_port_number != req_port_number) { + GPTP_LOG_ERROR + ( "Request port number (%hu) is different from " + "Response port number (%hu)", + req_port_number, resp_port_number ); - goto abort; - } + goto abort; + } - /* - * IEEE 802.1AS, Figure 11-8, subclause 11.2.15.3 - */ - if ( fup_sourcePortIdentity != resp_sourcePortIdentity ) { - GPTP_LOG_ERROR("Source port identity from PDelay Response/FUP differ"); + /* + * IEEE 802.1AS, Figure 11-8, subclause 11.2.15.3 + */ + if (fup_sourcePortIdentity != resp_sourcePortIdentity) { + GPTP_LOG_ERROR( "Source port identity from " + "PDelay Response/FUP differ" ); - goto abort; + goto abort; + } } port->getClock()->deleteEventTimerLocked @@ -1589,20 +1696,22 @@ void PTPMessagePathDelayRespFollowUp::processMessage /* Assume that we are a two step clock, otherwise originTimestamp may be used */ request_tx_timestamp = req->getTimestamp(); - if( request_tx_timestamp.nanoseconds == INVALID_TIMESTAMP.nanoseconds ) { + if( request_tx_timestamp.nanoseconds == INVALID_TIMESTAMP.nanoseconds ) + { /* Stop processing the packet */ goto abort; } + if (request_tx_timestamp.nanoseconds == PDELAY_PENDING_TIMESTAMP.nanoseconds) { // Defer processing if( - port->getLastPDelayRespFollowUp() != NULL && - port->getLastPDelayRespFollowUp() != this ) + eport->getLastPDelayRespFollowUp() != NULL && + eport->getLastPDelayRespFollowUp() != this ) { - delete port->getLastPDelayRespFollowUp(); + delete eport->getLastPDelayRespFollowUp(); } - port->setLastPDelayRespFollowUp(this); + eport->setLastPDelayRespFollowUp(this); port->getClock()->addEventTimerLocked (port, PDELAY_DEFERRED_PROCESSING, 1000000); goto defer; @@ -1646,7 +1755,8 @@ void PTPMessagePathDelayRespFollowUp::processMessage if ( port->getPeerRateOffset() > .998 && port->getPeerRateOffset() < 1.002 ) { - turn_around = (int64_t) (turn_around * port->getPeerRateOffset()); + turn_around = (int64_t) + (turn_around * port->getPeerRateOffset()); } GPTP_LOG_VERBOSE @@ -1671,42 +1781,52 @@ void PTPMessagePathDelayRespFollowUp::processMessage FrequencyRatio rate_offset; if( port->getPeerOffset( prev_peer_ts_mine, prev_peer_ts_theirs )) { FrequencyRatio upper_ratio_limit, lower_ratio_limit; - upper_ratio_limit = PPM_OFFSET_TO_RATIO(UPPER_LIMIT_PPM); - lower_ratio_limit = PPM_OFFSET_TO_RATIO(LOWER_LIMIT_PPM); - - mine_elapsed = TIMESTAMP_TO_NS(request_tx_timestamp)-TIMESTAMP_TO_NS(prev_peer_ts_mine); - theirs_elapsed = TIMESTAMP_TO_NS(remote_req_rx_timestamp)-TIMESTAMP_TO_NS(prev_peer_ts_theirs); + upper_ratio_limit = + PPM_OFFSET_TO_RATIO(UPPER_LIMIT_PPM); + lower_ratio_limit = + PPM_OFFSET_TO_RATIO(LOWER_LIMIT_PPM); + + mine_elapsed = TIMESTAMP_TO_NS(request_tx_timestamp) - + TIMESTAMP_TO_NS(prev_peer_ts_mine); + theirs_elapsed = + TIMESTAMP_TO_NS(remote_req_rx_timestamp) - + TIMESTAMP_TO_NS(prev_peer_ts_theirs); theirs_elapsed -= port->getLinkDelay(); theirs_elapsed += link_delay < 0 ? 0 : link_delay; - rate_offset = ((FrequencyRatio) mine_elapsed)/theirs_elapsed; + rate_offset = ((FrequencyRatio) mine_elapsed) + / theirs_elapsed; - if( rate_offset < upper_ratio_limit && rate_offset > lower_ratio_limit ) { + if( rate_offset < upper_ratio_limit && + rate_offset > lower_ratio_limit ) port->setPeerRateOffset(rate_offset); - } } } - if( !port->setLinkDelay( link_delay ) ) { - if (!port->getAutomotiveProfile()) { - GPTP_LOG_ERROR("Link delay %ld beyond neighborPropDelayThresh; not AsCapable", link_delay); + if( !port->setLinkDelay( link_delay )) + { + if( !eport->getAutomotiveProfile( )) + { + GPTP_LOG_ERROR( "Link delay %ld beyond " + "neighborPropDelayThresh; " + "not AsCapable", link_delay ); port->setAsCapable( false ); } - } else { - if (!port->getAutomotiveProfile()) { + } else + { + if( !eport->getAutomotiveProfile( )) port->setAsCapable( true ); - } } port->setPeerOffset( request_tx_timestamp, remote_req_rx_timestamp ); abort: delete req; - port->setLastPDelayReq(NULL); + eport->setLastPDelayReq(NULL); delete resp; - port->setLastPDelayResp(NULL); + eport->setLastPDelayResp(NULL); _gc = true; defer: - port->putPDelayRxLock(); + eport->putPDelayRxLock(); return; } @@ -1821,7 +1941,7 @@ bool PTPMessageSignalling::sendPort return true; } -void PTPMessageSignalling::processMessage( EtherPort *port ) +void PTPMessageSignalling::processMessage( CommonPort *port ) { long long unsigned int waitTime; @@ -1834,7 +1954,7 @@ void PTPMessageSignalling::processMessage( EtherPort *port ) char announceInterval = tlv.getAnnounceInterval(); if (linkDelayInterval == PTPMessageSignalling::sigMsgInterval_Initial) { - port->setInitPDelayInterval(); + port->resetInitPDelayInterval(); waitTime = ((long long) (pow((double)2, port->getPDelayInterval()) * 1000000000.0)); waitTime = waitTime > EVENT_TIMER_GRANULARITY ? waitTime : EVENT_TIMER_GRANULARITY; diff --git a/daemons/gptp/common/wireless_port.cpp b/daemons/gptp/common/wireless_port.cpp new file mode 100644 index 00000000..40db5015 --- /dev/null +++ b/daemons/gptp/common/wireless_port.cpp @@ -0,0 +1,242 @@ +/****************************************************************************** + +Copyright (c) 2009-2015, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the Intel Corporation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include <wireless_port.hpp> +#include <wireless_tstamper.hpp> +#include <avbts_clock.hpp> + +WirelessPort::~WirelessPort() +{ + // Intentionally left blank +} + +WirelessPort::WirelessPort( PortInit_t *portInit, LinkLayerAddress peer_addr ) +: CommonPort( portInit ) +{ + this->peer_addr = peer_addr; + + setAsCapable( true ); + if ( getInitSyncInterval() == LOG2_INTERVAL_INVALID ) + setInitSyncInterval (-3 ); // 125 ms + if ( getInitPDelayInterval() == LOG2_INTERVAL_INVALID ) + setInitPDelayInterval( 127 ); // 1 second + + prev_dialog.dialog_token = 0; +} + +bool WirelessPort::_init_port(void) +{ + port_ready_condition = condition_factory->createCondition(); + + return true; +} + +bool WirelessPort::_processEvent( Event e ) +{ + bool ret; + + switch (e) + { + default: + GPTP_LOG_ERROR + ("Unhandled event type in " + "WirelessPort::processEvent(), %d", e); + ret = false; + break; + + case POWERUP: + case INITIALIZE: + { + port_ready_condition->wait_prelock(); + + if ( !linkOpen(_openPort, (void *)this )) + { + GPTP_LOG_ERROR( "Error creating port thread" ); + ret = false; + break; + } + + port_ready_condition->wait(); + + ret = true; + break; + } + + case SYNC_INTERVAL_TIMEOUT_EXPIRES: + { + WirelessTimestamper *timestamper = + dynamic_cast<WirelessTimestamper *>( _hw_timestamper ); + PTPMessageFollowUp *follow_up; + PortIdentity dest_id; + uint16_t seq; + uint8_t buffer[128]; + size_t length; + + memset(buffer, 0, sizeof( buffer )); + + if( prev_dialog.dialog_token != 0 ) + { + follow_up = new PTPMessageFollowUp( this ); + + getPortIdentity(dest_id); + follow_up->setPortIdentity(&dest_id); + follow_up->setSequenceId(prev_dialog.fwup_seq); + follow_up->setPreciseOriginTimestamp + ( prev_dialog.action ); + length = follow_up->buildMessage + (this, buffer + timestamper->getFwUpOffset()); + + delete follow_up; + } + + seq = getNextSyncSequenceId(); + ret = timestamper->requestTimingMeasurement + ( &peer_addr, seq, &prev_dialog, buffer, (int)length ) + == net_succeed; + + ret = true; + break; + } + + case ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: + case SYNC_RECEIPT_TIMEOUT_EXPIRES: + ret = false; + break; + + case STATE_CHANGE_EVENT: + ret = false; + break; + } + + return ret; +} + +bool WirelessPort::openPort() +{ + port_ready_condition->signal(); + + while (true) + { + uint8_t buf[128]; + LinkLayerAddress remote; + net_result rrecv; + size_t length = sizeof(buf); + uint32_t link_speed; + + if(( rrecv = recv( &remote, buf, length, link_speed )) + == net_succeed ) + { + processMessage + ((char *)buf, (int)length, &remote, link_speed ); + } + else if( rrecv == net_fatal ) + { + GPTP_LOG_ERROR( "read from network interface failed" ); + this->processEvent( FAULT_DETECTED ); + break; + } + } + + return false; +} + +void WirelessPort::sendGeneralPort +(uint16_t etherType, uint8_t * buf, int len, MulticastType mcast_type, + PortIdentity * destIdentity) +{ + net_result rtx = send( &peer_addr, etherType, buf, len, false); + if( rtx != net_succeed ) + GPTP_LOG_ERROR( "sendGeneralPort(): failure" ); + + return; +} + +void WirelessPort::processMessage +(char *buf, int length, LinkLayerAddress *remote, uint32_t link_speed) +{ + GPTP_LOG_VERBOSE( "Processing network buffer" ); + + PTPMessageCommon *msg = + buildPTPMessage( buf, (int)length, remote, this ); + + if( msg == NULL ) + { + GPTP_LOG_ERROR( "Discarding invalid message" ); + return; + } + GPTP_LOG_VERBOSE( "Processing message" ); + + if( !msg->isEvent( )) + { + msg->processMessage( this ); + } else + { + GPTP_LOG_ERROR( "Received event message on port " + "incapable of processing" ); + msg->PTPMessageCommon::processMessage( this ); + } + + if (msg->garbage()) + delete msg; +} + +void WirelessPort::becomeSlave(bool restart_syntonization) +{ + clock->deleteEventTimerLocked(this, ANNOUNCE_INTERVAL_TIMEOUT_EXPIRES); + clock->deleteEventTimerLocked( this, SYNC_INTERVAL_TIMEOUT_EXPIRES ); + + setPortState( PTP_SLAVE ); + + GPTP_LOG_STATUS("Switching to Slave"); + if( restart_syntonization ) clock->newSyntonizationSetPoint(); + + getClock()->updateFUPInfo(); +} + +void WirelessPort::becomeMaster(bool annc) +{ + setPortState( PTP_MASTER ); + // Stop announce receipt timeout timer + clock->deleteEventTimerLocked( this, ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES); + + // Stop sync receipt timeout timer + stopSyncReceiptTimer(); + + if (annc) + startAnnounce(); + + startSyncIntervalTimer( 16000000 ); + GPTP_LOG_STATUS( "Switching to Master" ); + + clock->updateFUPInfo(); +} diff --git a/daemons/gptp/common/wireless_port.hpp b/daemons/gptp/common/wireless_port.hpp new file mode 100644 index 00000000..bfffbd47 --- /dev/null +++ b/daemons/gptp/common/wireless_port.hpp @@ -0,0 +1,179 @@ +/****************************************************************************** + +Copyright (c) 2009-2015, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the Intel Corporation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#ifndef WIRELESS_PORT_HPP +#define WIRELESS_PORT_HPP + +#include <common_port.hpp> +#include <avbts_oscondition.hpp> + +class WirelessDialog +{ +public: + Timestamp action; + uint64_t action_devclk; + Timestamp ack; + uint64_t ack_devclk; + uint8_t dialog_token; + uint16_t fwup_seq; + + WirelessDialog( uint32_t action, uint32_t ack, uint8_t dialog_token ) + { + this->action_devclk = action; this->ack_devclk = ack; + this->dialog_token = dialog_token; + } + + WirelessDialog() { dialog_token = 0; } + + WirelessDialog & operator=( const WirelessDialog & a ) + { + if (this != &a) + { + this->ack = a.ack; + this->ack_devclk = a.ack_devclk; + this->action = a.action; + this->action_devclk = a.action_devclk; + this->dialog_token = a.dialog_token; + this->fwup_seq = a.fwup_seq; + } + return *this; + } +}; + +class WirelessPort : public CommonPort +{ +private: + OSCondition *port_ready_condition; + LinkLayerAddress peer_addr; + WirelessDialog prev_dialog; + +public: + WirelessPort( PortInit_t *portInit, LinkLayerAddress peer_addr ); + virtual ~WirelessPort(); + + /** + * @brief Media specific port initialization + * @return true on success + */ + bool _init_port( void ); + + /** + * @brief Perform media specific event handling action + * @return true if event is handled without errors + */ + bool _processEvent( Event e ); + + /** + * @brief Process message + * @param buf [in] Pointer to the data buffer + * @param length Size of the message + * @param remote [in] source address of message + * @param link_speed [in] for receive operation + * @return void + */ + void processMessage + (char *buf, int length, LinkLayerAddress *remote, uint32_t link_speed); + + /** + * @brief Sends a general message to a port. No timestamps + * @param buf [in] Pointer to the data buffer + * @param len Size of the message + * @param mcast_type Enumeration + * MulticastType (pdelay, none or other). Depracated. + * @param destIdentity Destination port identity + * @return void + */ + void sendGeneralPort + ( uint16_t etherType, uint8_t * buf, int len, MulticastType mcast_type, + PortIdentity * destIdentity ); + + /** + * @brief Nothing required for wireless port + */ + void syncDone() {} + + /** + * @brief Switches port to a gPTP master + * @param annc If TRUE, starts announce event timer. + * @return void + */ + void becomeMaster( bool annc ); + + /** + * @brief Switches port to a gPTP slave. + * @param restart_syntonization if TRUE, restarts the syntonization + * @return void + */ + void becomeSlave( bool restart_syntonization ); + + /** + * @brief Receives messages from the network interface + * @return Its an infinite loop. Returns false in case of error. + */ + bool openPort(); + + /** + * @brief Wraps open port method for argument to thread + * @param larg pointer to WirelessPort object + * @return thread exit code + */ + static OSThreadExitCode _openPort( void *larg ) + { + WirelessPort *port = (decltype(port))larg; + + if (!port->openPort()) + return osthread_error; + + return osthread_ok; + } + + /** + * @brief Sets previous dialog + * @param dialog new value of prev_dialog + */ + void setPrevDialog( WirelessDialog *dialog ) + { + prev_dialog = *dialog; + } + + /** + * @brief Sets previous dialog + * @return reference to prev_dialog + */ + WirelessDialog *getPrevDialog( void ) + { + return &prev_dialog; + } +}; + +#endif/*WIRELESS_PORT_HPP*/ diff --git a/daemons/gptp/common/wireless_tstamper.cpp b/daemons/gptp/common/wireless_tstamper.cpp new file mode 100644 index 00000000..10c3be06 --- /dev/null +++ b/daemons/gptp/common/wireless_tstamper.cpp @@ -0,0 +1,127 @@ +/****************************************************************************** + +Copyright (c) 2009-2015, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the Intel Corporation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include <wireless_tstamper.hpp> +#include <wireless_port.hpp> + +net_result WirelessTimestamper::requestTimingMeasurement +( LinkLayerAddress *dest, uint16_t seq, WirelessDialog *prev_dialog, + uint8_t *follow_up, int followup_length ) +{ + WirelessDialog next_dialog; + net_result retval; + TIMINGMSMT_REQUEST *req; + + // Valid dialog token > 0 && < 256 + next_dialog.dialog_token = (seq % MAX_DIALOG_TOKEN) + 1; + next_dialog.fwup_seq = seq; + next_dialog.action_devclk = 0; + req = (TIMINGMSMT_REQUEST *)follow_up; + + // File in request + req->DialogToken = next_dialog.dialog_token; + req->FollowUpDialogToken = prev_dialog->dialog_token; + req->Category = 0x0; + req->Action = 0x0; + req->WiFiVSpecHdr.ElementId = FWUP_VDEF_TAG; + req->WiFiVSpecHdr.Length = 0; + if( req->FollowUpDialogToken != 0 && prev_dialog->action_devclk != 0 ) + { + req->T1 = (uint32_t)prev_dialog->action_devclk; + req->T4 = (uint32_t)prev_dialog->ack_devclk; + req->WiFiVSpecHdr.Length = followup_length + + (uint8_t)sizeof(FwUpLabel); // 1 byte type + req->PtpSpec.FwUpLabel = FwUpLabel; + } + dest->toOctetArray(req->PeerMACAddress); + + retval = _requestTimingMeasurement( req ); + port->setPrevDialog(&next_dialog); + + return retval; +} + +void WirelessTimestamper::timingMeasurementConfirmCB +( LinkLayerAddress addr, WirelessDialog *dialog ) +{ + WirelessDialog *prev_dialog = port->getPrevDialog(); + + // Translate action dev clock to Timestamp + // ACK timestamp isn't needed + dialog->action.set64(dialog->action_devclk*10); + + if (dialog->dialog_token == prev_dialog->dialog_token) + { + dialog->fwup_seq = prev_dialog->fwup_seq; + port->setPrevDialog(dialog); + } +} + +void WirelessTimestamper::timeMeasurementIndicationCB +( LinkLayerAddress addr, WirelessDialog *current, WirelessDialog *previous, + uint8_t *buf, size_t buflen ) +{ + uint64_t link_delay; + WirelessDialog *prev_local; + PTPMessageCommon *msg; + PTPMessageFollowUp *fwup; + // Translate devclk scalar to Timestamp + + msg = buildPTPMessage((char *)buf, (int)buflen, &addr, port); + fwup = dynamic_cast<PTPMessageFollowUp *> (msg); + current->action_devclk *= 10; + current->ack_devclk *= 10; + current->action.set64(current->action_devclk); + prev_local = port->getPrevDialog(); + if ( previous->dialog_token == prev_local->dialog_token && + fwup != NULL && previous->action_devclk != 0) + { + previous->action_devclk *= 10; + previous->ack_devclk *= 10; + unsigned round_trip = (unsigned) + (previous->ack_devclk - previous->action_devclk); + unsigned turn_around = (unsigned) + (prev_local->ack_devclk - prev_local->action_devclk); + link_delay = (round_trip - turn_around) / 2; + port->setLinkDelay(link_delay); + GPTP_LOG_VERBOSE( "Link Delay = %llu(RT=%u,TA=%u," + "T4=%llu,T1=%llu,DT=%hhu)", + link_delay, round_trip, turn_around, + previous->ack_devclk, + previous->action_devclk, + previous->dialog_token); + fwup->processMessage(port, prev_local->action); + } + + port->setPrevDialog(current); +} diff --git a/daemons/gptp/common/wireless_tstamper.hpp b/daemons/gptp/common/wireless_tstamper.hpp new file mode 100644 index 00000000..79413d0d --- /dev/null +++ b/daemons/gptp/common/wireless_tstamper.hpp @@ -0,0 +1,221 @@ +/****************************************************************************** + +Copyright (c) 2009-2015, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the Intel Corporation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#ifndef WIRELESS_TSTAMPER_HPP +#define WIRELESS_TSTAMPER_HPP + +#include <common_tstamper.hpp> +#include <wireless_port.hpp> + +#define MAX_DIALOG_TOKEN 255 +#define OUI_8021AS_OCTETS { 0x00, 0x80, 0xC2 } +static const uint8_t OUI_8021AS[] = OUI_8021AS_OCTETS; +#define FWUP_TYPE 0 +#define FWUP_VDEF_TAG 0xDD /* Vendor Defined Tag */ + +typedef enum _WIRELESS_EVENT_TYPE +{ + TIMINGMSMT_EVENT = 0, + TIMINGMSMT_CONFIRM_EVENT, + TIMINGMSMT_CORRELATEDTIME_EVENT, +} WIRELESS_EVENT_TYPE; + +#pragma pack(push, 1) +typedef struct +{ + uint8_t oui[sizeof(OUI_8021AS)]; + uint8_t type; +} FwUpLabel_t; + +typedef struct _PTP_SPEC +{ + FwUpLabel_t FwUpLabel; + uint8_t fwup_data[1]; +} PTP_SPEC; + +static const FwUpLabel_t FwUpLabel = { OUI_8021AS_OCTETS, FWUP_TYPE }; + +typedef struct _WIFI_VENDOR_SPEC_HDR +{ + uint8_t ElementId; + uint8_t Length; +} WIFI_VENDOR_SPEC_HDR; +#pragma pack(pop) + +typedef struct _TIMINGMSMT_REQUEST +{ + uint8_t PeerMACAddress[ETHER_ADDR_OCTETS]; + uint8_t Category; + uint8_t Action; + uint8_t DialogToken; + uint8_t FollowUpDialogToken; + uint32_t T1; + uint32_t T4; + uint8_t MaxT1Error; + uint8_t MaxT4Error; + + WIFI_VENDOR_SPEC_HDR WiFiVSpecHdr; + PTP_SPEC PtpSpec; +} TIMINGMSMT_REQUEST; + +typedef struct _TIMINGMSMT_EVENT_DATA +{ + uint8_t PeerMACAddress[ETHER_ADDR_OCTETS]; + uint32_t DialogToken; + uint32_t FollowUpDialogToken; + uint64_t T1; + uint32_t MaxT1Error; + uint64_t T4; + uint32_t MaxT4Error; + uint64_t T2; + uint32_t MaxT2Error; + uint64_t T3; + uint32_t MaxT3Error; + + WIFI_VENDOR_SPEC_HDR WiFiVSpecHdr; + PTP_SPEC PtpSpec; +} TIMINGMSMT_EVENT_DATA; + +typedef struct _TIMINGMSMT_CONFIRM_EVENT_DATA +{ + uint8_t PeerMACAddress[ETHER_ADDR_OCTETS]; + uint32_t DialogToken; + uint64_t T1; + uint32_t MaxT1Error; + uint64_t T4; + uint32_t MaxT4Error; +} TIMINGMSMT_CONFIRM_EVENT_DATA; + +typedef struct _WIRELESS_CORRELATEDTIME +{ + uint64_t TSC; + uint64_t LocalClk; +} WIRELESS_CORRELATEDTIME; + +struct S8021AS_Indication { + TIMINGMSMT_EVENT_DATA indication; + uint8_t followup[75]; // (34 header + 10 followup + 32 TLV) - 1 byte contained in indication struct = 75 +}; + +union TimeSyncEventData +{ + TIMINGMSMT_CONFIRM_EVENT_DATA confirm; + S8021AS_Indication indication; + WIRELESS_CORRELATEDTIME ptm_wa; +}; + +class WirelessTimestamper : public CommonTimestamper +{ +private: + WirelessPort *port; +public: + virtual ~WirelessTimestamper() {} + + /** + * @brief attach timestamper to port + * @param port port to attach + */ + void setPort( WirelessPort *port ) + { + this->port = port; + } + + /** + * @brief return reference to attached port + * @return reference to attached port + */ + WirelessPort *getPort(void) const + { + return port; + } + + /** + * @brief Return buffer offset where followup message should be placed + * @return byte offset + */ + uint8_t getFwUpOffset(void) + { + // Subtract 1 to compensate for 'bogus' vendor specific buffer + // length + return (uint8_t)((size_t) &((TIMINGMSMT_REQUEST *) 0)-> + PtpSpec.fwup_data ); + } + + /** + * @brief Request transmission of TM frame + * @param dest [in] MAC destination the frame should be sent to + * @param seq [in] 802.1AS sequence number + * @param prev_dialog [in] last dialog message + * @param follow_up [in] buffer containing followup message + * @param followup_length [in] fw-up message length in bytes + */ + virtual net_result requestTimingMeasurement + ( LinkLayerAddress *dest, uint16_t seq, WirelessDialog *prev_dialog, + uint8_t *follow_up, int followup_length ); + + /** + * @brief abstract method for driver/os specific TM transmit code + * @param timingmsmt_req fully formed TM message + */ + virtual net_result _requestTimingMeasurement + ( TIMINGMSMT_REQUEST *timingmsmt_req ) = 0; + + /** + * @brief Asynchronous completion of TM transmit + * @param addr [in] MAC the message was transmitted to + * @param dialog [in] dialog filled with T1, T4 timestamps + */ + void timingMeasurementConfirmCB( LinkLayerAddress addr, + WirelessDialog *dialog ); + + /** + * @brief Reception of TM frame + * @param addr [in] MAC the message was received from + * @param current [in] dialog filled with T2, T3 timestamps + * @param previous [in] dialog filled with T1, T4 timestamps + * @param buf [in] buffer containing followup message + * @param previous [in] length of followup message + */ + void timeMeasurementIndicationCB + ( LinkLayerAddress addr, WirelessDialog *current, + WirelessDialog *previous, uint8_t *buf, size_t buflen ); +}; + +struct WirelessTimestamperCallbackArg +{ + WIRELESS_EVENT_TYPE iEvent_type; + WirelessTimestamper *timestamper; + TimeSyncEventData event_data; +}; + +#endif/*WIRELESS_TSTAMPER_HPP*/ diff --git a/daemons/gptp/linux/src/daemon_cl.cpp b/daemons/gptp/linux/src/daemon_cl.cpp index 86806783..3a6d95e5 100644 --- a/daemons/gptp/linux/src/daemon_cl.cpp +++ b/daemons/gptp/linux/src/daemon_cl.cpp @@ -37,6 +37,7 @@ #include "avbts_oslock.hpp" #include "avbts_persist.hpp" #include "gptp_cfg.hpp" +#include "ether_port.hpp" #ifdef ARCH_INTELCE #include "linux_hal_intelce.hpp" diff --git a/daemons/gptp/linux/src/linux_hal_common.cpp b/daemons/gptp/linux/src/linux_hal_common.cpp index 3be16c86..ab312c95 100644 --- a/daemons/gptp/linux/src/linux_hal_common.cpp +++ b/daemons/gptp/linux/src/linux_hal_common.cpp @@ -428,14 +428,19 @@ void *LinuxTimerQueueHandler( void *arg ) { timeout.tv_sec = 0; timeout.tv_nsec = 100000000; /* 100 ms */ sigemptyset( &waitfor ); - + GPTP_LOG_DEBUG("Signal thread started"); while( !timerq->stop ) { siginfo_t info; LinuxTimerQueueMap_t::iterator iter; sigaddset( &waitfor, SIGUSR1 ); if( sigtimedwait( &waitfor, &info, &timeout ) == -1 ) { - if( errno == EAGAIN ) continue; - else break; + if( errno == EAGAIN ) { + continue; + } + else { + GPTP_LOG_ERROR("Signal thread sigtimedwait error: %d", errno); + break; + } } if( timerq->lock->lock() != oslock_ok ) { break; @@ -456,7 +461,7 @@ void *LinuxTimerQueueHandler( void *arg ) { break; } } - + GPTP_LOG_DEBUG("Signal thread exit"); return NULL; } @@ -474,6 +479,9 @@ OSTimerQueue *LinuxTimerQueueFactory::createOSTimerQueue return NULL; } + ret->key = 0; + ret->stop = false; + ret->lock = clock->timerQLock(); if( pthread_create ( &(ret->_private->signal_thread), NULL, LinuxTimerQueueHandler, ret ) != 0 ) { @@ -481,10 +489,6 @@ OSTimerQueue *LinuxTimerQueueFactory::createOSTimerQueue return NULL; } - ret->stop = false; - ret->key = 0; - ret->lock = clock->timerQLock(); - return ret; } @@ -528,9 +532,7 @@ bool LinuxTimerQueue::addEvent its.it_value.tv_nsec = (micros % 1000000) * 1000; err = timer_settime( outer_arg->timer_handle, 0, &its, NULL ); if( err < 0 ) { - fprintf - ( stderr, "Failed to arm timer: %s\n", - strerror( errno )); + GPTP_LOG_ERROR("Failed to arm timer: %s", strerror(errno)); return false; } } diff --git a/daemons/gptp/windows/daemon_cl/daemon_cl.cpp b/daemons/gptp/windows/daemon_cl/daemon_cl.cpp index 5bb2f3c5..8da3ddfa 100644 --- a/daemons/gptp/windows/daemon_cl/daemon_cl.cpp +++ b/daemons/gptp/windows/daemon_cl/daemon_cl.cpp @@ -44,6 +44,10 @@ POSSIBILITY OF SUCH DAMAGE. #include <tchar.h> #include <iphlpapi.h> +#include <ether_port.hpp> +#include <wireless_port.hpp> +#include <intel_wireless.hpp> + /* Generic PCH delays */ #define PHY_DELAY_GB_TX_PCH 7750 //1G delay #define PHY_DELAY_GB_RX_PCH 7750 //1G delay @@ -125,9 +129,11 @@ int _tmain(int argc, _TCHAR* argv[]) CommonPort::NEIGHBOR_PROP_DELAY_THRESH; bool syntonize = false; + bool wireless = false; uint8_t priority1 = 248; int i; int phy_delays[4] = { -1, -1, -1, -1 }; + uint8_t addr_ostr[ETHER_ADDR_OCTETS]; // Register default network interface WindowsPCAPNetworkInterfaceFactory *default_factory = new WindowsPCAPNetworkInterfaceFactory(); @@ -140,6 +146,8 @@ int _tmain(int argc, _TCHAR* argv[]) portInit.condition_factory = new WindowsConditionFactory(); WindowsNamedPipeIPC *ipc = new WindowsNamedPipeIPC(); WindowsTimerQueueFactory *timerq_factory = new WindowsTimerQueueFactory(); + CommonPort *port; + WindowsWirelessAdapter *wl_adapter = NULL; if( !ipc->init() ) { delete ipc; @@ -155,49 +163,106 @@ int _tmain(int argc, _TCHAR* argv[]) /* Process optional arguments */ for( i = 1; i < argc; ++i ) { - if( ispunct(argv[i][0]) ) { - if( toupper( argv[i][1] ) == 'H' ) { - print_usage( argv[0] ); + if (ispunct(argv[i][0])) + { + if (toupper(argv[i][1]) == 'H') { + print_usage(argv[0]); return -1; } - else if( toupper( argv[i][1] ) == 'R' ) { - if( i+1 >= argc ) { - printf( "Priority 1 value must be specified on " - "command line, using default value\n" ); - } else { - unsigned long tmp = strtoul( argv[i+1], NULL, 0 ); ++i; - if( tmp > 254 ) { - printf( "Invalid priority 1 value, using " - "default value\n" ); - } else { - priority1 = (uint8_t) tmp; + if (toupper(argv[i][1]) == 'W') + { + wireless = true; + } + else if (toupper(argv[i][1]) == 'R') { + if (i + 1 >= argc) { + printf("Priority 1 value must be specified on " + "command line, using default value\n"); + } + else { + unsigned long tmp = strtoul(argv[i + 1], NULL, 0); ++i; + if (tmp > 255) { + printf("Invalid priority 1 value, using " + "default value\n"); + } + else { + priority1 = (uint8_t)tmp; } } } + } else + { + break; } } - // the last argument is supposed to be a MAC address, so decrement argv index to read it - i--; + // Parse local HW MAC address + if (i < argc) + { + parseMacAddr(argv[i++], addr_ostr); + portInit.net_label = new LinkLayerAddress(addr_ostr); + } else + { + printf("Local hardware MAC address required"); + return -1; + } + + if( wireless ) + { + if (i < argc) + { + parseMacAddr(argv[i++], addr_ostr); + portInit.virtual_label = new LinkLayerAddress(addr_ostr); + } else + { + printf("Wireless operation requires local virtual MAC address"); + return -1; + } + } + + if (!wireless) + { + // Create HWTimestamper object + portInit.timestamper = new WindowsEtherTimestamper(); + } else + { + portInit.timestamper = new WindowsWirelessTimestamper(); + (static_cast<WindowsWirelessTimestamper *> (portInit.timestamper))->setAdapter(new IntelWirelessAdapter()); + } - // Create Low level network interface object - uint8_t local_addr_ostr[ETHER_ADDR_OCTETS]; - parseMacAddr( argv[i], local_addr_ostr ); - LinkLayerAddress local_addr(local_addr_ostr); - portInit.net_label = &local_addr; - // Create HWTimestamper object - portInit.timestamper = new WindowsTimestamper(); // Create Clock object - portInit.clock = new IEEE1588Clock( false, false, priority1, timerq_factory, ipc, portInit.lock_factory ); // Do not force slave - // Create Port Object linked to clock and low level - portInit.phy_delay = ðer_phy_delay; - EtherPort *port = new EtherPort( &portInit ); - port->setLinkSpeed(findLinkSpeed(&local_addr)); - if ( !port->init_port() ) { - printf( "Failed to initialize port\n" ); - return -1; + portInit.clock = new IEEE1588Clock(false, false, priority1, timerq_factory, ipc, portInit.lock_factory); // Do not force slave + + if (!wireless) + { + // Create Port Object linked to clock and low level + portInit.phy_delay = ðer_phy_delay; + EtherPort *eport = new EtherPort(&portInit); + eport->setLinkSpeed( findLinkSpeed( static_cast <LinkLayerAddress *> ( portInit.net_label ))); + port = eport; + if (!eport->init_port()) { + printf("Failed to initialize port\n"); + return -1; + } + port->processEvent(POWERUP); + } else + { + if (i < argc) + { + parseMacAddr(argv[i++], addr_ostr); + LinkLayerAddress peer_addr(addr_ostr); + port = new WirelessPort(&portInit, peer_addr); + (static_cast <WirelessTimestamper *> (portInit.timestamper))->setPort( static_cast<WirelessPort *> ( port )); + if (!port->init_port()) { + printf("Failed to initialize port\n"); + return -1; + } + port->processEvent(POWERUP); + } else + { + printf("Wireless operation requires remote MAC address"); + return -1; + } } - port->processEvent( POWERUP ); // Wait for Ctrl-C if( !SetConsoleCtrlHandler( ctrl_handler, true )) { diff --git a/daemons/gptp/windows/daemon_cl/intel_wireless.cpp b/daemons/gptp/windows/daemon_cl/intel_wireless.cpp new file mode 100644 index 00000000..a6f8f99f --- /dev/null +++ b/daemons/gptp/windows/daemon_cl/intel_wireless.cpp @@ -0,0 +1,424 @@ +/****************************************************************************** + +Copyright (c) 2009-2015, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the Intel Corporation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include <intel_wireless.hpp> +#include <windows_hal.hpp> +#include <work_queue.hpp> +#include <memory> +#include <map> + +#define INTEL_EVENT_OFFSET 0x1141 +#define GP2_ROLLOVER 4294967296ULL + +GetAdapterList_t GetAdapterList; +WifiCmdTimingMeasurementRequest_t WifiCmdTimingMeasurementRequest; +WifiCmdTimingPtmWa_t WifiCmdTimingPtmWa; +RegisterIntelCallback_t RegisterIntelCallback; +DeregisterIntelCallback_t DeregisterIntelCallback; + +struct TimestamperContext +{ + WindowsWorkQueue work_queue; + WindowsWirelessTimestamper *timestamper; + ~TimestamperContext() + { + work_queue.stop(); + } +}; + +typedef std::map< LinkLayerAddress, TimestamperContext > TimestamperContextMap; + +class LockedTimestamperContextMap +{ +public: + LockedTimestamperContextMap() + { + InitializeSRWLock(&lock); + } + TimestamperContextMap map; + SRWLOCK lock; +}; + +LockedTimestamperContextMap timestamperContextMap; +HANDLE IntelTimestamperThread; +bool IntelTimestamperThreadStop = false; + +void IntelWirelessTimestamperEventHandler( INTEL_EVENT iEvent, void *pContext ) +{ + WindowsWirelessTimestamper *timestamper; + IntelWirelessAdapter *adapter; + LockedTimestamperContextMap *timestamperContextMap = + (LockedTimestamperContextMap *)pContext; + WirelessTimestamperCallbackArg *arg = + new WirelessTimestamperCallbackArg(); + int vendor_extension_copy_length; + bool error = false; + bool submit = false; // If true submit the event for later processing + + // We share driver callback with other events, subtract the offset for + // timing measurement related events and check this is time sync event + iEvent.eType = + (WIRELESS_EVENT_TYPE) (iEvent.eType - INTEL_EVENT_OFFSET); + switch( iEvent.eType ) + { + default: + return; + case TIMINGMSMT_CONFIRM_EVENT: + case TIMINGMSMT_EVENT: + case TIMINGMSMT_CORRELATEDTIME_EVENT: + break; + } + + LinkLayerAddress event_source( iEvent.BtMacAddress ); + arg->iEvent_type = (WIRELESS_EVENT_TYPE) iEvent.eType; + AcquireSRWLockShared( ×tamperContextMap->lock ); + if( timestamperContextMap->map.find( event_source ) + != timestamperContextMap->map.cend()) + { + arg->timestamper = + timestamperContextMap->map[event_source].timestamper; + } else + { + goto bail; + } + + timestamper = dynamic_cast <WindowsWirelessTimestamper *> + (arg->timestamper); + if( timestamper == NULL ) + { + GPTP_LOG_ERROR( "Unexpected timestamper type encountered" ); + goto bail; + } + adapter = dynamic_cast <IntelWirelessAdapter *> + (timestamper->getAdapter( )); + if( adapter == NULL ) + { + GPTP_LOG_ERROR( "Unexpected adapter type encountered" ); + goto bail; + } + + // The driver implementation makes no guarantee of the lifetime of the + // iEvent object we make a copy and delete the copy when processing is + // complete + switch( arg->iEvent_type ) + { + case TIMINGMSMT_CONFIRM_EVENT: + arg->event_data.confirm = + *(TIMINGMSMT_CONFIRM_EVENT_DATA *)iEvent.data; + arg->event_data.confirm.T1 = + adapter->calc_rollover( arg->event_data.confirm.T1 ); + arg->event_data.confirm.T4 = + adapter->calc_rollover( arg->event_data.confirm.T4 ); + submit = true; + + break; + + case TIMINGMSMT_EVENT: + arg->event_data.indication.indication = + *(TIMINGMSMT_EVENT_DATA *)iEvent.data; + arg->event_data.indication.indication.T2 = + adapter->calc_rollover + ( arg->event_data.indication.indication.T2/100 ); + arg->event_data.indication.indication.T3 = + adapter->calc_rollover + ( arg->event_data.indication.indication.T3/100 ); + // Calculate copy length, at most followup message + // (See S8021AS indication) + vendor_extension_copy_length = arg->event_data. + indication.indication.WiFiVSpecHdr.Length - 1; + vendor_extension_copy_length -= vendor_extension_copy_length - + sizeof(arg->event_data.indication.followup) > 0 ? + vendor_extension_copy_length - + sizeof(arg->event_data.indication.followup) : 0; + // Copy the rest of the vendor specific field + memcpy( arg->event_data.indication.followup, + ((BYTE *)iEvent.data) + + sizeof( arg->event_data.indication.indication ), + vendor_extension_copy_length ); + submit = true; + break; + case TIMINGMSMT_CORRELATEDTIME_EVENT: + AcquireSRWLockExclusive(&adapter->ct_lock); + if (adapter->ct_miss > 0) + { + --adapter->ct_miss; + } else + { + arg->event_data.ptm_wa = + *(WIRELESS_CORRELATEDTIME *)iEvent.data; + arg->event_data.ptm_wa.LocalClk = + adapter->calc_rollover + (arg->event_data.ptm_wa.LocalClk); + adapter->ct_done = true; + // No need to schedule this, it returns immediately + WirelessTimestamperCallback(arg); + WakeAllConditionVariable(&adapter->ct_cond); + } + ReleaseSRWLockExclusive(&adapter->ct_lock); + + break; + } + + if (submit && + !timestamperContextMap->map[event_source].work_queue.submit + ( WirelessTimestamperCallback, arg )) + GPTP_LOG_ERROR("Failed to submit WiFi event"); +bail: + ReleaseSRWLockShared(×tamperContextMap->lock); +} + +DWORD WINAPI IntelWirelessLoop(LPVOID arg) +{ + // Register for callback + INTEL_CALLBACK tempCallback; + tempCallback.fnIntelCallback = IntelWirelessTimestamperEventHandler; + tempCallback.pContext = arg; + + if (RegisterIntelCallback(&tempCallback) != S_OK) { + GPTP_LOG_ERROR( "Failed to register WiFi callback" ); + return 0; + } + + while (!IntelTimestamperThreadStop) + { + SleepEx(320, true); + } + + DeregisterIntelCallback(tempCallback.fnIntelCallback); + + return 0; +} + +bool IntelWirelessAdapter::initialize(void) +{ + HMODULE MurocApiDLL = LoadLibrary("MurocApi.dll"); + + GetAdapterList = (GetAdapterList_t) + GetProcAddress(MurocApiDLL, "GetAdapterList"); + if( GetAdapterList == NULL ) + return false; + + RegisterIntelCallback = (RegisterIntelCallback_t) + GetProcAddress(MurocApiDLL, "RegisterIntelCallback"); + if( RegisterIntelCallback == NULL ) + return false; + + DeregisterIntelCallback = (DeregisterIntelCallback_t) + GetProcAddress(MurocApiDLL, "DeregisterIntelCallback"); + if( DeregisterIntelCallback == NULL ) + return false; + + WifiCmdTimingPtmWa = (WifiCmdTimingPtmWa_t) + GetProcAddress(MurocApiDLL, "WifiCmdTimingPtmWa"); + if( WifiCmdTimingPtmWa == NULL ) + return false; + + WifiCmdTimingMeasurementRequest = (WifiCmdTimingMeasurementRequest_t) + GetProcAddress(MurocApiDLL, "WifiCmdTimingMeasurementRequest"); + if (WifiCmdTimingMeasurementRequest == NULL) + return false; + + // Initialize crosstimestamp condition + InitializeConditionVariable(&ct_cond); + InitializeSRWLock(&ct_lock); + ct_miss = 0; + + // Spawn thread + IntelTimestamperThread = CreateThread + ( NULL, 0, IntelWirelessLoop, ×tamperContextMap, 0, NULL); + return true; +} + +void IntelWirelessAdapter::shutdown(void) { + // Signal thread exit + IntelTimestamperThreadStop = true; + // Join thread + WaitForSingleObject(IntelTimestamperThread, INFINITE); +} + +bool IntelWirelessAdapter::refreshCrossTimestamp(void) { + INTEL_WIFI_HEADER header; + WIRELESS_CORRELATEDTIME ptm_wa; + DWORD wait_return = TRUE; + DWORD start = 0; + + AcquireSRWLockExclusive(&ct_lock); + ct_done = false; + header.dwSize = sizeof(ptm_wa); + header.dwStructVersion = INTEL_STRUCT_VER_LATEST; + HRESULT hres = WifiCmdTimingPtmWa(hAdapter, &header, &ptm_wa); + if (hres != S_OK) + return false; + + while (!ct_done && wait_return == TRUE) { + DWORD now = GetTickCount(); + start = start == 0 ? now : start; + DWORD wait = now - start < CORRELATEDTIME_TRANSACTION_TIMEOUT ? + CORRELATEDTIME_TRANSACTION_TIMEOUT - + (now - start) : 0; + wait_return = SleepConditionVariableSRW + ( &ct_cond, &ct_lock, wait, 0 ); + } + ct_miss += wait_return != TRUE ? 1 : 0; + ReleaseSRWLockExclusive(&ct_lock); + + return wait_return == TRUE ? true : false; +} + +bool IntelWirelessAdapter::initiateTimingRequest +( TIMINGMSMT_REQUEST *tm_request ) +{ + INTEL_WIFI_HEADER header; + HRESULT err; + + memset(&header, 0, sizeof(header)); + // Proset wants an equivalent, but slightly different struct definition + header.dwSize = sizeof(*tm_request) - sizeof(PTP_SPEC) + 1; + header.dwStructVersion = INTEL_STRUCT_VER_LATEST; + if(( err = WifiCmdTimingMeasurementRequest + (hAdapter, &header, tm_request)) != S_OK ) + return false; + + return true; +} + +// Find Intel adapter given MAC address and return adapter ID +// +// @adapter(out): Adapter identifier used for all driver interaction +// @mac_addr: HW address of the adapter +// @return: *false* if adapter is not found or driver communication failure +// occurs, otherwise *true* +// +bool IntelWirelessAdapter::attachAdapter(uint8_t *mac_addr) +{ + PINTEL_ADAPTER_LIST adapter_list; + int i; + + if (GetAdapterList(&adapter_list) != S_OK) + return false; + + GPTP_LOG_VERBOSE("Driver query returned %d adapters\n", + adapter_list->count); + for (i = 0; i < adapter_list->count; ++i) { + if( memcmp(adapter_list->adapter[i].btMACAddress, + mac_addr, ETHER_ADDR_OCTETS) == 0 ) + break; + } + + if (i == adapter_list->count) + { + GPTP_LOG_ERROR("Driver failed to find the requested adapter"); + return false; + } + hAdapter = adapter_list->adapter[i].hAdapter; + + return true; +} + +// Register timestamper with Intel wireless subsystem. +// +// @timestamper: timestamper object that should receive events +// @source: MAC address of local interface +// @return: *false* if the MAC address has already been registered, *true* if +// +bool IntelWirelessAdapter::registerTimestamper +(WindowsWirelessTimestamper *timestamper) +{ + bool ret = false; + + AcquireSRWLockExclusive(×tamperContextMap.lock); + // Do not "re-add" the same timestamper + if( timestamperContextMap.map.find + ( *timestamper->getPort()->getLocalAddr() ) + == timestamperContextMap.map.cend()) + { + // Initialize per timestamper context and add + timestamperContextMap.map + [*timestamper->getPort()->getLocalAddr()]. + work_queue.init(0); + timestamperContextMap.map + [*timestamper->getPort()->getLocalAddr()]. + timestamper = timestamper; + ret = true; + } + ReleaseSRWLockExclusive(×tamperContextMap.lock); + + return ret; +} + +bool IntelWirelessAdapter::deregisterTimestamper +( WindowsWirelessTimestamper *timestamper ) +{ + bool ret; + + TimestamperContextMap::iterator iter; + AcquireSRWLockExclusive(×tamperContextMap.lock); + // Find timestamper + iter = timestamperContextMap.map.find + ( *timestamper->getPort()->getLocalAddr( )); + if( iter != timestamperContextMap.map.end( )) + { + // Shutdown work queue + iter->second.work_queue.stop(); + // Remove timestamper from list + timestamperContextMap.map.erase(iter); + ret = true; + } + ReleaseSRWLockExclusive(×tamperContextMap.lock); + + return ret; +} + +uint64_t IntelWirelessAdapter::calc_rollover( uint64_t gp2 ) +{ + gp2 = gp2 & 0xFFFFFFFF; + uint64_t l_gp2_ext; + + if (gp2_last == ULLONG_MAX) + { + gp2_last = gp2; + + return gp2; + } + + if (gp2 < GP2_ROLLOVER/4 && gp2_last > (GP2_ROLLOVER*3)/4) + ++gp2_ext; + + l_gp2_ext = gp2_ext; + if( gp2_last < GP2_ROLLOVER / 4 && gp2 >(GP2_ROLLOVER * 3) / 4 ) + --l_gp2_ext; + else + gp2_last = gp2; + + return gp2 + ( l_gp2_ext * GP2_ROLLOVER ); +} diff --git a/daemons/gptp/windows/daemon_cl/intel_wireless.hpp b/daemons/gptp/windows/daemon_cl/intel_wireless.hpp new file mode 100644 index 00000000..bad97d71 --- /dev/null +++ b/daemons/gptp/windows/daemon_cl/intel_wireless.hpp @@ -0,0 +1,187 @@ +/****************************************************************************** + +Copyright (c) 2009-2015, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the Intel Corporation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#ifndef INTEL_WIRELESS_HPP +#define INTEL_WIRELESS_HPP + +#include <windows_hal.hpp> +#include <Windows.h> +#include <stdint.h> + +#define INTEL_MAC_ADDR_LENGTH 6 +#define INTEL_MAX_ADAPTERS 20 +#define CORRELATEDTIME_TRANSACTION_TIMEOUT 110/*ms*/ + +enum _WIRELESS_EVENT_TYPE; + +typedef struct INTEL_EVENT +{ + BYTE BtMacAddress[INTEL_MAC_ADDR_LENGTH + 1]; + ///< Source of event (MAC address) + _WIRELESS_EVENT_TYPE eType; ///< Event type + HRESULT hErrCode; ///< Error code + DWORD dwSize; + BYTE* data; +} INTEL_EVENT, *PINTEL_EVENT; + +typedef void(*INTEL_EVENT_CALLBACK) ( INTEL_EVENT iEvent, void *pContext ); + +typedef struct INTEL_CALLBACK +{ + INTEL_EVENT_CALLBACK fnIntelCallback; + void *pContext; + +} INTEL_CALLBACK, *PINTEL_CALLBACK; + +/// Adapter handle +typedef DWORD HADAPTER; + +//intel Header structure +typedef struct _INTEL_WIFI_HEADER +{ + DWORD dwStructVersion; + //Structure version for which this header is created + DWORD dwSize; + //size of the structure for which this header is created + DWORD dwFlags; //For future use + DWORD dwReserved; //For future use +} INTEL_WIFI_HEADER, *PINTEL_WIFI_HEADER; + +typedef enum INTEL_ADAPTER_TYPE +{ + eStone, // Stone Peak - without BT Support + eStoneBT, // Stone Peak - with BT Support + eSnowfield // Snowfield Peak +} INTEL_ADAPTER_TYPE; + +#define INTEL_ADAPTER_NAME_SIZE 64 +#define INTEL_ADAPTER_DESCRIPTION_SIZE 64 +#define INTEL_ADAPTER_CLSGUID_SIZE 64 +#define INTEL_REGPATH_SIZE 512 + +#define INTEL_STRUCT_VER_LATEST 156 + +typedef enum INTEL_ADAPTER_PROTOCOL +{ + eUnknownProtocol, ///< Unknown adapter. + e802_11, ///< WiFi +} INTEL_ADAPTER_PROTOCOL; + +typedef struct INTEL_ADAPTER_LIST_ENTRY +{ + HADAPTER hAdapter; + ///< Adapter handle + UCHAR btMACAddress[INTEL_MAC_ADDR_LENGTH]; + ///< Adapter MAC address + CHAR szAdapterName[INTEL_ADAPTER_NAME_SIZE]; + ///< Adapter OS CLSGUID + CHAR szAdapterDescription[INTEL_ADAPTER_DESCRIPTION_SIZE]; + ///< Display name + CHAR szAdapterClsguid[INTEL_ADAPTER_CLSGUID_SIZE]; + ///< Muroc CLSGUID + CHAR szRegistryPath[INTEL_REGPATH_SIZE]; + ///< Adapter registry root + CHAR szPnPId[INTEL_REGPATH_SIZE]; ///< Plug-and-Play ID + BOOL bEnabled; ///< enabled in driver + INTEL_ADAPTER_TYPE eAdapterType; ///< Adapter type + INTEL_ADAPTER_PROTOCOL eAdapterProtocol; ///< Adapter type +} INTEL_ADAPTER_LIST_ENTRY, *PINTEL_ADAPTER_LIST_ENTRY; + +typedef struct INTEL_ADAPTER_LIST +{ + int count; ///< Number of entries + INTEL_ADAPTER_LIST_ENTRY adapter[INTEL_MAX_ADAPTERS]; + ///< Array of adapter entries +} INTEL_ADAPTER_LIST, *PINTEL_ADAPTER_LIST; + + +typedef HRESULT ( APIENTRY *GetAdapterList_t ) + ( PINTEL_ADAPTER_LIST* ppAdapterList ); +typedef HRESULT ( APIENTRY *WifiCmdTimingMeasurementRequest_t ) + ( HADAPTER, PINTEL_WIFI_HEADER, void * ); +typedef HRESULT ( APIENTRY *WifiCmdTimingPtmWa_t ) + (HADAPTER, PINTEL_WIFI_HEADER, void *); +typedef HRESULT ( APIENTRY *RegisterIntelCallback_t ) ( PINTEL_CALLBACK ); +typedef HRESULT ( APIENTRY *DeregisterIntelCallback_t ) + ( INTEL_EVENT_CALLBACK ); + +extern GetAdapterList_t GetAdapterList; +extern WifiCmdTimingMeasurementRequest_t WifiCmdTimingMeasurementRequest; +extern WifiCmdTimingPtmWa_t WifiCmdTimingPtmWa; +extern RegisterIntelCallback_t RegisterIntelCallback; +extern DeregisterIntelCallback_t DeregisterIntelCallback; + +typedef struct _TIMINGMSMT_REQUEST *tm_request_t; +typedef struct WirelessTimestamperCallbackArg +*WirelessTimestamperCallbackArg_t; +typedef void( *WirelessTimestamperCallback_t ) +( WirelessTimestamperCallbackArg_t arg ); + +class IntelWirelessAdapter : public WindowsWirelessAdapter +{ +private: + HADAPTER hAdapter; + + // Get correlated time operation may timeout + // Use condition wait to manage this + SRWLOCK ct_lock; + CONDITION_VARIABLE ct_cond; + bool ct_done; + int ct_miss; + + uint64_t gp2_last; + unsigned gp2_ext; + +public: + bool initialize( void ); + void shutdown( void ); + bool refreshCrossTimestamp(); + bool initiateTimingRequest( TIMINGMSMT_REQUEST *tm_request ); + bool attachAdapter( uint8_t *mac_addr ); + bool registerTimestamper( WindowsWirelessTimestamper *timestamper ); + bool deregisterTimestamper( WindowsWirelessTimestamper *timestamper ); + IntelWirelessAdapter(); + + uint64_t calc_rollover( uint64_t gp2 ); + + friend void IntelWirelessTimestamperEventHandler + ( INTEL_EVENT iEvent, void *pContext ); +}; + +inline IntelWirelessAdapter::IntelWirelessAdapter() +{ + gp2_ext = 0; + gp2_last = ULLONG_MAX; +} + +#endif diff --git a/daemons/gptp/windows/daemon_cl/tsc.hpp b/daemons/gptp/windows/daemon_cl/tsc.hpp index ed22752c..425205d3 100644 --- a/daemons/gptp/windows/daemon_cl/tsc.hpp +++ b/daemons/gptp/windows/daemon_cl/tsc.hpp @@ -95,13 +95,14 @@ inline uint32_t FindFrequencyByModel(uint8_t model_query) { /** * @brief Gets the TSC frequnecy - * @param millis time in miliseconds + * @param builtin whether device is connected to the Intel bus (not PCIE) * @return TSC frequency, or 0 on error */ -inline uint64_t getTSCFrequency( unsigned millis ) { +inline uint64_t getTSCFrequency( bool builtin ) { int max_cpuid_level; int tmp[4]; BOOL is_windows_10; + LARGE_INTEGER freq; // Find the max cpuid level, and if possible find clock info __cpuid(tmp, 0); @@ -124,9 +125,11 @@ inline uint64_t getTSCFrequency( unsigned millis ) { // clock will be returned, *else* use QPC for everything else // // EAX (tmp[0]) must be >= 1, See Intel SDM 17.15.4 "Invariant Time-keeping" - if (is_windows_10 && + if (!is_windows_10 && max_cpuid_level >= CLOCK_INFO_CPUID_LEAF && - tmp[0] >= 1) { + tmp[0] >= 1 && + builtin ) + { SYSTEM_INFO info; GetSystemInfo(&info); @@ -135,8 +138,6 @@ inline uint64_t getTSCFrequency( unsigned millis ) { return FindFrequencyByModel(info.wProcessorRevision >> 8); } - LARGE_INTEGER freq; - if (QueryPerformanceFrequency(&freq)) return freq.QuadPart; diff --git a/daemons/gptp/windows/daemon_cl/windows_hal.cpp b/daemons/gptp/windows/daemon_cl/windows_hal.cpp index a05d473c..3d034e39 100644 --- a/daemons/gptp/windows/daemon_cl/windows_hal.cpp +++ b/daemons/gptp/windows/daemon_cl/windows_hal.cpp @@ -67,8 +67,184 @@ VOID CALLBACK WindowsTimerQueueHandler( PVOID arg_in, BOOLEAN ignore ) { ReleaseSRWLockExclusive( &arg->queue->retiredTimersLock ); } +inline uint64_t scale64(uint64_t i, uint32_t m, uint32_t n) +{ + uint64_t tmp, res, rem; + + rem = i % n; + i /= n; + + res = i * m; + tmp = rem * m; + + tmp /= n; + + return res + tmp; +} + +void WirelessTimestamperCallback( LPVOID arg ) +{ + WirelessTimestamperCallbackArg *larg = + (WirelessTimestamperCallbackArg *)arg; + WindowsWirelessTimestamper *timestamper = + dynamic_cast<WindowsWirelessTimestamper *> (larg->timestamper); + WirelessDialog tmp1, tmp2; + LinkLayerAddress *peer_addr = NULL; + + if (timestamper == NULL) + { + GPTP_LOG_ERROR( "Wrong timestamper type: %p", + larg->timestamper ); + return; + } + + switch( larg->iEvent_type ) + { + default: + case TIMINGMSMT_CONFIRM_EVENT: + tmp1.action_devclk = larg->event_data.confirm.T1; + tmp1.ack_devclk = larg->event_data.confirm.T4; + tmp1.dialog_token = (BYTE)larg->event_data.confirm.DialogToken; + GPTP_LOG_VERBOSE + ( "Got confirm, %hhu(%llu,%llu)", tmp1.dialog_token, + tmp1.action_devclk, tmp1.ack_devclk ); + peer_addr = new LinkLayerAddress + ( larg->event_data.confirm.PeerMACAddress ); + larg->timestamper-> + timingMeasurementConfirmCB( *peer_addr, &tmp1 ); + + break; + + case TIMINGMSMT_EVENT: + tmp1/*prev*/.action_devclk = larg->event_data.indication. + indication.T1; + tmp1/*prev*/.ack_devclk = larg->event_data.indication. + indication.T4; + tmp1/*prev*/.dialog_token = (BYTE)larg->event_data.indication. + indication.FollowUpDialogToken; + tmp2/*curr*/.action_devclk = larg->event_data.indication. + indication.T2; + tmp2/*curr*/.ack_devclk = larg->event_data.indication. + indication.T3; + tmp2/*curr*/.dialog_token = (BYTE)larg->event_data.indication. + indication.DialogToken; + GPTP_LOG_VERBOSE + ("Got indication, %hhu(%llu,%llu) %hhu(%llu,%llu)", + tmp1.dialog_token, tmp1.action_devclk, + tmp1.ack_devclk, tmp2.dialog_token, + tmp2.action_devclk, tmp2.ack_devclk); + peer_addr = new LinkLayerAddress(larg->event_data.indication. + indication.PeerMACAddress); + + larg->timestamper->timeMeasurementIndicationCB + ( *peer_addr, &tmp2, &tmp1, + larg->event_data.indication. + indication.PtpSpec.fwup_data, + larg->event_data.indication. + indication.WiFiVSpecHdr.Length - (sizeof(PTP_SPEC) - + 1)); + + break; + + case TIMINGMSMT_CORRELATEDTIME_EVENT: + timestamper->system_counter = + scale64( larg->event_data.ptm_wa.TSC, NS_PER_SECOND, + (uint32_t)timestamper->tsc_hz.QuadPart ); + timestamper->system_time.set64(timestamper->system_counter); + // Scale from TM timescale to nanoseconds + larg->event_data.ptm_wa.LocalClk *= 10; + timestamper->device_time.set64 + (larg->event_data.ptm_wa.LocalClk*10); + + break; + } + + delete peer_addr; +} + +net_result WindowsWirelessTimestamper::_requestTimingMeasurement +(TIMINGMSMT_REQUEST *timingmsmt_req) +{ + net_result ret = net_succeed; + + if (!adapter->initiateTimingRequest(timingmsmt_req)) { + GPTP_LOG_ERROR("Failed to send timing measurement request\n"); + ret = net_fatal; + } + + return ret; +} + +bool WindowsWirelessTimestamper::HWTimestamper_gettime +( Timestamp *system_time, + Timestamp * device_time, + uint32_t * local_clock, + uint32_t * nominal_clock_rate ) const +{ + bool refreshed = adapter->refreshCrossTimestamp(); + if (refreshed) + { + // We have a fresh cross-timestamp just use it + *system_time = this->system_time; + *device_time = this->device_time; + } else + { + // We weren't able to get a fresh timestamp, + // extrapolate from the last + LARGE_INTEGER tsc_now; + QueryPerformanceCounter(&tsc_now); + unsigned device_delta = (unsigned) + (((long double) (tsc_now.QuadPart - system_counter)) / + (((long double)tsc_hz.QuadPart) / 1000000000)); + device_delta = (unsigned)(device_delta*getPort()-> + getLocalSystemFreqOffset()); + system_time->set64((uint64_t) + (((long double)tsc_now.QuadPart) / + ((long double)tsc_hz.QuadPart / + 1000000000))); + device_time->set64(device_delta); + *device_time = *device_time + this->device_time; + } + + return true; +} + +bool WindowsWirelessTimestamper::HWTimestamper_init +(InterfaceLabel *iface_label, OSNetworkInterface *iface) +{ + uint8_t mac_addr_local[ETHER_ADDR_OCTETS]; + + if (!initialized) { + if (!adapter->initialize()) return false; + if (getPort()->getLocalAddr() == NULL) + return false; + + getPort()->getLocalAddr()->toOctetArray(mac_addr_local); + if (!adapter->attachAdapter(mac_addr_local)) { + return false; + } + + tsc_hz.QuadPart = getTSCFrequency(false); + if (tsc_hz.QuadPart == 0) { + return false; + } + + if (!adapter->registerTimestamper(this)) + return false; + } + + initialized = true; + return true; +} + +WindowsWirelessTimestamper::~WindowsWirelessTimestamper() { + if (adapter->deregisterTimestamper(this)) + adapter->shutdown(); + else + GPTP_LOG_INFO("Failed to shutdown time sync on adapter"); +} -bool WindowsTimestamper::HWTimestamper_init( InterfaceLabel *iface_label, OSNetworkInterface *net_iface ) { +bool WindowsEtherTimestamper::HWTimestamper_init( InterfaceLabel *iface_label, OSNetworkInterface *net_iface ) { char network_card_id[64]; LinkLayerAddress *addr = dynamic_cast<LinkLayerAddress *>(iface_label); if( addr == NULL ) return false; @@ -112,7 +288,7 @@ bool WindowsTimestamper::HWTimestamper_init( InterfaceLabel *iface_label, OSNetw NULL, OPEN_EXISTING, 0, NULL ); if( miniport == INVALID_HANDLE_VALUE ) return false; - tsc_hz.QuadPart = getTSCFrequency( 1000 ); + tsc_hz.QuadPart = getTSCFrequency( true ); if( tsc_hz.QuadPart == 0 ) { return false; } diff --git a/daemons/gptp/windows/daemon_cl/windows_hal.hpp b/daemons/gptp/windows/daemon_cl/windows_hal.hpp index f5192910..5f1a6f61 100644 --- a/daemons/gptp/windows/daemon_cl/windows_hal.hpp +++ b/daemons/gptp/windows/daemon_cl/windows_hal.hpp @@ -36,7 +36,6 @@ /**@file*/ -#include <minwindef.h> #include <IPCListener.hpp> #include "avbts_osnet.hpp" #include "avbts_oslock.hpp" @@ -47,6 +46,7 @@ #include "packet.hpp" #include "ieee1588.hpp" #include "ether_tstamper.hpp" +#include "wireless_tstamper.hpp" #include "iphlpapi.h" #include "windows_ipc.hpp" #include "tsc.hpp" @@ -590,6 +590,114 @@ public: } }; +void WirelessTimestamperCallback(LPVOID arg); + +class WindowsWirelessAdapter; + +/** +* @brief Windows Wireless (802.11) HWTimestamper implementation +*/ +class WindowsWirelessTimestamper : public WirelessTimestamper +{ +private: + WindowsWirelessAdapter *adapter; + + uint64_t system_counter; + Timestamp system_time; + Timestamp device_time; + LARGE_INTEGER tsc_hz; + bool initialized; + +public: + WindowsWirelessTimestamper() + { + initialized = false; + } + + net_result _requestTimingMeasurement + ( TIMINGMSMT_REQUEST *timingmsmt_req ); + + bool HWTimestamper_gettime + ( Timestamp *system_time, Timestamp * device_time, + uint32_t * local_clock, uint32_t * nominal_clock_rate ) const; + + virtual bool HWTimestamper_init + ( InterfaceLabel *iface_label, OSNetworkInterface *iface ); + + /** + * @brief attach adapter to timestamper + * @param adapter [in] adapter to attach + */ + void setAdapter( WindowsWirelessAdapter *adapter ) + { + this->adapter = adapter; + } + + /** + * @brief get attached adapter + * @return attached adapter + */ + WindowsWirelessAdapter *getAdapter(void) + { + return adapter; + } + + ~WindowsWirelessTimestamper(); + + friend void WirelessTimestamperCallback( LPVOID arg ); +}; + +class WindowsWirelessAdapter +{ +public: + /** + * @brief initiate wireless TM request (completion is asynchronous) + * @param tm_request [in] pointer to TM request object + * @return true on success + */ + virtual bool initiateTimingRequest(TIMINGMSMT_REQUEST *tm_request) = 0; + + /** + * @brief attempt to refresh cross timestamp (extrapolate on failure) + * @return true on success + */ + virtual bool refreshCrossTimestamp() = 0; + + /** + * @brief register timestamper with adapter + * @param timestamper [in] timestamper object + * @return true on success + */ + virtual bool registerTimestamper + ( WindowsWirelessTimestamper *timestamper ) = 0; + + /** + * @brief deregister timestamper + * @param timestamper [in] timestamper object + * @return true on success + */ + virtual bool deregisterTimestamper + ( WindowsWirelessTimestamper *timestamper ) = 0; + + /** + * @brief initialize adapter object + * @return true on success + */ + virtual bool initialize() = 0; + + /** + * @brief shutdown adapter + */ + virtual void shutdown() = 0; + + /** + * @brief attach adapter to MAC address + * @param mac_addr [in] MAC address to attach to + * @return true on success + */ + virtual bool attachAdapter( uint8_t *mac_addr ) = 0; +}; + #define I217_DESC "I217-LM" #define I219_DESC "I219-V" @@ -616,9 +724,9 @@ static DeviceClockRateMapping DeviceClockRateMap[] = }; /** - * @brief Windows HWTimestamper implementation + * @brief Windows Ethernet HWTimestamper implementation */ -class WindowsTimestamper : public EtherTimestamper { +class WindowsEtherTimestamper : public EtherTimestamper { private: // No idea whether the underlying implementation is thread safe HANDLE miniport; diff --git a/daemons/gptp/windows/daemon_cl/work_queue.cpp b/daemons/gptp/windows/daemon_cl/work_queue.cpp new file mode 100644 index 00000000..461d6923 --- /dev/null +++ b/daemons/gptp/windows/daemon_cl/work_queue.cpp @@ -0,0 +1,106 @@ +/****************************************************************************** + +Copyright (c) 2009-2015, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the Intel Corporation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include <work_queue.hpp> +#include <stdio.h> + + +struct WWQueueThreadState { + bool running; + bool stop; + WWQueueCallback task; + LPVOID arg; +}; + +DWORD WINAPI WindowsWorkQueueLoop(LPVOID arg) { + WWQueueThreadState *state = (WWQueueThreadState *)arg; + state->running = true; + while (!state->stop) { + if (state->task != NULL) { + state->task(state->arg); + delete state->arg; + state->task = NULL; + } + Sleep(1); + } + state->running = false; + + return 0; +} + +bool WindowsWorkQueue::init(int number_threads) +{ + if (number_threads == 0) number_threads = DEFAULT_THREAD_COUNT; + state = new WWQueueThreadState[number_threads]; + for (int i = 0; i < number_threads; ++i) { + state[i].running = false; + state[i].stop = false; + state[i].task = NULL; + } + workers = new HANDLE[number_threads]; + for (int i = 0; i < number_threads; ++i) { + workers[i] = CreateThread(NULL, 0, WindowsWorkQueueLoop, state + i, 0, NULL); + if (workers[i] == INVALID_HANDLE_VALUE) + return false; + while (!state[i].running) + Sleep(1); + } + this->number_threads = number_threads; + return true; +} + +bool WindowsWorkQueue::submit(WWQueueCallback cb, LPVOID arg) +{ + int i; + + for (i = 0; i < number_threads; ++i) { + if (state[i].task == NULL) { + state[i].arg = arg; + state[i].task = cb; + break; + } + } + if (i == number_threads) + return false; + + return true; +} + +void WindowsWorkQueue::stop() +{ + for (int i = 0; i < number_threads; ++i) { + state[i].stop = true; + while (state[i].running) + Sleep(1); + } +} diff --git a/daemons/gptp/windows/daemon_cl/work_queue.hpp b/daemons/gptp/windows/daemon_cl/work_queue.hpp new file mode 100644 index 00000000..4ac750c5 --- /dev/null +++ b/daemons/gptp/windows/daemon_cl/work_queue.hpp @@ -0,0 +1,74 @@ +/****************************************************************************** + +Copyright (c) 2009-2015, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the Intel Corporation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#ifndef WORK_QUEUE_HPP +#define WORK_QUEUE_HPP + +#include <WTypesbase.h> + +#define DEFAULT_THREAD_COUNT (5) + +struct WWQueueThreadState; + +typedef void(*WWQueueCallback)(LPVOID arg); + +class WindowsWorkQueue +{ +private: + HANDLE *workers; + WWQueueThreadState *state; + int number_threads; + +public: + /** + * @brief initialize work queue + * @param number_threads [in] number of threads (0 = default) + * @return true on success + */ + bool init( int number_threads ); + + /** + * @brief submit job to work queue + * @param cb [in] function to call + * @param arg [in] parameter provided to callback + * @return true on success + */ + bool submit( WWQueueCallback cb, LPVOID arg ); + + /** + * @brief stop work queue + */ + void stop(); +}; + +#endif/*WORK_QUEUE_HPP*/ diff --git a/daemons/gptp/windows/named_pipe_test/named_pipe_test.cpp b/daemons/gptp/windows/named_pipe_test/named_pipe_test.cpp index 713a2b4a..9a34a4ef 100644 --- a/daemons/gptp/windows/named_pipe_test/named_pipe_test.cpp +++ b/daemons/gptp/windows/named_pipe_test/named_pipe_test.cpp @@ -60,7 +60,7 @@ int _tmain(int argc, _TCHAR* argv[]) strcpy_s( pipename, 64, PIPE_PREFIX ); strcat_s( pipename, 64-strlen(pipename), P802_1AS_PIPENAME ); HANDLE pipe; - uint64_t tsc_frequency = getTSCFrequency( 1000 ); + uint64_t tsc_frequency = getTSCFrequency( true ); // Wait for Ctrl-C if( !SetConsoleCtrlHandler( ctrl_handler, true )) { diff --git a/kmod/igb/e1000_82575.c b/kmod/igb/e1000_82575.c index f8abc3e5..2fcc3bf3 100644 --- a/kmod/igb/e1000_82575.c +++ b/kmod/igb/e1000_82575.c @@ -98,10 +98,10 @@ static void e1000_clear_vfta_i350(struct e1000_hw *hw); static void e1000_i2c_start(struct e1000_hw *hw); static void e1000_i2c_stop(struct e1000_hw *hw); -static s32 e1000_clock_in_i2c_byte(struct e1000_hw *hw, u8 *data); +static void e1000_clock_in_i2c_byte(struct e1000_hw *hw, u8 *data); static s32 e1000_clock_out_i2c_byte(struct e1000_hw *hw, u8 data); static s32 e1000_get_i2c_ack(struct e1000_hw *hw); -static s32 e1000_clock_in_i2c_bit(struct e1000_hw *hw, bool *data); +static void e1000_clock_in_i2c_bit(struct e1000_hw *hw, bool *data); static s32 e1000_clock_out_i2c_bit(struct e1000_hw *hw, bool data); static void e1000_raise_i2c_clk(struct e1000_hw *hw, u32 *i2cctl); static void e1000_lower_i2c_clk(struct e1000_hw *hw, u32 *i2cctl); @@ -3217,9 +3217,7 @@ s32 e1000_read_i2c_byte_generic(struct e1000_hw *hw, u8 byte_offset, if (status != E1000_SUCCESS) goto fail; - status = e1000_clock_in_i2c_byte(hw, data); - if (status != E1000_SUCCESS) - goto fail; + e1000_clock_in_i2c_byte(hw, data); status = e1000_clock_out_i2c_bit(hw, nack); if (status != E1000_SUCCESS) @@ -3383,7 +3381,7 @@ static void e1000_i2c_stop(struct e1000_hw *hw) * * Clocks in one byte data via I2C data/clock **/ -static s32 e1000_clock_in_i2c_byte(struct e1000_hw *hw, u8 *data) +static void e1000_clock_in_i2c_byte(struct e1000_hw *hw, u8 *data) { s32 i; bool bit = 0; @@ -3396,7 +3394,6 @@ static s32 e1000_clock_in_i2c_byte(struct e1000_hw *hw, u8 *data) *data |= bit << i; } - return E1000_SUCCESS; } /** @@ -3485,7 +3482,7 @@ static s32 e1000_get_i2c_ack(struct e1000_hw *hw) * * Clocks in one bit via I2C data/clock **/ -static s32 e1000_clock_in_i2c_bit(struct e1000_hw *hw, bool *data) +static void e1000_clock_in_i2c_bit(struct e1000_hw *hw, bool *data) { u32 i2cctl = E1000_READ_REG(hw, E1000_I2CPARAMS); @@ -3504,7 +3501,6 @@ static s32 e1000_clock_in_i2c_bit(struct e1000_hw *hw, bool *data) /* Minimum low period of clock is 4.7 us */ usec_delay(E1000_I2C_T_LOW); - return E1000_SUCCESS; } /** diff --git a/kmod/igb/e1000_api.c b/kmod/igb/e1000_api.c index 54faa686..87bccbd1 100644 --- a/kmod/igb/e1000_api.c +++ b/kmod/igb/e1000_api.c @@ -187,6 +187,7 @@ s32 e1000_set_mac_type(struct e1000_hw *hw) case E1000_DEV_ID_I210_FIBER: case E1000_DEV_ID_I210_SERDES: case E1000_DEV_ID_I210_SGMII: + case E1000_DEV_ID_I210_AUTOMOTIVE: mac->type = e1000_i210; break; case E1000_DEV_ID_I211_COPPER: diff --git a/kmod/igb/e1000_hw.h b/kmod/igb/e1000_hw.h index 3bcecf19..74cb22ee 100644 --- a/kmod/igb/e1000_hw.h +++ b/kmod/igb/e1000_hw.h @@ -59,6 +59,7 @@ struct e1000_hw; #define E1000_DEV_ID_I210_FIBER 0x1536 #define E1000_DEV_ID_I210_SERDES 0x1537 #define E1000_DEV_ID_I210_SGMII 0x1538 +#define E1000_DEV_ID_I210_AUTOMOTIVE 0x15F6 #define E1000_DEV_ID_I210_COPPER_FLASHLESS 0x157B #define E1000_DEV_ID_I210_SERDES_FLASHLESS 0x157C #define E1000_DEV_ID_I211_COPPER 0x1539 diff --git a/kmod/igb/e1000_osdep.h b/kmod/igb/e1000_osdep.h index 422bba05..3c6b7958 100644 --- a/kmod/igb/e1000_osdep.h +++ b/kmod/igb/e1000_osdep.h @@ -90,7 +90,7 @@ struct e1000_hw; /* write operations, indexed using DWORDS */ #define E1000_WRITE_REG(hw, reg, val) \ do { \ - u8 __iomem *hw_addr = ACCESS_ONCE((hw)->hw_addr); \ + u8 __iomem *hw_addr = READ_ONCE((hw)->hw_addr); \ if (!E1000_REMOVED(hw_addr)) \ writel((val), &hw_addr[(reg)]); \ } while (0) diff --git a/kmod/igb/igb.h b/kmod/igb/igb.h index 7fb3e53a..c8bbf307 100644 --- a/kmod/igb/igb.h +++ b/kmod/igb/igb.h @@ -92,6 +92,9 @@ struct igb_user_page { #include <linux/i2c-algo-bit.h> #endif /* HAVE_I2C_SUPPORT */ +#include <linux/miscdevice.h> +typedef u64 cycle_t; + /* Interrupt defines */ #define IGB_START_ITR 648 /* ~6000 ints/sec */ #define IGB_4K_ITR 980 @@ -796,7 +799,7 @@ enum e1000_state_t { extern char igb_driver_name[]; extern char igb_driver_version[]; -extern int igb_up(struct igb_adapter *); +extern void igb_up(struct igb_adapter *); extern void igb_down(struct igb_adapter *); extern void igb_reinit_locked(struct igb_adapter *); extern void igb_reset(struct igb_adapter *); @@ -880,6 +883,22 @@ void igb_procfs_topdir_exit(void); #define IGB_MAP_RX_RING _IOW('E', 207, int) #define IGB_UNMAP_RX_RING _IOW('E', 208, int) +/*set of newly defined ioctl calls - new libigb compatibility + each of them is an equivalent of the old ioctl + changed numberiong convention: new_ioctl = old_ioctl + 100*/ + +#define IGB_IOCTL_MAPRING _IOW('E', 302, int) +#define IGB_IOCTL_MAP_TX_RING IGB_IOCTL_MAPRING +#define IGB_IOCTL_UNMAPRING _IOW('E', 303, int) +#define IGB_IOCTL_UNMAP_TX_RING IGB_IOCTL_UNMAPRING +#define IGB_IOCTL_MAPBUF _IOW('E', 304, int) +#define IGB_IOCTL_UNMAPBUF _IOW('E', 305, int) +#define IGB_IOCTL_MAP_RX_RING _IOW('E', 307, int) +#define IGB_IOCTL_UNMAP_RX_RING _IOW('E', 308, int) + + +/*END*/ + #define IGB_BIND_NAMESZ 24 struct igb_bind_cmd { @@ -897,6 +916,7 @@ struct igb_buf_cmd { u64 physaddr; u32 queue; u32 mmap_size; + u64 pa; }; struct igb_link_cmd { diff --git a/kmod/igb/igb_ethtool.c b/kmod/igb/igb_ethtool.c index 51ccf3a0..91e27861 100644 --- a/kmod/igb/igb_ethtool.c +++ b/kmod/igb/igb_ethtool.c @@ -1528,7 +1528,7 @@ static void igb_loopback_cleanup(struct igb_adapter *adapter) e1000_read_phy_reg(hw, PHY_CONTROL, &phy_reg); if (phy_reg & MII_CR_LOOPBACK) { phy_reg &= ~MII_CR_LOOPBACK; - if (hw->phy.type == I210_I_PHY_ID) + if (hw->phy.id == I210_I_PHY_ID) e1000_write_phy_reg(hw, I347AT4_PAGE_SELECT, 0); e1000_write_phy_reg(hw, PHY_CONTROL, phy_reg); e1000_phy_commit(hw); @@ -1713,10 +1713,10 @@ static int igb_loopback_test(struct igb_adapter *adapter, u64 *data) *data = igb_setup_desc_rings(adapter); if (*data) goto out; - *data = igb_setup_loopback_test(adapter); + igb_setup_loopback_test(adapter); + *data = igb_run_loopback_test(adapter); if (*data) goto err_loopback; - *data = igb_run_loopback_test(adapter); igb_loopback_cleanup(adapter); @@ -2141,7 +2141,7 @@ static void igb_get_strings(struct net_device *netdev, u32 stringset, u8 *data) switch (stringset) { case ETH_SS_TEST: - memcpy(data, *igb_gstrings_test, + memcpy(data, igb_gstrings_test, IGB_TEST_LEN*ETH_GSTRING_LEN); break; case ETH_SS_STATS: diff --git a/kmod/igb/igb_main.c b/kmod/igb/igb_main.c index c8a4a7e3..1f3ae697 100644 --- a/kmod/igb/igb_main.c +++ b/kmod/igb/igb_main.c @@ -96,6 +96,7 @@ static const struct pci_device_id igb_pci_tbl[] = { { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_FIBER) }, { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SERDES) }, { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SGMII) }, + { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_AUTOMOTIVE) }, { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_COPPER_FLASHLESS) }, { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SERDES_FLASHLESS) }, /* required last entry */ @@ -121,11 +122,17 @@ static void igb_clean_all_tx_rings(struct igb_adapter *); static void igb_clean_all_rx_rings(struct igb_adapter *); static void igb_clean_tx_ring(struct igb_ring *); static void igb_set_rx_mode(struct net_device *); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) +static void igb_update_phy_info(struct timer_list *); +static void igb_watchdog(struct timer_list *); +static void igb_dma_err_timer(struct timer_list *); +#else static void igb_update_phy_info(unsigned long); static void igb_watchdog(unsigned long); +static void igb_dma_err_timer(unsigned long data); +#endif static void igb_watchdog_task(struct work_struct *); static void igb_dma_err_task(struct work_struct *); -static void igb_dma_err_timer(unsigned long data); /* AVB specific */ #ifdef HAVE_NDO_SELECT_QUEUE_ACCEL_FALLBACK static u16 igb_select_queue(struct net_device *dev, struct sk_buff *skb, @@ -214,7 +221,11 @@ static long igb_ioctl_file(struct file *file, unsigned int cmd, unsigned long arg); static void igb_vm_open(struct vm_area_struct *vma); static void igb_vm_close(struct vm_area_struct *vma); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0) +static int igb_vm_fault(struct vm_fault *fdata); +#else static int igb_vm_fault(struct vm_area_struct *area, struct vm_fault *fdata); +#endif static int igb_mmap(struct file *file, struct vm_area_struct *vma); static ssize_t igb_read(struct file *file, char __user *buf, size_t count, loff_t *pos); @@ -458,7 +469,7 @@ static void igb_cache_ring_register(struct igb_adapter *adapter) u32 e1000_read_reg(struct e1000_hw *hw, u32 reg) { struct igb_adapter *igb = container_of(hw, struct igb_adapter, hw); - u8 __iomem *hw_addr = ACCESS_ONCE(hw->hw_addr); + u8 __iomem *hw_addr = READ_ONCE(hw->hw_addr); u32 value = 0; if (E1000_REMOVED(hw_addr)) @@ -1090,8 +1101,13 @@ static void igb_set_interrupt_capability(struct igb_adapter *adapter, bool msix) for (i = 0; i < numvecs; i++) adapter->msix_entries[i].entry = i; - err = pci_enable_msix(pdev, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0) + err = pci_enable_msix_exact(pdev, adapter->msix_entries, numvecs); +#else + err = pci_enable_msix(pdev, + adapter->msix_entries, numvecs); +#endif if (err == 0) break; } @@ -1810,7 +1826,7 @@ static s32 igb_init_i2c(struct igb_adapter *adapter) * igb_up - Open the interface and prepare it to handle traffic * @adapter: board private structure **/ -int igb_up(struct igb_adapter *adapter) +void igb_up(struct igb_adapter *adapter) { struct e1000_hw *hw = &adapter->hw; int i; @@ -1854,7 +1870,6 @@ int igb_up(struct igb_adapter *adapter) (!hw->dev_spec._82575.eee_disable)) adapter->eee_advert = MDIO_EEE_100TX | MDIO_EEE_1000T; - return 0; } void igb_down(struct igb_adapter *adapter) @@ -2877,6 +2892,13 @@ static int igb_probe(struct pci_dev *pdev, /* Check if Media Autosense is enabled */ if (hw->mac.type == e1000_82580) igb_init_mas(adapter); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) + timer_setup(&adapter->watchdog_timer, &igb_watchdog, 0); + if (adapter->flags & IGB_FLAG_DETECT_BAD_DMA) + timer_setup(&adapter->dma_err_timer, &igb_dma_err_timer, 0); + timer_setup(&adapter->phy_info_timer, &igb_update_phy_info, 0); +#else setup_timer(&adapter->watchdog_timer, &igb_watchdog, (unsigned long) adapter); if (adapter->flags & IGB_FLAG_DETECT_BAD_DMA) @@ -2884,6 +2906,7 @@ static int igb_probe(struct pci_dev *pdev, (unsigned long) adapter); setup_timer(&adapter->phy_info_timer, &igb_update_phy_info, (unsigned long) adapter); +#endif INIT_WORK(&adapter->reset_task, igb_reset_task); INIT_WORK(&adapter->watchdog_task, igb_watchdog_task); @@ -4673,9 +4696,19 @@ static void igb_spoof_check(struct igb_adapter *adapter) /* Need to wait a few seconds after link up to get diagnostic information from * the phy */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) +static void igb_update_phy_info(struct timer_list *timer) +#else static void igb_update_phy_info(unsigned long data) +#endif { - struct igb_adapter *adapter = (struct igb_adapter *) data; + struct igb_adapter *adapter; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) + adapter = container_of(timer, struct igb_adapter, watchdog_timer); +#else + adapter = (struct igb_adapter *) data; +#endif e1000_get_phy_info(&adapter->hw); } @@ -4725,9 +4758,20 @@ bool igb_has_link(struct igb_adapter *adapter) * igb_watchdog - Timer Call-back * @data: pointer to adapter cast into an unsigned long **/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) +static void igb_watchdog(struct timer_list *timer) +#else static void igb_watchdog(unsigned long data) +#endif { - struct igb_adapter *adapter = (struct igb_adapter *)data; + struct igb_adapter *adapter; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) + adapter = container_of(timer, struct igb_adapter, watchdog_timer); +#else + adapter = (struct igb_adapter *)data; +#endif + /* Do the rest outside of interrupt context */ schedule_work(&adapter->watchdog_task); } @@ -4983,9 +5027,20 @@ dma_timer_reset: * igb_dma_err_timer - Timer Call-back * @data: pointer to adapter cast into an unsigned long **/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) +static void igb_dma_err_timer(struct timer_list *timer) +#else static void igb_dma_err_timer(unsigned long data) +#endif { - struct igb_adapter *adapter = (struct igb_adapter *)data; + struct igb_adapter *adapter; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) + adapter = container_of(timer, struct igb_adapter, dma_err_timer); +#else + adapter = (struct igb_adapter *)data; +#endif + /* Do the rest outside of interrupt context */ schedule_work(&adapter->dma_err_task); } @@ -9606,10 +9661,7 @@ static void igb_io_resume(struct pci_dev *pdev) } if (netif_running(netdev)) { - if (igb_up(adapter)) { - dev_err(pci_dev_to_dev(pdev), "igb_up failed after reset\n"); - return; - } + igb_up(adapter); } netif_device_attach(netdev); @@ -10218,7 +10270,14 @@ static int igb_bind(struct file *file, void __user *argp) if (copy_from_user(&req, argp, sizeof(req))) return -EFAULT; + + /* + * Set the last character of req.iface to '/0' to + * guarantee null termination of req.iface string + * param in printk call. + */ + req.iface[IGB_BIND_NAMESZ-1] = 0; printk("bind to iface %s\n", req.iface); if (igb_priv == NULL) { @@ -10309,11 +10368,16 @@ static long igb_mapbuf_user(struct file *file, void __user *arg, int ring) struct igb_private_data *igb_priv = file->private_data; struct igb_adapter *adapter; struct igb_buf_cmd req; + /*size used for the purpose of copying the contents of igb_buf_cmd + between userspace and kernel space + introduced to handle possible mismatch in libigb and igb version*/ + int buf_cmd_size = 0; int err = 0; struct page *page; dma_addr_t page_dma; struct igb_user_page *userpage; + if (igb_priv == NULL) { printk("cannot find private data!\n"); return -ENOENT; @@ -10325,7 +10389,22 @@ static long igb_mapbuf_user(struct file *file, void __user *arg, int ring) return -ENOENT; } - if (copy_from_user(&req, arg, sizeof(req))) + if(ring != IGB_IOCTL_MAPBUF) + { + dev_warn(&adapter->pdev->dev, "Old ioctl value used: %d, consider using a new one from libigb \n", ring); + /* this situation suggest using an old ioctl by libigb + as a consequence the igb_buf_cmd struct from libigb perspective does not contain the "pa" field + we need to align the requested size in copy_from_user() for possibility */ + buf_cmd_size = sizeof(req) - sizeof(u64); + } else { + /* assuming no compatibility issue: + libigb and kernel module have the same + igb_buf_cmd structs ("pa" field included in both) + */ + buf_cmd_size = sizeof(req); + } + + if (copy_from_user(&req, arg, buf_cmd_size)) return -EFAULT; userpage = vzalloc(sizeof(struct igb_user_page)); @@ -10368,11 +10447,14 @@ static long igb_mapbuf_user(struct file *file, void __user *arg, int ring) igb_priv->userpages->page = page; igb_priv->userpages->page_dma = page_dma; + if(ring == IGB_IOCTL_MAPBUF) + req.pa = page_to_phys(page); + req.physaddr = page_dma; req.mmap_size = PAGE_SIZE; mutex_unlock(&adapter->lock); - if (copy_to_user(arg, &req, sizeof(req))) { + if (copy_to_user(arg, &req, buf_cmd_size)) { printk("copyout to user failed\n"); err = -EFAULT; mutex_lock(&adapter->lock); @@ -10405,15 +10487,17 @@ static long igb_mapbuf(struct file *file, void __user *arg, int ring) struct igb_private_data *igb_priv = file->private_data; struct igb_adapter *adapter; struct igb_buf_cmd req; + /*size used for the purpose of copying the contents of igb_buf_cmd + between userspace and kernel space + introduced to handle possible mismatch in libigb and igb version*/ + int buf_cmd_size = 0; + int err = 0; if (igb_priv == NULL) { printk("cannot find private data!\n"); return -ENOENT; - } - - if (copy_from_user(&req, arg, sizeof(req))) - return -EFAULT; + } adapter = igb_priv->adapter; if (adapter == NULL) { @@ -10421,7 +10505,29 @@ static long igb_mapbuf(struct file *file, void __user *arg, int ring) return -ENOENT; } - if ((ring == IGB_MAPRING) || (ring == IGB_MAP_TX_RING)) { + if((ring != IGB_IOCTL_MAP_TX_RING) && (ring != IGB_IOCTL_MAP_RX_RING)) + { + dev_warn(&adapter->pdev->dev, "Old ioctl value used: %d, consider using new one from libigb \n", ring); + /* this situation suggest using an old ioctl by libigb + as a consequence the igb_buf_cmd struct from libigb perspective does not contain the "pa" field + we need to align the requested size in copy_from_user() for possibility */ + buf_cmd_size = sizeof(req) - sizeof(u64); + + } else { + + /* assuming no compatibility issue: + libigb and kernel module have the same + igb_buf_cmd structs ("pa" field included in both) + */ + buf_cmd_size = sizeof(req); + + } + + if (copy_from_user(&req, arg, buf_cmd_size)) + return -EFAULT; + + if ((ring == IGB_MAPRING) || (ring == IGB_MAP_TX_RING) || + ring == IGB_IOCTL_MAP_TX_RING) { if (req.queue >= 3) { printk("mapring:invalid queue specified(%d)\n", req.queue); @@ -10443,10 +10549,13 @@ static long igb_mapbuf(struct file *file, void __user *arg, int ring) adapter->uring_tx_init |= (1 << req.queue); igb_priv->uring_tx_init |= (1 << req.queue); + if(ring == IGB_IOCTL_MAP_TX_RING) + req.pa = virt_to_phys(adapter->tx_ring[req.queue]->desc); + req.physaddr = adapter->tx_ring[req.queue]->dma; req.mmap_size = adapter->tx_ring[req.queue]->size; mutex_unlock(&adapter->lock); - } else if (ring == IGB_MAP_RX_RING) { + } else if ((ring == IGB_MAP_RX_RING) || (ring == IGB_IOCTL_MAP_RX_RING)) { if (req.queue >= 3) { printk("mapring:invalid queue specified(%d)\n", req.queue); @@ -10467,6 +10576,9 @@ static long igb_mapbuf(struct file *file, void __user *arg, int ring) adapter->uring_rx_init |= (1 << req.queue); igb_priv->uring_rx_init |= (1 << req.queue); + + if(ring == IGB_IOCTL_MAP_RX_RING) + req.pa = virt_to_phys(adapter->rx_ring[req.queue]->desc); req.physaddr = adapter->rx_ring[req.queue]->dma; req.mmap_size = adapter->rx_ring[req.queue]->size; @@ -10476,7 +10588,7 @@ static long igb_mapbuf(struct file *file, void __user *arg, int ring) return -EINVAL; } - if (copy_to_user(arg, &req, sizeof(req))) { + if (copy_to_user(arg, &req, buf_cmd_size)) { printk("copyout to user failed\n"); err = -EFAULT; goto failed; @@ -10494,22 +10606,36 @@ static long igb_unmapbuf(struct file *file, void __user *arg, int ring) struct igb_private_data *igb_priv = file->private_data; struct igb_adapter *adapter; struct igb_buf_cmd req; + int buf_cmd_size = 0; if (igb_priv == NULL) { printk("cannot find private data!\n"); return -ENOENT; } - if (copy_from_user(&req, arg, sizeof(req))) - return -EFAULT; - adapter = igb_priv->adapter; if (adapter == NULL) { printk("map to unbound device!\n"); return -ENOENT; } - if ((ring == IGB_UNMAP_TX_RING)) { + + if((ring != IGB_IOCTL_UNMAPBUF) && (ring != IGB_IOCTL_UNMAP_TX_RING) && + (ring != IGB_IOCTL_UNMAP_RX_RING)) { + + dev_warn(&adapter->pdev->dev, "Old ioctl number used: %d, consider using new one from libigb \n", ring); + buf_cmd_size = sizeof(req) - sizeof(u64); + + } else { + + buf_cmd_size = sizeof(req); + } + + if (copy_from_user(&req, arg, buf_cmd_size)) + return -EFAULT; + + + if ((ring == IGB_UNMAP_TX_RING) || (ring == IGB_IOCTL_UNMAP_TX_RING)) { /* its easy to figure out what to free on the rings ... */ if (req.queue >= 3) return -EINVAL; @@ -10527,7 +10653,7 @@ static long igb_unmapbuf(struct file *file, void __user *arg, int ring) adapter->uring_tx_init &= ~(1 << req.queue); igb_priv->uring_tx_init &= ~(1 << req.queue); mutex_unlock(&adapter->lock); - } else if (ring == IGB_UNMAP_RX_RING) { + } else if ((ring == IGB_UNMAP_RX_RING) || (ring == IGB_IOCTL_UNMAP_RX_RING)) { /* its easy to figure out what to free on the rings ... */ if (req.queue >= 3) return -EINVAL; @@ -10601,14 +10727,20 @@ static long igb_ioctl_file(struct file *file, unsigned int cmd, break; case IGB_MAP_TX_RING: case IGB_MAP_RX_RING: + case IGB_IOCTL_MAP_TX_RING: + case IGB_IOCTL_MAP_RX_RING: err = igb_mapbuf(file, argp, cmd); break; case IGB_MAPBUF: + case IGB_IOCTL_MAPBUF: err = igb_mapbuf_user(file, argp, cmd); break; case IGB_UNMAP_TX_RING: case IGB_UNMAP_RX_RING: case IGB_UNMAPBUF: + case IGB_IOCTL_UNMAPBUF: + case IGB_IOCTL_UNMAP_TX_RING: + case IGB_IOCTL_UNMAP_RX_RING: err = igb_unmapbuf(file, argp, cmd); break; case IGB_LINKSPEED: @@ -10701,8 +10833,11 @@ static void igb_vm_open(struct vm_area_struct *vma) static void igb_vm_close(struct vm_area_struct *vma) { } - +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0) +static int igb_vm_fault(struct vm_fault *fdata) +#else static int igb_vm_fault(struct vm_area_struct *area, struct vm_fault *fdata) +#endif { return VM_FAULT_SIGBUS; } diff --git a/kmod/igb/igb_param.c b/kmod/igb/igb_param.c index ca700b93..6c102ed5 100644 --- a/kmod/igb/igb_param.c +++ b/kmod/igb/igb_param.c @@ -539,7 +539,7 @@ void igb_check_options(struct igb_adapter *adapter) .max = (MAX_VMDQ - adapter->vfs_allocated_count)} } }; - if ((hw->mac.type != e1000_i210) || + if ((hw->mac.type != e1000_i210) && (hw->mac.type != e1000_i211)) { #ifdef module_param_array if (num_VMDQ > bd) { diff --git a/lib/avtp_pipeline/map_h264/openavb_map_h264.c b/lib/avtp_pipeline/map_h264/openavb_map_h264.c index 5fd114ac..6c2557f7 100755..100644 --- a/lib/avtp_pipeline/map_h264/openavb_map_h264.c +++ b/lib/avtp_pipeline/map_h264/openavb_map_h264.c @@ -59,11 +59,11 @@ https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175. // Header sizes #define AVTP_V0_HEADER_SIZE 12 -#define MAP_HEADER_SIZE 12 +#define MAP_HEADER_SIZE 16 #define TOTAL_HEADER_SIZE (AVTP_V0_HEADER_SIZE + MAP_HEADER_SIZE) -#define MAX_PAYLOAD_SIZE 1412 +#define MAX_PAYLOAD_SIZE 1416 ////// // AVTP Version 0 Header @@ -84,7 +84,7 @@ https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175. ////// // - 4 bytes avtp_timestamp -#define HIDX_AVTP_TIMESPAMP32 12 +#define HIDX_AVTP_TIMESTAMP32 12 // - 1 bytes Format information = 0x02 RTP Video #define HIDX_FORMAT8 16 @@ -109,6 +109,9 @@ https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175. // - 1 byte Reserved = binary 0x00 #define HIDX_RESV8 23 +// - 4 bytes h264_timestamp +#define HIDX_H264_TIMESTAMP32 24 + typedef struct { ///////////// // Config data @@ -261,7 +264,7 @@ tx_cb_ret_t openavbMapH264TxCB(media_q_t *pMediaQ, U8 *pData, U32 *dataLen) return TX_CB_RET_PACKET_NOT_READY; } - //pHdr[HIDX_AVTP_TIMESPAMP32] = 0x00; // Set later + //pHdr[HIDX_AVTP_TIMESTAMP32] = 0x00; // Set later pHdr[HIDX_FORMAT8] = 0x02; // RTP Payload type pHdr[HIDX_FORMAT_SUBTYPE8] = 0x01; // H.264 subtype pHdr[HIDX_RESV16] = 0x0000; // Reserved @@ -295,7 +298,7 @@ tx_cb_ret_t openavbMapH264TxCB(media_q_t *pMediaQ, U8 *pData, U32 *dataLen) else pHdr[HIDX_AVTP_HIDE7_TU1] &= ~0x01; // Clear // Set the timestamp. - *(U32 *)(&pHdr[HIDX_AVTP_TIMESPAMP32]) = htonl(openavbAvtpTimeGetAvtpTimestamp(pMediaQItem->pAvtpTime)); + *(U32 *)(&pHdr[HIDX_AVTP_TIMESTAMP32]) = htonl(openavbAvtpTimeGetAvtpTimestamp(pMediaQItem->pAvtpTime)); if (((media_q_item_map_h264_pub_data_t *)pMediaQItem->pPubMapData)->lastPacket) { pHdr[HIDX_M31_M21_M11_M01_EVT2_RESV2] = 0x10;; @@ -304,7 +307,11 @@ tx_cb_ret_t openavbMapH264TxCB(media_q_t *pMediaQ, U8 *pData, U32 *dataLen) pHdr[HIDX_M31_M21_M11_M01_EVT2_RESV2] = 0x00; } - // Copy the JPEG fragment into the outgoing avtp packet. + // Set h264_timestamp + *(U32 *)(&pHdr[HIDX_H264_TIMESTAMP32]) = + htonl(((media_q_item_map_h264_pub_data_t *)pMediaQItem->pPubMapData)->timestamp); + + // Copy the h264 rtp payload into the outgoing avtp packet. memcpy(pPayload, pMediaQItem->pPubData, pMediaQItem->dataLen); *(U16 *)(&pHdr[HIDX_STREAM_DATA_LEN16]) = htons(pMediaQItem->dataLen); @@ -347,7 +354,7 @@ bool openavbMapH264RxCB(media_q_t *pMediaQ, U8 *pData, U32 dataLen) U8 *pPayload = pData + TOTAL_HEADER_SIZE; - //pHdr[HIDX_AVTP_TIMESPAMP32] + //pHdr[HIDX_AVTP_TIMESTAMP32] //pHdr[HIDX_FORMAT8] //pHdr[HIDX_FORMAT_SUBTYPE8] //pHdr[HIDX_RESV16] @@ -367,7 +374,7 @@ bool openavbMapH264RxCB(media_q_t *pMediaQ, U8 *pData, U32 dataLen) media_q_item_t *pMediaQItem = openavbMediaQHeadLock(pMediaQ); if (pMediaQItem) { // Get the timestamp and place it in the media queue item. - U32 timestamp = ntohl(*(U32 *)(&pHdr[HIDX_AVTP_TIMESPAMP32])); + U32 timestamp = ntohl(*(U32 *)(&pHdr[HIDX_AVTP_TIMESTAMP32])); openavbAvtpTimeSetToTimestamp(pMediaQItem->pAvtpTime, timestamp); // Set timestamp valid and timestamp uncertain flags @@ -379,6 +386,9 @@ bool openavbMapH264RxCB(media_q_t *pMediaQ, U8 *pData, U32 dataLen) else ((media_q_item_map_h264_pub_data_t *)pMediaQItem->pPubMapData)->lastPacket = FALSE; + ((media_q_item_map_h264_pub_data_t *)pMediaQItem->pPubMapData)->timestamp = + ntohl(*(U32 *)(&pHdr[HIDX_H264_TIMESTAMP32])); + if (pMediaQItem->itemSize >= payloadLen) { memcpy(pMediaQItem->pPubData, pPayload, payloadLen); pMediaQItem->dataLen = payloadLen; diff --git a/lib/avtp_pipeline/map_h264/openavb_map_h264_pub.h b/lib/avtp_pipeline/map_h264/openavb_map_h264_pub.h index 307b4ba1..b547fb93 100755..100644 --- a/lib/avtp_pipeline/map_h264/openavb_map_h264_pub.h +++ b/lib/avtp_pipeline/map_h264/openavb_map_h264_pub.h @@ -54,6 +54,8 @@ https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175. typedef struct { // Last fragment of frame flag. bool lastPacket; // For details see 1722a 9.4.3.1.1 M0 field + // The timestamp of h.264 NAL unit fragment. + U32 timestamp; // For details see 1722-2016 8.5.3.1 h264_timestamp field } media_q_item_map_h264_pub_data_t; #endif // OPENAVB_MAP_H264_PUB_H diff --git a/lib/avtp_pipeline/platform/Linux/gst_al/gst_al.h b/lib/avtp_pipeline/platform/Linux/gst_al/gst_al.h index dbdb5b09..7638729a 100644 --- a/lib/avtp_pipeline/platform/Linux/gst_al/gst_al.h +++ b/lib/avtp_pipeline/platform/Linux/gst_al/gst_al.h @@ -128,6 +128,14 @@ gboolean gst_al_rtp_buffer_get_marker(GstAlBuf *buf); */ void gst_al_rtp_buffer_set_marker(GstAlBuf *buf, gboolean mark); /** + * \brief - gets a RTP buffer timestamp + * + * \param buf - a RTP buffer + * + * \return - a RTP buffer timestamp + */ +guint32 gst_al_rtp_buffer_get_timestamp(GstAlBuf *buf); +/** * \brief - set a RTP buffer params * * \param buf - a RTP buffer diff --git a/lib/avtp_pipeline/platform/Linux/gst_al/gst_al_10.c b/lib/avtp_pipeline/platform/Linux/gst_al/gst_al_10.c index 0d1898c5..0718e1fc 100644 --- a/lib/avtp_pipeline/platform/Linux/gst_al/gst_al_10.c +++ b/lib/avtp_pipeline/platform/Linux/gst_al/gst_al_10.c @@ -185,6 +185,11 @@ void gst_al_rtp_buffer_set_marker(GstAlBuf *buf, gboolean mark) gst_rtp_buffer_set_marker(&buf->m_rtpbuf, mark); } +guint32 gst_al_rtp_buffer_get_timestamp(GstAlBuf *buf) +{ + return gst_rtp_buffer_get_timestamp(&buf->m_rtpbuf); +} + void gst_al_rtp_buffer_set_params(GstAlBuf *buf, gint ssrc, gint payload_type, gint version, gint sequence) diff --git a/lib/avtp_pipeline/platform/Linux/intf_h264_gst/openavb_intf_h264_gst.c b/lib/avtp_pipeline/platform/Linux/intf_h264_gst/openavb_intf_h264_gst.c index 1be182d1..68508e91 100644 --- a/lib/avtp_pipeline/platform/Linux/intf_h264_gst/openavb_intf_h264_gst.c +++ b/lib/avtp_pipeline/platform/Linux/intf_h264_gst/openavb_intf_h264_gst.c @@ -340,6 +340,8 @@ bool openavbIntfH264RtpGstTxCB(media_q_t *pMediaQ) { ((media_q_item_map_h264_pub_data_t *)pMediaQItem->pPubMapData)->lastPacket = FALSE; } + ((media_q_item_map_h264_pub_data_t *)pMediaQItem->pPubMapData)->timestamp = + gst_al_rtp_buffer_get_timestamp(txBuf); openavbAvtpTimeSetToWallTime(pMediaQItem->pAvtpTime); openavbMediaQHeadPush(pMediaQ); @@ -545,7 +547,8 @@ bool openavbIntfH264RtpGstRxCB(media_q_t *pMediaQ) } memcpy(GST_AL_BUF_DATA(rxBuf), pMediaQItem->pPubData, pMediaQItem->dataLen); - GST_AL_BUFFER_TIMESTAMP(rxBuf) = GST_CLOCK_TIME_NONE; + //GST_AL_BUFFER_TIMESTAMP(rxBuf) = GST_CLOCK_TIME_NONE; + GST_AL_BUFFER_TIMESTAMP(rxBuf) = ((media_q_item_map_h264_pub_data_t *)pMediaQItem->pPubMapData)->timestamp; GST_AL_BUFFER_DURATION(rxBuf) = GST_CLOCK_TIME_NONE; if ( ((media_q_item_map_h264_pub_data_t *)pMediaQItem->pPubMapData)->lastPacket ) { diff --git a/lib/igb/e1000_hw.h b/lib/igb/e1000_hw.h index 1a3c46c7..1732d502 100644 --- a/lib/igb/e1000_hw.h +++ b/lib/igb/e1000_hw.h @@ -71,6 +71,7 @@ struct e1000_hw; #define E1000_DEV_ID_I210_FIBER 0x1536 #define E1000_DEV_ID_I210_SERDES 0x1537 #define E1000_DEV_ID_I210_SGMII 0x1538 +#define E1000_DEV_ID_I210_AUTOMOTIVE 0x15F6 #define E1000_DEV_ID_I210_COPPER_FLASHLESS 0x157B #define E1000_DEV_ID_I210_SERDES_FLASHLESS 0x157C #define E1000_DEV_ID_I211_COPPER 0x1539 diff --git a/lib/igb/igb.c b/lib/igb/igb.c index d59a0fcb..96e18951 100644 --- a/lib/igb/igb.c +++ b/lib/igb/igb.c @@ -79,6 +79,7 @@ static igb_vendor_info_t igb_vendor_info_array[] = { { 0x8086, E1000_DEV_ID_I210_SERDES_FLASHLESS, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_I210_SGMII, PCI_ANY_ID, PCI_ANY_ID, 0}, + { 0x8086, E1000_DEV_ID_I210_AUTOMOTIVE, PCI_ANY_ID, PCI_ANY_ID, 0}, /* required last entry */ { 0, 0, 0, 0, 0} }; @@ -681,14 +682,15 @@ int igb_dma_malloc_page(device_t *dev, struct igb_dma_alloc *dma) error = errno; goto err; } - error = ioctl(adapter->ldev, IGB_MAPBUF, &ubuf); + error = ioctl(adapter->ldev, IGB_IOCTL_MAPBUF, &ubuf); if (igb_unlock(dev) != 0) { error = errno; goto err; } if (error < 0) { - error = -ENOMEM; + if(error != -EINVAL) + error = -ENOMEM; goto err; } @@ -699,7 +701,7 @@ int igb_dma_malloc_page(device_t *dev, struct igb_dma_alloc *dma) PROT_READ | PROT_WRITE, MAP_SHARED, adapter->ldev, - ubuf.physaddr); + ubuf.pa); if (dma->dma_vaddr == MAP_FAILED) error = -ENOMEM; @@ -729,7 +731,7 @@ void igb_dma_free_page(device_t *dev, struct igb_dma_alloc *dma) if (igb_lock(dev) != 0) goto err; - ioctl(adapter->ldev, IGB_UNMAPBUF, &ubuf); + ioctl(adapter->ldev, IGB_IOCTL_UNMAPBUF, &ubuf); if (igb_unlock(dev) != 0) goto err; @@ -767,8 +769,11 @@ static int igb_allocate_queues(struct adapter *adapter) for (i = 0; i < adapter->num_queues; i++) { ubuf.queue = i; - error = ioctl(dev, IGB_MAPRING, &ubuf); + error = ioctl(dev, IGB_IOCTL_MAPRING, &ubuf); if (error < 0) { + if(error == -EINVAL) + goto tx_fail; + error = EBUSY; goto tx_desc; } @@ -784,7 +789,7 @@ static int igb_allocate_queues(struct adapter *adapter) (struct e1000_tx_desc *)mmap(NULL, ubuf.mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, adapter->ldev, - ubuf.physaddr); + ubuf.pa); if (adapter->tx_rings[i].tx_base == MAP_FAILED) { error = -ENOMEM; @@ -828,7 +833,7 @@ tx_desc: munmap(adapter->tx_rings[i].tx_base, adapter->tx_rings[i].txdma.mmap_size); ubuf.queue = i; - ioctl(dev, IGB_UNMAPRING, &ubuf); + ioctl(dev, IGB_IOCTL_UNMAPRING, &ubuf); }; tx_fail: free(adapter->tx_rings); @@ -908,7 +913,7 @@ static void igb_free_transmit_structures(struct adapter *adapter) munmap(adapter->tx_rings[i].tx_base, adapter->tx_rings[i].txdma.mmap_size); ubuf.queue = i; - ioctl(adapter->ldev, IGB_UNMAPRING, &ubuf); + ioctl(adapter->ldev, IGB_IOCTL_UNMAPRING, &ubuf); free(adapter->tx_rings[i].tx_buffers); } @@ -1352,8 +1357,12 @@ static int igb_allocate_rx_queues(struct adapter *adapter) } ubuf.queue = i; - error = ioctl(dev, IGB_MAP_RX_RING, &ubuf); + error = ioctl(dev, IGB_IOCTL_MAP_RX_RING, &ubuf); if (error < 0) { + + if(error == -EINVAL) + goto rx_fail; + error = EBUSY; goto rx_desc; } @@ -1367,7 +1376,7 @@ static int igb_allocate_rx_queues(struct adapter *adapter) adapter->rx_rings[i].rx_base = NULL; adapter->rx_rings[i].rx_base = mmap(NULL, ubuf.mmap_size, PROT_READ | PROT_WRITE, - MAP_SHARED, adapter->ldev, ubuf.physaddr); + MAP_SHARED, adapter->ldev, ubuf.pa); if (adapter->rx_rings[i].rx_base == MAP_FAILED) { error = -ENOMEM; @@ -1416,7 +1425,7 @@ rx_desc: munmap(adapter->rx_rings[i].rx_base, adapter->rx_rings[i].rxdma.mmap_size); ubuf.queue = i; - ioctl(dev, IGB_UNMAP_RX_RING, &ubuf); + ioctl(dev, IGB_IOCTL_UNMAP_RX_RING, &ubuf); sem_destroy(&adapter->rx_rings[i].lock); }; @@ -1571,7 +1580,7 @@ static void igb_free_receive_structures(struct adapter *adapter) munmap(rxr->rx_base, rxr->rxdma.mmap_size); } ubuf.queue = i; - ioctl(adapter->ldev, IGB_UNMAP_RX_RING, &ubuf); + ioctl(adapter->ldev, IGB_IOCTL_UNMAP_RX_RING, &ubuf); igb_free_receive_buffers(rxr); (void)sem_destroy(&adapter->rx_rings[i].lock); diff --git a/lib/igb/igb_internal.h b/lib/igb/igb_internal.h index b99ee063..24dd7496 100644 --- a/lib/igb/igb_internal.h +++ b/lib/igb/igb_internal.h @@ -216,6 +216,21 @@ typedef struct _igb_vendor_info_t { #define IGB_MAP_RX_RING _IOW('E', 207, int) #define IGB_UNMAP_RX_RING _IOW('E', 208, int) +/*set of newly defined ioctl calls - new libigb compatibility + each of them is an equivalent of the old ioctl + changed numberiong convention: new_ioctl = old_ioctl + 100*/ + +#define IGB_IOCTL_MAPRING _IOW('E', 302, int) +#define IGB_IOCTL_MAP_TX_RING IGB_IOCTL_MAPRING +#define IGB_IOCTL_UNMAPRING _IOW('E', 303, int) +#define IGB_IOCTL_UNMAP_TX_RING IGB_IOCTL_UNMAPRING +#define IGB_IOCTL_MAPBUF _IOW('E', 304, int) +#define IGB_IOCTL_UNMAPBUF _IOW('E', 305, int) +#define IGB_IOCTL_MAP_RX_RING _IOW('E', 307, int) +#define IGB_IOCTL_UNMAP_RX_RING _IOW('E', 308, int) + +/*END*/ + #define IGB_BIND_NAMESZ 24 struct igb_bind_cmd { @@ -227,6 +242,7 @@ struct igb_buf_cmd { u_int64_t physaddr; /* dma_addr_t is 64-bit */ unsigned int queue; unsigned int mmap_size; + u_int64_t pa; }; struct igb_link_cmd { diff --git a/lib/libavtp/HACKING.md b/lib/libavtp/HACKING.md new file mode 100644 index 00000000..8b93064a --- /dev/null +++ b/lib/libavtp/HACKING.md @@ -0,0 +1,73 @@ +# Contributing + +If you have a bug fixed or added support for some AVTP feature, your patches +are welcomed! In order to get your patches merged faster, please follow the +guidelines: + +* Check if all patches are following the coding style from libavtp project. See +section 'Coding Style' for more information. + +* Before submitting your patch to review, make sure it doesn't break any unit +test. See section 'Running Unit Tests' for information about how to build and +run unit tests. + +* Besides the bugfix/feature itself, also provide unit test covering the code +you're contributing. See section 'Code Coverage' to check how you can easily +generate coverage reports and see where you need to work on to get your code +covered. + +* If your patch adds new public APIs to libavtp, please also provide patches +adding example applications (or modify an existing one if it makes sense) +which demonstrate how to use the new APIs. + +* Make sure the author's name and email are set properly. + +# Coding Style + +The coding style from libavtp is pretty much the same from Linux kernel +described in https://www.kernel.org/doc/html/latest/process/coding-style.html. + +The style for line wrapping is to indent as far as possible to the right +without hitting the 80 columns limit. + +Example: + +``` +/* Correct */ +int avtp_aaf_pdu_set(struct avtp_stream_pdu *pdu, enum avtp_aaf_field field, + uint64_t val) + +/* Wrong */ +int avtp_aaf_pdu_set(struct avtp_stream_pdu *pdu, enum avtp_aaf_field field, + uint64_t val) +``` + +# Running Unit Tests + +Unit tests required libcmocka so make sure you have cmocka packages installed +in your system. + +Run the following command to build and run all unit tests. Make sure the build +system files are generated already, see section 'Build' in the README.md file +for more information. + +``` +$ ninja -C build test +``` + +# Code Coverage + +Meson build system provides some built-in code coverage support based on `lcov` +so make sure you have this package installed on your system in order to +generate coverage reports. + +To generate html reports run the following commands: + +``` +$ rm -rf build/ +$ meson build -Db_coverage=true +$ ninja -C build/ test +$ ninja -C build/ coverage-html +``` + +The coverage report can be found in build/meson-logs/coveragereport/ directory. diff --git a/lib/libavtp/LICENSE b/lib/libavtp/LICENSE new file mode 100644 index 00000000..f0eb281f --- /dev/null +++ b/lib/libavtp/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2017, Intel Corporation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Intel Corporation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/libavtp/README.md b/lib/libavtp/README.md new file mode 100644 index 00000000..4aa025b0 --- /dev/null +++ b/lib/libavtp/README.md @@ -0,0 +1,48 @@ +# About + +Open source implementation of Audio Video Transport Protocol (AVTP) specified +in IEEE 1722-2016 spec. + +Libavtp is under BSD License. For more information see LICENSE file. + +# Build + +Before building libavtp make sure you have all the required software installed +in your system. Below are the requirements and their tested versions: + +* Meson >= 0.43 +* Ninja >= 1.8.2 + +The first step to build libavtp is to generate the build system files. + +``` +$ meson build +``` + +Then build libavtp by running the following command. The building artifacts +will be created under the build/ in the top-level directory. + +``` +$ ninja -C build +``` + +To install libavtp on your system run: +``` +$ sudo ninja -C build install +``` + +# AVTP Formats Support + +AVTP protocol defines several AVTPDU type formats (see Table 6 from IEEE +1722-2016 spec). Libavtp doesn't support all of them yet. The list of supported +formarts is: +* AAF (PCM encapsulation only) + +# Examples + +The `examples/` directory in the top-level directory provides example +applications which demonstrate the libavtp functionalities. To build an +example application run `$ ninja -C build <example name>`. + +Information about what exactly each example application does and how it works +is provided in the beginning of the .c file from each application. diff --git a/lib/libavtp/examples/aaf-listener.c b/lib/libavtp/examples/aaf-listener.c new file mode 100644 index 00000000..61578482 --- /dev/null +++ b/lib/libavtp/examples/aaf-listener.c @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* AAF Listener example. + * + * This example implements a very simple AAF listener application which + * receives AFF packets from the network, retrieves the PCM samples, and + * writes them to stdout once the presentation time is reached. + * + * For simplicity, the example accepts only AAF packets with the following + * specification: + * - Sample format: 16-bit little endian + * - Sample rate: 48 kHz + * - Number of channels: 2 (stereo) + * + * TSN stream parameters such as destination mac address are passed via + * command-line arguments. Run 'aaf-listener --help' for more information. + * + * This example relies on the system clock to schedule PCM samples for + * playback. So make sure the system clock is synchronized with the PTP + * Hardware Clock (PHC) from your NIC and that the PHC is synchronized with + * the PTP time from the network. For further information on how to synchronize + * those clocks see ptp4l(8) and phc2sys(8) man pages. + * + * The easiest way to use this example is combining it with 'aplay' tool + * provided by alsa-utils. 'aplay' reads a PCM stream from stdin and sends it + * to a ALSA playback device (e.g. your speaker). So, to play Audio from a TSN + * stream, you should do something like this: + * + * $ aaf-listener <args> | aplay -f dat -t raw -D <playback-device> + */ + +#include <assert.h> +#include <argp.h> +#include <arpa/inet.h> +#include <linux/if.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <poll.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/queue.h> +#include <sys/timerfd.h> +#include <unistd.h> + +#include "avtp.h" +#include "avtp_aaf.h" + +#define STREAM_ID 0xAABBCCDDEEFF0001 +#define SAMPLE_SIZE 2 /* Sample size in bytes. */ +#define NUM_CHANNELS 2 +#define DATA_LEN (SAMPLE_SIZE * NUM_CHANNELS) +#define PDU_SIZE (sizeof(struct avtp_stream_pdu) + DATA_LEN) +#define NSEC_PER_SEC 1000000000ULL + +struct sample_entry { + STAILQ_ENTRY(sample_entry) entries; + + struct timespec tspec; + uint8_t pcm_sample[DATA_LEN]; +}; + +static STAILQ_HEAD(sample_queue, sample_entry) samples; +static char ifname[IFNAMSIZ]; +static uint8_t macaddr[ETH_ALEN]; +static uint8_t expected_seq; + +static struct argp_option options[] = { + {"dst-addr", 'd', "MACADDR", 0, "Stream Destination MAC address" }, + {"ifname", 'i', "IFNAME", 0, "Network Interface" }, + { 0 } +}; + +static error_t parser(int key, char *arg, struct argp_state *state) +{ + int res; + + switch (key) { + case 'd': + res = sscanf(arg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &macaddr[0], &macaddr[1], &macaddr[2], + &macaddr[3], &macaddr[4], &macaddr[5]); + if (res != 6) { + fprintf(stderr, "Invalid address\n"); + exit(EXIT_FAILURE); + } + + break; + case 'i': + strncpy(ifname, arg, sizeof(ifname) - 1); + break; + } + + return 0; +} + +static struct argp argp = { options, parser }; + +static int arm_timer(int fd, struct timespec *tspec) +{ + int res; + struct itimerspec timer_spec = { 0 }; + + timer_spec.it_value.tv_sec = tspec->tv_sec; + timer_spec.it_value.tv_nsec = tspec->tv_nsec; + + res = timerfd_settime(fd, TFD_TIMER_ABSTIME, &timer_spec, NULL); + if (res < 0) { + perror("Failed to set timer"); + return -1; + } + + return 0; +} + +/* Schedule 'pcm_sample' to be presented at time specified by 'tspec'. */ +static int schedule_sample(int fd, struct timespec *tspec, uint8_t *pcm_sample) +{ + struct sample_entry *entry; + + entry = malloc(sizeof(*entry)); + if (!entry) { + fprintf(stderr, "Failed to allocate memory\n"); + return -1; + } + + entry->tspec.tv_sec = tspec->tv_sec; + entry->tspec.tv_nsec = tspec->tv_nsec; + memcpy(entry->pcm_sample, pcm_sample, DATA_LEN); + + STAILQ_INSERT_TAIL(&samples, entry, entries); + + /* If this was the first entry inserted onto the queue, we need to arm + * the timer. + */ + if (STAILQ_FIRST(&samples) == entry) { + int res; + + res = arm_timer(fd, tspec); + if (res < 0) { + STAILQ_REMOVE(&samples, entry, sample_entry, entries); + free(entry); + return -1; + } + } + + return 0; +} + +static int present_sample(uint8_t *pcm_sample) +{ + ssize_t n; + + n = write(STDOUT_FILENO, pcm_sample, DATA_LEN); + if (n < 0 || n != DATA_LEN) { + perror("Failed to write()"); + return -1; + } + + return 0; +} + +static int setup_socket(void) +{ + int fd, res; + struct ifreq req; + struct packet_mreq mreq; + + struct sockaddr_ll sk_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_TSN), + }; + + fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_TSN)); + if (fd < 0) { + perror("Failed to open socket"); + return -1; + } + + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) { + perror("Failed to get interface index"); + goto err; + } + + sk_addr.sll_ifindex = req.ifr_ifindex; + + res = bind(fd, (struct sockaddr *) &sk_addr, sizeof(sk_addr)); + if (res < 0) { + perror("Couldn't bind() to interface"); + goto err; + } + + mreq.mr_ifindex = sk_addr.sll_ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + memcpy(&mreq.mr_address, macaddr, ETH_ALEN); + + res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(struct packet_mreq)); + if (res < 0) { + perror("Couldn't set PACKET_ADD_MEMBERSHIP"); + goto err; + } + + return fd; + +err: + close(fd); + return -1; +} + +static bool is_valid_packet(struct avtp_stream_pdu *pdu) +{ + struct avtp_common_pdu *common = (struct avtp_common_pdu *) pdu; + uint64_t val64; + uint32_t val32; + int res; + + res = avtp_pdu_get(common, AVTP_FIELD_SUBTYPE, &val32); + if (res < 0) { + fprintf(stderr, "Failed to get subtype field: %d\n", res); + return false; + } + if (val32 != AVTP_SUBTYPE_AAF) { + fprintf(stderr, "Subtype mismatch: expected %u, got %u\n", + AVTP_FIELD_SUBTYPE, val32); + return false; + } + + res = avtp_pdu_get(common, AVTP_FIELD_VERSION, &val32); + if (res < 0) { + fprintf(stderr, "Failed to get version field: %d\n", res); + return false; + } + if (val32 != 0) { + fprintf(stderr, "Version mismatch: expected %u, got %u\n", + 0, val32); + return false; + } + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_TV, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get tv field: %d\n", res); + return false; + } + if (val64 != 1) { + fprintf(stderr, "tv mismatch: expected %u, got %lu\n", + 1, val64); + return false; + } + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_SP, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get sp field: %d\n", res); + return false; + } + if (val64 != AVTP_AAF_PCM_SP_NORMAL) { + fprintf(stderr, "tv mismatch: expected %u, got %lu\n", + 1, val64); + return false; + } + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_STREAM_ID, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get stream ID field: %d\n", res); + return false; + } + if (val64 != STREAM_ID) { + fprintf(stderr, "Stream ID mismatch: expected %lu, got %lu\n", + STREAM_ID, val64); + return false; + } + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_SEQ_NUM, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get sequence num field: %d\n", res); + return false; + } + + if (val64 != expected_seq) { + /* If we have a sequence number mismatch, we simply log the + * issue and continue to process the packet. We don't want to + * invalidate it since it is a valid packet after all. + */ + fprintf(stderr, "Sequence number mismatch: expected %u, got %lu\n", + expected_seq, val64); + expected_seq = val64; + } + + expected_seq++; + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_FORMAT, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get format field: %d\n", res); + return false; + } + if (val64 != AVTP_AAF_FORMAT_INT_16BIT) { + fprintf(stderr, "Format mismatch: expected %u, got %lu\n", + AVTP_AAF_FORMAT_INT_16BIT, val64); + return false; + } + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_NSR, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get sample rate field: %d\n", res); + return false; + } + if (val64 != AVTP_AAF_PCM_NSR_48KHZ) { + fprintf(stderr, "Sample rate mismatch: expected %u, got %lu", + AVTP_AAF_PCM_NSR_48KHZ, val64); + return false; + } + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get channels field: %d\n", res); + return false; + } + if (val64 != NUM_CHANNELS) { + fprintf(stderr, "Channels mismatch: expected %u, got %lu\n", + NUM_CHANNELS, val64); + return false; + } + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_BIT_DEPTH, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get depth field: %d\n", res); + return false; + } + if (val64 != 16) { + fprintf(stderr, "Depth mismatch: expected %u, got %lu\n", + 16, val64); + return false; + } + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, &val64); + if (res < 0) { + fprintf(stderr, "Failed to get data_len field: %d\n", res); + return false; + } + if (val64 != DATA_LEN) { + fprintf(stderr, "Data len mismatch: expected %u, got %lu\n", + DATA_LEN, val64); + return false; + } + + return true; +} + +static int get_presentation_time(struct avtp_stream_pdu *pdu, + struct timespec *tspec) +{ + int res; + uint64_t avtp_time, ptime, now; + + res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_TIMESTAMP, &avtp_time); + if (res < 0) { + fprintf(stderr, "Failed to get AVTP time from PDU\n"); + return -1; + } + + res = clock_gettime(CLOCK_REALTIME, tspec); + if (res < 0) { + perror("Failed to get time from PHC"); + return -1; + } + + now = (tspec->tv_sec * NSEC_PER_SEC) + tspec->tv_nsec; + + /* The avtp_timestamp within AAF packet is the lower part (32 + * less-significant bits) from presentation time calculated by the + * talker. + */ + ptime = (now & 0xFFFFFFFF00000000ULL) | avtp_time; + + /* If 'ptime' is less than the 'now', it means the higher part + * from 'ptime' needs to be incremented by 1 in order to recover the + * presentation time set by the talker. + */ + if (ptime < now) + ptime += (1ULL << 32); + + tspec->tv_sec = ptime / NSEC_PER_SEC; + tspec->tv_nsec = ptime % NSEC_PER_SEC; + + return 0; +} + +static int new_packet(int sk_fd, int timer_fd) +{ + int res; + ssize_t n; + struct timespec tspec; + struct avtp_stream_pdu *pdu = alloca(PDU_SIZE); + + memset(pdu, 0, PDU_SIZE); + + n = recv(sk_fd, pdu, PDU_SIZE, 0); + if (n < 0 || n != PDU_SIZE) { + perror("Failed to receive data"); + return -1; + } + + if (!is_valid_packet(pdu)) { + fprintf(stderr, "Dropping packet\n"); + return 0; + } + + res = get_presentation_time(pdu, &tspec); + if (res < 0) + return -1; + + res = schedule_sample(timer_fd, &tspec, pdu->avtp_payload); + if (res < 0) + return -1; + + return 0; +} + +static int timeout(int fd) +{ + int res; + ssize_t n; + uint64_t expirations; + struct sample_entry *entry; + + n = read(fd, &expirations, sizeof(uint64_t)); + if (n < 0) { + perror("Failed to read timerfd"); + return -1; + } + + assert(expirations == 1); + + entry = STAILQ_FIRST(&samples); + assert(entry != NULL); + + res = present_sample(entry->pcm_sample); + if (res < 0) + return -1; + + STAILQ_REMOVE_HEAD(&samples, entries); + free(entry); + + if (!STAILQ_EMPTY(&samples)) { + entry = STAILQ_FIRST(&samples); + + res = arm_timer(fd, &entry->tspec); + if (res < 0) + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + int sk_fd, timer_fd, res; + struct pollfd fds[2]; + + argp_parse(&argp, argc, argv, 0, NULL, NULL); + + STAILQ_INIT(&samples); + + sk_fd = setup_socket(); + if (sk_fd < 0) + return 1; + + timer_fd = timerfd_create(CLOCK_REALTIME, 0); + if (timer_fd < 0) { + close(sk_fd); + return 1; + } + + fds[0].fd = sk_fd; + fds[0].events = POLLIN; + fds[1].fd = timer_fd; + fds[1].events = POLLIN; + + while (1) { + res = poll(fds, 2, -1); + if (res < 0) { + perror("Failed to poll() fds"); + goto err; + } + + if (fds[0].revents & POLLIN) { + res = new_packet(sk_fd, timer_fd); + if (res < 0) + goto err; + } + + if (fds[1].revents & POLLIN) { + res = timeout(timer_fd); + if (res < 0) + goto err; + } + } + + return 0; + +err: + close(sk_fd); + close(timer_fd); + return 1; +} diff --git a/lib/libavtp/examples/aaf-talker.c b/lib/libavtp/examples/aaf-talker.c new file mode 100644 index 00000000..37ae1dfb --- /dev/null +++ b/lib/libavtp/examples/aaf-talker.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* AAF Talker example. + * + * This example implements a very simple AAF talker application which reads + * a PCM stream from stdin, creates AAF packets and transmit them via the + * network. + * + * For simplicity, the example supports only one set of PCM parameters: + * - Sample format: 16-bit little endian + * - Sample rate: 48 kHz + * - Number of channels: 2 (stereo) + * + * TSN stream parameters (e.g. destination mac address, traffic priority) are + * passed via command-line arguments. Run 'aaf-talker --help' for more + * information. + * + * In order to have this example working properly, make sure you have + * configured FQTSS feature from your NIC according (for further information + * see tc-cbs(8)). Also, this example relies on system clock to set the AVTP + * timestamp so make sure it is synchronized with the PTP Hardware Clock (PHC) + * from your NIC and that the PHC is synchronized with the network clock. For + * further information see ptp4l(8) and phc2sys(8). + * + * The easiest way to use this example is combining it with 'arecord' tool + * provided by alsa-utils. 'arecord' reads the PCM stream from a capture ALSA + * device (e.g. your microphone) and writes it to stdout. So to stream Audio + * captured from your mic to a TSN network you should do something like this: + * + * $ arecord -f dat -t raw -D <capture-device> | aaf-talker <args> + */ + +#include <alloca.h> +#include <argp.h> +#include <arpa/inet.h> +#include <linux/if.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <time.h> +#include <unistd.h> + +#include "avtp.h" +#include "avtp_aaf.h" + +#define STREAM_ID 0xAABBCCDDEEFF0001 +#define SAMPLE_SIZE 2 /* Sample size in bytes. */ +#define NUM_CHANNELS 2 +#define DATA_LEN (SAMPLE_SIZE * NUM_CHANNELS) +#define PDU_SIZE (sizeof(struct avtp_stream_pdu) + DATA_LEN) +#define NSEC_PER_SEC 1000000000ULL +#define NSEC_PER_MSEC 1000000ULL + +static char ifname[IFNAMSIZ]; +static uint8_t macaddr[ETH_ALEN]; +static int priority = -1; +static int max_transit_time; + +static struct argp_option options[] = { + {"dst-addr", 'd', "MACADDR", 0, "Stream Destination MAC address" }, + {"ifname", 'i', "IFNAME", 0, "Network Interface" }, + {"max-transit-time", 'm', "MSEC", 0, "Maximum Transit Time in ms" }, + {"prio", 'p', "NUM", 0, "SO_PRIORITY to be set in socket" }, + { 0 } +}; + +static error_t parser(int key, char *arg, struct argp_state *state) +{ + int res; + + switch (key) { + case 'd': + res = sscanf(arg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &macaddr[0], &macaddr[1], &macaddr[2], + &macaddr[3], &macaddr[4], &macaddr[5]); + if (res != 6) { + fprintf(stderr, "Invalid address\n"); + exit(EXIT_FAILURE); + } + + break; + case 'i': + strncpy(ifname, arg, sizeof(ifname) - 1); + break; + case 'm': + max_transit_time = atoi(arg); + break; + case 'p': + priority = atoi(arg); + break; + } + + return 0; +} + +static struct argp argp = { options, parser }; + +static int setup_socket(void) +{ + int fd, res; + + fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_TSN)); + if (fd < 0) { + perror("Failed to open socket"); + return -1; + } + + if (priority != -1) { + res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, + sizeof(priority)); + if (res < 0) { + perror("Failed to set priority"); + goto err; + } + + } + + return fd; + +err: + close(fd); + return -1; +} + +static int calculate_avtp_time(uint32_t *avtp_time) +{ + int res; + struct timespec tspec; + uint64_t ptime; + + res = clock_gettime(CLOCK_REALTIME, &tspec); + if (res < 0) { + perror("Failed to get time"); + return -1; + } + + ptime = (tspec.tv_sec * NSEC_PER_SEC) + + (max_transit_time * NSEC_PER_MSEC) + tspec.tv_nsec; + + *avtp_time = ptime % (1ULL << 32); + + return 0; +} + +static int init_pdu(struct avtp_stream_pdu *pdu) +{ + int res; + + res = avtp_aaf_pdu_init(pdu); + if (res < 0) + return -1; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_TV, 1); + if (res < 0) + return -1; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_STREAM_ID, STREAM_ID); + if (res < 0) + return -1; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_FORMAT, + AVTP_AAF_FORMAT_INT_16BIT); + if (res < 0) + return -1; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_NSR, + AVTP_AAF_PCM_NSR_48KHZ); + if (res < 0) + return -1; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, + NUM_CHANNELS); + if (res < 0) + return -1; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_BIT_DEPTH, 16); + if (res < 0) + return -1; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, DATA_LEN); + if (res < 0) + return -1; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_SP, AVTP_AAF_PCM_SP_NORMAL); + if (res < 0) + return -1; + + return 0; +} + +int main(int argc, char *argv[]) +{ + int fd, res; + struct ifreq req; + struct sockaddr_ll sk_addr; + struct avtp_stream_pdu *pdu = alloca(PDU_SIZE); + uint8_t seq_num = 0; + + argp_parse(&argp, argc, argv, 0, NULL, NULL); + + fd = setup_socket(); + if (fd < 0) + return 1; + + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) { + perror("Failed to get interface index"); + goto err; + } + + sk_addr.sll_family = AF_PACKET; + sk_addr.sll_protocol = htons(ETH_P_TSN); + sk_addr.sll_halen = ETH_ALEN; + sk_addr.sll_ifindex = req.ifr_ifindex; + memcpy(&sk_addr.sll_addr, macaddr, ETH_ALEN); + + res = init_pdu(pdu); + if (res < 0) + goto err; + + while (1) { + ssize_t n; + uint32_t avtp_time; + + memset(pdu->avtp_payload, 0, DATA_LEN); + + n = read(STDIN_FILENO, pdu->avtp_payload, DATA_LEN); + if (n == 0) + break; + + if (n != DATA_LEN) { + fprintf(stderr, "read %zd bytes, expected %d\n", + n, DATA_LEN); + } + + res = calculate_avtp_time(&avtp_time); + if (res < 0) { + fprintf(stderr, "Failed to calculate avtp time\n"); + goto err; + } + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_TIMESTAMP, + avtp_time); + if (res < 0) + goto err; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_SEQ_NUM, seq_num++); + if (res < 0) + goto err; + + n = sendto(fd, pdu, PDU_SIZE, 0, + (struct sockaddr *) &sk_addr, sizeof(sk_addr)); + if (n < 0) { + perror("Failed to send data"); + goto err; + } + + if (n != PDU_SIZE) { + fprintf(stderr, "wrote %zd bytes, expected %zd\n", + n, PDU_SIZE); + } + } + + close(fd); + return 0; + +err: + close(fd); + return 1; +} diff --git a/lib/libavtp/include/avtp.h b/lib/libavtp/include/avtp.h new file mode 100644 index 00000000..c999c1eb --- /dev/null +++ b/lib/libavtp/include/avtp.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <errno.h> +#include <stdint.h> + +/* AVTP subtypes values. For further information refer to section 4.4.3.2 from + * IEEE 1722-2016 spec. + */ +#define AVTP_SUBTYPE_61883_IIDC 0x00 +#define AVTP_SUBTYPE_MMA_STREAM 0x01 +#define AVTP_SUBTYPE_AAF 0x02 +#define AVTP_SUBTYPE_CVF 0x03 +#define AVTP_SUBTYPE_CRF 0x04 +#define AVTP_SUBTYPE_TSCF 0x05 +#define AVTP_SUBTYPE_SVF 0x06 +#define AVTP_SUBTYPE_RVF 0x07 +#define AVTP_SUBTYPE_AEF_CONTINUOUS 0x6E +#define AVTP_SUBTYPE_VSF_STREAM 0x6F +#define AVTP_SUBTYPE_EF_STREAM 0x7F +#define AVTP_SUBTYPE_NTSCF 0x82 +#define AVTP_SUBTYPE_ESCF 0xEC +#define AVTP_SUBTYPE_EECF 0xED +#define AVTP_SUBTYPE_AEF_DISCRETE 0xEE +#define AVTP_SUBTYPE_ADP 0xFA +#define AVTP_SUBTYPE_AECP 0xFB +#define AVTP_SUBTYPE_ACMP 0xFC +#define AVTP_SUBTYPE_MAAP 0xFE +#define AVTP_SUBTYPE_EF_CONTROL 0xFF + +/* XXX: Fields from PDU structs should not be read or written directly since + * they are encoded in Network order which may be different from the Host + * order (see section 3.4.1 from IEEE 1722-2016 spec for further information). + * + * Any read or write operation with PDU structs should be done via getter and + * setter APIs which handle byte order conversion. + */ +struct avtp_common_pdu { + uint32_t subtype_data; + uint8_t pdu_specific[0]; +} __attribute__ ((__packed__)); + +struct avtp_stream_pdu { + uint32_t subtype_data; + uint64_t stream_id; + uint32_t avtp_time; + uint32_t format_specific; + uint32_t packet_info; + uint8_t avtp_payload[0]; +} __attribute__ ((__packed__)); + +enum avtp_field { + AVTP_FIELD_SUBTYPE, + AVTP_FIELD_VERSION, + AVTP_FIELD_MAX, +}; + +/* Get value from Common AVTPDU field. + * @pdu: Pointer to PDU struct. + * @field: PDU field to be retrieved. + * @val: Pointer to variable which the retrieved value should be saved. + * + * Returns: + * 0: Success. + * -EINVAL: If any argument is invalid. + */ +int avtp_pdu_get(const struct avtp_common_pdu *pdu, enum avtp_field field, + uint32_t *val); + +/* Set value from Common AVTPDU field. + * @pdu: Pointer to PDU struct. + * @field: PDU field to be set. + * @val: Value to be set. + * + * Returns: + * 0: Success. + * -EINVAL: If any argument is invalid. + */ +int avtp_pdu_set(struct avtp_common_pdu *pdu, enum avtp_field field, + uint32_t val); diff --git a/lib/libavtp/include/avtp_aaf.h b/lib/libavtp/include/avtp_aaf.h new file mode 100644 index 00000000..80b7be14 --- /dev/null +++ b/lib/libavtp/include/avtp_aaf.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <errno.h> +#include <stdint.h> + +/* AAF PCM 'format' field values. */ +#define AVTP_AAF_FORMAT_USER 0x00 +#define AVTP_AAF_FORMAT_FLOAT_32BIT 0x01 +#define AVTP_AAF_FORMAT_INT_32BIT 0x02 +#define AVTP_AAF_FORMAT_INT_24BIT 0x03 +#define AVTP_AAF_FORMAT_INT_16BIT 0x04 +#define AVTP_AAF_FORMAT_AES3_32BIT 0x05 + +/* AAF PCM 'nsr' (nominal sample rate) field values. */ +#define AVTP_AAF_PCM_NSR_USER 0x00 +#define AVTP_AAF_PCM_NSR_8KHZ 0x01 +#define AVTP_AAF_PCM_NSR_16KHZ 0x02 +#define AVTP_AAF_PCM_NSR_32KHZ 0x03 +#define AVTP_AAF_PCM_NSR_44_1KHZ 0x04 +#define AVTP_AAF_PCM_NSR_48KHZ 0x05 +#define AVTP_AAF_PCM_NSR_88_2KHZ 0x06 +#define AVTP_AAF_PCM_NSR_96KHZ 0x07 +#define AVTP_AAF_PCM_NSR_176_4KHZ 0x08 +#define AVTP_AAF_PCM_NSR_192KHZ 0x09 +#define AVTP_AAF_PCM_NSR_24KHZ 0x0A + +/* AAF PCM 'sp' (sparse timestamp) field values. */ +#define AVTP_AAF_PCM_SP_NORMAL 0x00 +#define AVTP_AAF_PCM_SP_SPARSE 0x01 + +enum avtp_aaf_field { + AVTP_AAF_FIELD_SV, + AVTP_AAF_FIELD_MR, + AVTP_AAF_FIELD_TV, + AVTP_AAF_FIELD_SEQ_NUM, + AVTP_AAF_FIELD_TU, + AVTP_AAF_FIELD_STREAM_ID, + AVTP_AAF_FIELD_TIMESTAMP, + AVTP_AAF_FIELD_FORMAT, + AVTP_AAF_FIELD_NSR, + AVTP_AAF_FIELD_CHAN_PER_FRAME, + AVTP_AAF_FIELD_BIT_DEPTH, + AVTP_AAF_FIELD_STREAM_DATA_LEN, + AVTP_AAF_FIELD_SP, + AVTP_AAF_FIELD_EVT, + AVTP_AAF_FIELD_MAX, +}; + +/* Get value from AAF AVTPDU field. + * @pdu: Pointer to PDU struct. + * @field: PDU field to be retrieved. + * @val: Pointer to variable which the retrieved value should be saved. + * + * Returns: + * 0: Success. + * -EINVAL: If any argument is invalid. + */ +int avtp_aaf_pdu_get(const struct avtp_stream_pdu *pdu, + enum avtp_aaf_field field, uint64_t *val); + +/* Set value from AAF AVTPDU field. + * @pdu: Pointer to PDU struct. + * @field: PDU field to be set. + * @val: Value to be set. + * + * Returns: + * 0: Success. + * -EINVAL: If any argument is invalid. + */ +int avtp_aaf_pdu_set(struct avtp_stream_pdu *pdu, enum avtp_aaf_field field, + uint64_t val); + +/* Initialize AAF AVTPDU. All AVTPDU fields are initialized with zero except + * 'subtype' (which is set to AVTP_SUBTYPE_AAF) and 'sv' (which is set to 1). + * @pdu: Pointer to PDU struct. + * + * Return values: + * 0: Success. + * -EINVAL: If any argument is invalid. + */ +int avtp_aaf_pdu_init(struct avtp_stream_pdu *pdu); diff --git a/lib/libavtp/meson.build b/lib/libavtp/meson.build new file mode 100644 index 00000000..e12b56b5 --- /dev/null +++ b/lib/libavtp/meson.build @@ -0,0 +1,67 @@ +project( + 'libavtp', + 'c', + version: '0.1', + license: 'BSD-3-Clause', +) + +avtp_lib = library( + 'avtp', + [ + 'src/avtp.c', + 'src/avtp_aaf.c', + ], + include_directories: include_directories('include'), + install: true, +) + +install_headers( + 'include/avtp.h', + 'include/avtp_aaf.h', +) + +pkg = import('pkgconfig') +pkg.generate( + name: 'avtp', + description: 'AVTP packetization library', + version: '0.1', + url: 'github.com/AVnu/OpenAvnu', + libraries: avtp_lib, +) + +test_avtp = executable( + 'test-avtp', + 'unit/test-avtp.c', + include_directories: include_directories('include'), + link_with: avtp_lib, + dependencies: dependency('cmocka'), + build_by_default: false, +) + +test_aaf = executable( + 'test-aaf', + 'unit/test-aaf.c', + include_directories: include_directories('include'), + link_with: avtp_lib, + dependencies: dependency('cmocka'), + build_by_default: false, +) + +test('AVTP API', test_avtp) +test('AAF API', test_aaf) + +executable( + 'aaf-talker', + 'examples/aaf-talker.c', + include_directories: include_directories('include'), + link_with: avtp_lib, + build_by_default: false, +) + +executable( + 'aaf-listener', + 'examples/aaf-listener.c', + include_directories: include_directories('include'), + link_with: avtp_lib, + build_by_default: false, +) diff --git a/lib/libavtp/src/avtp.c b/lib/libavtp/src/avtp.c new file mode 100644 index 00000000..e3132457 --- /dev/null +++ b/lib/libavtp/src/avtp.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <arpa/inet.h> +#include <stddef.h> + +#include "avtp.h" +#include "util.h" + +#define SHIFT_SUBTYPE (31 - 7) +#define SHIFT_VERSION (31 - 11) + +#define MASK_SUBTYPE (BITMASK(8) << SHIFT_SUBTYPE) +#define MASK_VERSION (BITMASK(3) << SHIFT_VERSION) + +int avtp_pdu_get(const struct avtp_common_pdu *pdu, enum avtp_field field, + uint32_t *val) +{ + uint32_t bitmap, mask; + uint8_t shift; + + if (!pdu || !val) + return -EINVAL; + + switch (field) { + case AVTP_FIELD_SUBTYPE: + mask = MASK_SUBTYPE; + shift = SHIFT_SUBTYPE; + break; + case AVTP_FIELD_VERSION: + mask = MASK_VERSION; + shift = SHIFT_VERSION; + break; + default: + return -EINVAL; + } + + bitmap = ntohl(pdu->subtype_data); + + *val = BITMAP_GET_VALUE(bitmap, mask, shift); + + return 0; +} + +int avtp_pdu_set(struct avtp_common_pdu *pdu, enum avtp_field field, + uint32_t value) +{ + uint32_t bitmap, mask; + uint8_t shift; + + if (!pdu) + return -EINVAL; + + switch (field) { + case AVTP_FIELD_SUBTYPE: + mask = MASK_SUBTYPE; + shift = SHIFT_SUBTYPE; + break; + case AVTP_FIELD_VERSION: + mask = MASK_VERSION; + shift = SHIFT_VERSION; + break; + default: + return -EINVAL; + } + + bitmap = ntohl(pdu->subtype_data); + + BITMAP_SET_VALUE(bitmap, value, mask, shift); + + pdu->subtype_data = htonl(bitmap); + + return 0; +} diff --git a/lib/libavtp/src/avtp_aaf.c b/lib/libavtp/src/avtp_aaf.c new file mode 100644 index 00000000..ce84a2c7 --- /dev/null +++ b/lib/libavtp/src/avtp_aaf.c @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <arpa/inet.h> +#include <endian.h> +#include <string.h> + +#include "avtp.h" +#include "avtp_aaf.h" +#include "util.h" + +#define SHIFT_SV (31 - 8) +#define SHIFT_MR (31 - 12) +#define SHIFT_TV (31 - 15) +#define SHIFT_SEQ_NUM (31 - 23) +#define SHIFT_FORMAT (31 - 7) +#define SHIFT_NSR (31 - 11) +#define SHIFT_CHAN_PER_FRAME (31 - 23) +#define SHIFT_STREAM_DATA_LEN (31 - 15) +#define SHIFT_SP (31 - 19) +#define SHIFT_EVT (31 - 23) + +#define MASK_SV (BITMASK(1) << SHIFT_SV) +#define MASK_MR (BITMASK(1) << SHIFT_MR) +#define MASK_TV (BITMASK(1) << SHIFT_TV) +#define MASK_SEQ_NUM (BITMASK(8) << SHIFT_SEQ_NUM) +#define MASK_TU (BITMASK(1)) +#define MASK_FORMAT (BITMASK(8) << SHIFT_FORMAT) +#define MASK_NSR (BITMASK(4) << SHIFT_NSR) +#define MASK_CHAN_PER_FRAME (BITMASK(10) << SHIFT_CHAN_PER_FRAME) +#define MASK_BIT_DEPTH (BITMASK(8)) +#define MASK_STREAM_DATA_LEN (BITMASK(16) << SHIFT_STREAM_DATA_LEN) +#define MASK_SP (BITMASK(1) << SHIFT_SP) +#define MASK_EVT (BITMASK(4) << SHIFT_EVT) + +static int get_field_value(const struct avtp_stream_pdu *pdu, + enum avtp_aaf_field field, uint64_t *val) +{ + uint32_t bitmap, mask; + uint8_t shift; + + switch (field) { + case AVTP_AAF_FIELD_SV: + mask = MASK_SV; + shift = SHIFT_SV; + bitmap = ntohl(pdu->subtype_data); + break; + case AVTP_AAF_FIELD_MR: + mask = MASK_MR; + shift = SHIFT_MR; + bitmap = ntohl(pdu->subtype_data); + break; + case AVTP_AAF_FIELD_TV: + mask = MASK_TV; + shift = SHIFT_TV; + bitmap = ntohl(pdu->subtype_data); + break; + case AVTP_AAF_FIELD_SEQ_NUM: + mask = MASK_SEQ_NUM; + shift = SHIFT_SEQ_NUM; + bitmap = ntohl(pdu->subtype_data); + break; + case AVTP_AAF_FIELD_TU: + mask = MASK_TU; + shift = 0; + bitmap = ntohl(pdu->subtype_data); + break; + case AVTP_AAF_FIELD_FORMAT: + mask = MASK_FORMAT; + shift = SHIFT_FORMAT; + bitmap = ntohl(pdu->format_specific); + break; + case AVTP_AAF_FIELD_NSR: + mask = MASK_NSR; + shift = SHIFT_NSR; + bitmap = ntohl(pdu->format_specific); + break; + case AVTP_AAF_FIELD_CHAN_PER_FRAME: + mask = MASK_CHAN_PER_FRAME; + shift = SHIFT_CHAN_PER_FRAME; + bitmap = ntohl(pdu->format_specific); + break; + case AVTP_AAF_FIELD_BIT_DEPTH: + mask = MASK_BIT_DEPTH; + shift = 0; + bitmap = ntohl(pdu->format_specific); + break; + case AVTP_AAF_FIELD_STREAM_DATA_LEN: + mask = MASK_STREAM_DATA_LEN; + shift = SHIFT_STREAM_DATA_LEN; + bitmap = ntohl(pdu->packet_info); + break; + case AVTP_AAF_FIELD_SP: + mask = MASK_SP; + shift = SHIFT_SP; + bitmap = ntohl(pdu->packet_info); + break; + case AVTP_AAF_FIELD_EVT: + mask = MASK_EVT; + shift = SHIFT_EVT; + bitmap = ntohl(pdu->packet_info); + break; + default: + return -EINVAL; + } + + *val = BITMAP_GET_VALUE(bitmap, mask, shift); + + return 0; +} + +int avtp_aaf_pdu_get(const struct avtp_stream_pdu *pdu, + enum avtp_aaf_field field, uint64_t *val) +{ + int res; + + if (!pdu || !val) + return -EINVAL; + + switch (field) { + case AVTP_AAF_FIELD_SV: + case AVTP_AAF_FIELD_MR: + case AVTP_AAF_FIELD_TV: + case AVTP_AAF_FIELD_SEQ_NUM: + case AVTP_AAF_FIELD_TU: + case AVTP_AAF_FIELD_FORMAT: + case AVTP_AAF_FIELD_NSR: + case AVTP_AAF_FIELD_CHAN_PER_FRAME: + case AVTP_AAF_FIELD_BIT_DEPTH: + case AVTP_AAF_FIELD_STREAM_DATA_LEN: + case AVTP_AAF_FIELD_SP: + case AVTP_AAF_FIELD_EVT: + res = get_field_value(pdu, field, val); + break; + case AVTP_AAF_FIELD_TIMESTAMP: + *val = ntohl(pdu->avtp_time); + res = 0; + break; + case AVTP_AAF_FIELD_STREAM_ID: + *val = be64toh(pdu->stream_id); + res = 0; + break; + default: + res = -EINVAL; + break; + } + + return res; +} + +static int set_field_value(struct avtp_stream_pdu *pdu, + enum avtp_aaf_field field, uint32_t val) +{ + uint32_t *ptr, bitmap, mask; + uint8_t shift; + + switch (field) { + case AVTP_AAF_FIELD_SV: + mask = MASK_SV; + shift = SHIFT_SV; + ptr = &pdu->subtype_data; + break; + case AVTP_AAF_FIELD_MR: + mask = MASK_MR; + shift = SHIFT_MR; + ptr = &pdu->subtype_data; + break; + case AVTP_AAF_FIELD_TV: + mask = MASK_TV; + shift = SHIFT_TV; + ptr = &pdu->subtype_data; + break; + case AVTP_AAF_FIELD_SEQ_NUM: + mask = MASK_SEQ_NUM; + shift = SHIFT_SEQ_NUM; + ptr = &pdu->subtype_data; + break; + case AVTP_AAF_FIELD_TU: + mask = MASK_TU; + shift = 0; + ptr = &pdu->subtype_data; + break; + case AVTP_AAF_FIELD_FORMAT: + mask = MASK_FORMAT; + shift = SHIFT_FORMAT; + ptr = &pdu->format_specific; + break; + case AVTP_AAF_FIELD_NSR: + mask = MASK_NSR; + shift = SHIFT_NSR; + ptr = &pdu->format_specific; + break; + case AVTP_AAF_FIELD_CHAN_PER_FRAME: + mask = MASK_CHAN_PER_FRAME; + shift = SHIFT_CHAN_PER_FRAME; + ptr = &pdu->format_specific; + break; + case AVTP_AAF_FIELD_BIT_DEPTH: + mask = MASK_BIT_DEPTH; + shift = 0; + ptr = &pdu->format_specific; + break; + case AVTP_AAF_FIELD_STREAM_DATA_LEN: + mask = MASK_STREAM_DATA_LEN; + shift = SHIFT_STREAM_DATA_LEN; + ptr = &pdu->packet_info; + break; + case AVTP_AAF_FIELD_SP: + mask = MASK_SP; + shift = SHIFT_SP; + ptr = &pdu->packet_info; + break; + case AVTP_AAF_FIELD_EVT: + mask = MASK_EVT; + shift = SHIFT_EVT; + ptr = &pdu->packet_info; + break; + default: + return -EINVAL; + } + + bitmap = ntohl(*ptr); + + BITMAP_SET_VALUE(bitmap, val, mask, shift); + + *ptr = htonl(bitmap); + + return 0; +} + +int avtp_aaf_pdu_set(struct avtp_stream_pdu *pdu, enum avtp_aaf_field field, + uint64_t val) +{ + int res; + + if (!pdu) + return -EINVAL; + + switch (field) { + case AVTP_AAF_FIELD_SV: + case AVTP_AAF_FIELD_MR: + case AVTP_AAF_FIELD_TV: + case AVTP_AAF_FIELD_SEQ_NUM: + case AVTP_AAF_FIELD_TU: + case AVTP_AAF_FIELD_FORMAT: + case AVTP_AAF_FIELD_NSR: + case AVTP_AAF_FIELD_CHAN_PER_FRAME: + case AVTP_AAF_FIELD_BIT_DEPTH: + case AVTP_AAF_FIELD_STREAM_DATA_LEN: + case AVTP_AAF_FIELD_SP: + case AVTP_AAF_FIELD_EVT: + res = set_field_value(pdu, field, val); + break; + case AVTP_AAF_FIELD_TIMESTAMP: + pdu->avtp_time = htonl(val); + res = 0; + break; + case AVTP_AAF_FIELD_STREAM_ID: + pdu->stream_id = htobe64(val); + res = 0; + break; + default: + res = -EINVAL; + } + + return res; +} + +int avtp_aaf_pdu_init(struct avtp_stream_pdu *pdu) +{ + int res; + + if (!pdu) + return -EINVAL; + + memset(pdu, 0, sizeof(struct avtp_stream_pdu)); + + res = avtp_pdu_set((struct avtp_common_pdu *) pdu, AVTP_FIELD_SUBTYPE, + AVTP_SUBTYPE_AAF); + if (res < 0) + return res; + + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_SV, 1); + if (res < 0) + return res; + + return 0; +}; diff --git a/lib/libavtp/src/util.h b/lib/libavtp/src/util.h new file mode 100644 index 00000000..2a6ec3b0 --- /dev/null +++ b/lib/libavtp/src/util.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#define BIT(n) (1UL << n) + +#define BITMASK(len) (BIT(len) - 1) + +/* Get value from the bits within 'bitmap' represented by 'mask'. The 'mask' + * parameter must be a continuous bit mask (e.g. 0b00111000). This macro + * doesn't work with non-continuous bit masks (e.g. 0b00101001). + */ +#define BITMAP_GET_VALUE(bitmap, mask, shift) \ + ((bitmap & mask) >> shift) + +/* Set the value 'val' in the 'bitmap' variable at the position represented by + * 'mask'. + */ +#define BITMAP_SET_VALUE(bitmap, val, mask, shift) \ + (bitmap = (bitmap & ~mask) | ((val << shift) & mask)) diff --git a/lib/libavtp/unit/test-aaf.c b/lib/libavtp/unit/test-aaf.c new file mode 100644 index 00000000..20a79cb9 --- /dev/null +++ b/lib/libavtp/unit/test-aaf.c @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <cmocka.h> +#include <arpa/inet.h> + +#include "avtp.h" +#include "avtp_aaf.h" + +static void aaf_get_field_null_pdu(void **state) +{ + int res; + uint64_t val = 1; + + res = avtp_aaf_pdu_get(NULL, AVTP_AAF_FIELD_SV, &val); + + assert_int_equal(res, -EINVAL); +} + +static void aaf_get_field_null_val(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_SV, NULL); + + assert_int_equal(res, -EINVAL); +} + +static void aaf_get_field_invalid_field(void **state) +{ + int res; + uint64_t val = 1; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_MAX, &val); + + assert_int_equal(res, -EINVAL); +} + +static void aaf_get_field_sv(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'sv' field to 1. */ + pdu.subtype_data = htonl(0x00800000); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_SV, &val); + + assert_int_equal(res, 0); + assert_true(val == 1); +} + +static void aaf_get_field_mr(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'mr' field to 1. */ + pdu.subtype_data = htonl(0x00080000); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_MR, &val); + + assert_int_equal(res, 0); + assert_true(val == 1); +} + +static void aaf_get_field_tv(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'tv' field to 1. */ + pdu.subtype_data = htonl(0x00010000); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_TV, &val); + + assert_int_equal(res, 0); + assert_true(val == 1); +} + +static void aaf_get_field_seq_num(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'sequence_num' field to 0x55. */ + pdu.subtype_data = htonl(0x00005500); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_SEQ_NUM, &val); + + assert_int_equal(res, 0); + assert_true(val == 0x55); +} + +static void aaf_get_field_tu(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'tu' field to 1. */ + pdu.subtype_data = htonl(0x00000001); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_TU, &val); + + assert_int_equal(res, 0); + assert_true(val == 1); +} + +static void aaf_get_field_stream_id(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'stream_id' field to 0xAABBCCDDEEFF0001. */ + pdu.stream_id = htobe64(0xAABBCCDDEEFF0001); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_STREAM_ID, &val); + + assert_int_equal(res, 0); + assert_true(val == 0xAABBCCDDEEFF0001); +} + +static void aaf_get_field_timestamp(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'avtp_timestamp' field to 0x80C0FFEE. */ + pdu.avtp_time = htonl(0x80C0FFEE); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_TIMESTAMP, &val); + + assert_int_equal(res, 0); + assert_true(val == 0x80C0FFEE); +} + +static void aaf_get_field_format(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'format' field to AVTP_AAF_FORMAT_INT_16BIT. */ + pdu.format_specific = htonl(0x04000000); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_FORMAT, &val); + + assert_int_equal(res, 0); + assert_true(val == AVTP_AAF_FORMAT_INT_16BIT); +} + +static void aaf_get_field_nsr(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'nsr' field to AVTP_AAF_PCM_NSR_48KHZ. */ + pdu.format_specific = htonl(0x00500000); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_NSR, &val); + + assert_int_equal(res, 0); + assert_true(val == AVTP_AAF_PCM_NSR_48KHZ); +} + +static void aaf_get_field_chan(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'channels_per_frame' field to 0x2AA. */ + pdu.format_specific = htonl(0x0002AA00); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, &val); + + assert_int_equal(res, 0); + assert_true(val == 0x2AA); +} + +static void aaf_get_field_depth(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'bit_depth' field to 0xA5. */ + pdu.format_specific = htonl(0x000000A5); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_BIT_DEPTH, &val); + + assert_int_equal(res, 0); + assert_true(val == 0xA5); +} + +static void aaf_get_field_data_len(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'stream_data_length' field to 0xAAAA. */ + pdu.packet_info = htonl(0xAAAA0000); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, &val); + + assert_int_equal(res, 0); + assert_true(val == 0xAAAA); +} + +static void aaf_get_field_sp(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'sp' field to AVTP_AAF_PCM_SP_SPARSE. */ + pdu.packet_info = htonl(0x00001000); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_SP, &val); + + assert_int_equal(res, 0); + assert_true(val == AVTP_AAF_PCM_SP_SPARSE); +} + +static void aaf_get_field_evt(void **state) +{ + int res; + uint64_t val; + struct avtp_stream_pdu pdu = { 0 }; + + /* Set 'evt' field to 0xA. */ + pdu.packet_info = htonl(0x00000A00); + + res = avtp_aaf_pdu_get(&pdu, AVTP_AAF_FIELD_EVT, &val); + + assert_int_equal(res, 0); + assert_true(val == 0xA); +} + +static void aaf_set_field_null_pdu(void **state) +{ + int res; + + res = avtp_aaf_pdu_set(NULL, AVTP_AAF_FIELD_SV, 1); + + assert_int_equal(res, -EINVAL); +} + +static void aaf_set_field_invalid_field(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_MAX, 1); + + assert_int_equal(res, -EINVAL); +} + +static void aaf_set_field_sv(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_SV, 1); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.subtype_data) == 0x00800000); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_mr(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_MR, 1); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.subtype_data) == 0x00080000); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_tv(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_TV, 1); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.subtype_data) == 0x00010000); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_seq_num(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_SEQ_NUM, 0x55); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.subtype_data) == 0x00005500); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_tu(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_TU, 1); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.subtype_data) == 0x00000001); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_stream_id(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_STREAM_ID, + 0xAABBCCDDEEFF0001); + + assert_int_equal(res, 0); + assert_true(be64toh(pdu.stream_id) == 0xAABBCCDDEEFF0001); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_timestamp(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_TIMESTAMP, 0x80C0FFEE); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.avtp_time) == 0x80C0FFEE); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.format_specific) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_format(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_FORMAT, + AVTP_AAF_FORMAT_INT_16BIT); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.format_specific) == 0x04000000); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_nsr(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_NSR, + AVTP_AAF_PCM_NSR_48KHZ); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.format_specific) == 0x00500000); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_chan(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, 0x2AA); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.format_specific) == 0x0002AA00); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_depth(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_BIT_DEPTH, 0xA5); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.format_specific) == 0x000000A5); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.packet_info) == 0); +} + +static void aaf_set_field_data_len(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, 0xAAAA); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.packet_info) == 0xAAAA0000); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); +} + +static void aaf_set_field_sp(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_SP, 1); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.packet_info) == 0x00001000); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); +} + +static void aaf_set_field_evt(void **state) +{ + int res; + struct avtp_stream_pdu pdu = { 0 }; + + res = avtp_aaf_pdu_set(&pdu, AVTP_AAF_FIELD_EVT, 0xA); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.packet_info) == 0x00000A00); + assert_true(ntohl(pdu.subtype_data) == 0); + assert_true(ntohl(pdu.stream_id) == 0); + assert_true(ntohl(pdu.avtp_time) == 0); + assert_true(ntohl(pdu.format_specific) == 0); +} + +static void aaf_pdu_init_null_pdu(void **state) +{ + int res; + + res = avtp_aaf_pdu_init(NULL); + + assert_int_equal(res, -EINVAL); +} + +static void aaf_pdu_init(void **state) +{ + int res; + struct avtp_stream_pdu pdu; + + res = avtp_aaf_pdu_init(&pdu); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.subtype_data) == 0x02800000); + assert_true(pdu.stream_id == 0); + assert_true(pdu.avtp_time == 0); + assert_true(pdu.format_specific == 0); + assert_true(pdu.packet_info == 0); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(aaf_get_field_null_pdu), + cmocka_unit_test(aaf_get_field_null_val), + cmocka_unit_test(aaf_get_field_invalid_field), + cmocka_unit_test(aaf_get_field_sv), + cmocka_unit_test(aaf_get_field_mr), + cmocka_unit_test(aaf_get_field_tv), + cmocka_unit_test(aaf_get_field_seq_num), + cmocka_unit_test(aaf_get_field_tu), + cmocka_unit_test(aaf_get_field_stream_id), + cmocka_unit_test(aaf_get_field_timestamp), + cmocka_unit_test(aaf_get_field_format), + cmocka_unit_test(aaf_get_field_nsr), + cmocka_unit_test(aaf_get_field_chan), + cmocka_unit_test(aaf_get_field_depth), + cmocka_unit_test(aaf_get_field_data_len), + cmocka_unit_test(aaf_get_field_sp), + cmocka_unit_test(aaf_get_field_evt), + cmocka_unit_test(aaf_set_field_null_pdu), + cmocka_unit_test(aaf_set_field_invalid_field), + cmocka_unit_test(aaf_set_field_sv), + cmocka_unit_test(aaf_set_field_mr), + cmocka_unit_test(aaf_set_field_tv), + cmocka_unit_test(aaf_set_field_seq_num), + cmocka_unit_test(aaf_set_field_tu), + cmocka_unit_test(aaf_set_field_stream_id), + cmocka_unit_test(aaf_set_field_timestamp), + cmocka_unit_test(aaf_set_field_format), + cmocka_unit_test(aaf_set_field_nsr), + cmocka_unit_test(aaf_set_field_chan), + cmocka_unit_test(aaf_set_field_depth), + cmocka_unit_test(aaf_set_field_data_len), + cmocka_unit_test(aaf_set_field_sp), + cmocka_unit_test(aaf_set_field_evt), + cmocka_unit_test(aaf_pdu_init_null_pdu), + cmocka_unit_test(aaf_pdu_init), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/libavtp/unit/test-avtp.c b/lib/libavtp/unit/test-avtp.c new file mode 100644 index 00000000..ee056ee7 --- /dev/null +++ b/lib/libavtp/unit/test-avtp.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <cmocka.h> +#include <arpa/inet.h> + +#include "avtp.h" + +static void get_field_null_pdu(void **state) +{ + int res; + uint32_t val = AVTP_SUBTYPE_MAAP; + + res = avtp_pdu_get(NULL, AVTP_FIELD_SUBTYPE, &val); + + assert_int_equal(res, -EINVAL); +} + +static void get_field_null_val(void **state) +{ + int res; + struct avtp_common_pdu pdu = { 0 }; + + res = avtp_pdu_get(&pdu, AVTP_FIELD_SUBTYPE, NULL); + + assert_int_equal(res, -EINVAL); +} + +static void get_field_invalid_field(void **state) +{ + int res; + uint32_t val = AVTP_SUBTYPE_MAAP; + struct avtp_common_pdu pdu = { 0 }; + + res = avtp_pdu_get(&pdu, AVTP_FIELD_MAX, &val); + + assert_int_equal(res, -EINVAL); +} + +static void get_field_subtype(void **state) +{ + int res; + uint32_t val; + struct avtp_common_pdu pdu = { 0 }; + + /* Set 'subtype' field to 0xFE (AVTP_SUBTYPE_MAAP). */ + pdu.subtype_data = htonl(0xFE000000); + + res = avtp_pdu_get(&pdu, AVTP_FIELD_SUBTYPE, &val); + + assert_int_equal(res, 0); + assert_true(val == AVTP_SUBTYPE_MAAP); +} + +static void get_field_version(void **state) +{ + int res; + uint32_t val; + struct avtp_common_pdu pdu = { 0 }; + + /* Set 'version' field to 5. */ + pdu.subtype_data = htonl(0x00500000); + + res = avtp_pdu_get(&pdu, AVTP_FIELD_VERSION, &val); + + assert_int_equal(res, 0); + assert_true(val == 5); +} + +static void set_field_null_pdu(void **state) +{ + int res; + + res = avtp_pdu_set(NULL, AVTP_FIELD_SUBTYPE, AVTP_SUBTYPE_MAAP); + + assert_int_equal(res, -EINVAL); +} + +static void set_field_invalid_field(void **state) +{ + int res; + struct avtp_common_pdu pdu = { 0 }; + + res = avtp_pdu_set(&pdu, AVTP_FIELD_MAX, 1); + + assert_int_equal(res, -EINVAL); +} + +static void set_field_subtype(void **state) +{ + int res; + struct avtp_common_pdu pdu = { 0 }; + + res = avtp_pdu_set(&pdu, AVTP_FIELD_SUBTYPE, AVTP_SUBTYPE_MAAP); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.subtype_data) == 0xFE000000); +} + +static void set_field_version(void **state) +{ + int res; + struct avtp_common_pdu pdu = { 0 }; + + res = avtp_pdu_set(&pdu, AVTP_FIELD_VERSION, 5); + + assert_int_equal(res, 0); + assert_true(ntohl(pdu.subtype_data) == 0x00500000); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(get_field_null_pdu), + cmocka_unit_test(get_field_null_val), + cmocka_unit_test(get_field_invalid_field), + cmocka_unit_test(get_field_subtype), + cmocka_unit_test(get_field_version), + cmocka_unit_test(set_field_null_pdu), + cmocka_unit_test(set_field_invalid_field), + cmocka_unit_test(set_field_subtype), + cmocka_unit_test(set_field_version), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} @@ -1,5 +1,8 @@ #!/bin/bash set -ev + +ROOT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + make igb make lib make daemons_all @@ -17,3 +20,6 @@ mkdir build cd build cmake .. make doc +cd $ROOT_DIR +CFLAGS=-Wno-missing-braces meson lib/libavtp/ lib/libavtp/build +ninja -C lib/libavtp/build/ test aaf-talker aaf-listener |