/**
  * struct efx_ptp_rxfilter - Filter for PTP packets
  * @list: Node of the list where the filter is added
+ * @ether_type: Network protocol of the filter (ETHER_P_IP / ETHER_P_IPV6)
+ * @loc_port: UDP port of the filter (PTP_EVENT_PORT / PTP_GENERAL_PORT)
+ * @loc_host: IPv4/v6 address of the filter
  * @handle: Handle ID for the MCDI filters table
  */
 struct efx_ptp_rxfilter {
        struct list_head list;
+       __be16 ether_type;
+       __be16 loc_port;
+       __be32 loc_host[4];
        int handle;
 };
 
                           const struct timespec64 *e_ts);
 static int efx_phc_enable(struct ptp_clock_info *ptp,
                          struct ptp_clock_request *request, int on);
+static int efx_ptp_insert_unicast_filter(struct efx_nic *efx,
+                                        struct sk_buff *skb);
 
 bool efx_ptp_use_mac_tx_timestamps(struct efx_nic *efx)
 {
 
        tx_queue = efx_channel_get_tx_queue(ptp_data->channel, type);
        if (tx_queue && tx_queue->timestamping) {
+               skb_get(skb);
+
                /* This code invokes normal driver TX code which is always
                 * protected from softirqs when called from generic TX code,
                 * which in turn disables preemption. Look at __dev_queue_xmit
                local_bh_disable();
                efx_enqueue_skb(tx_queue, skb);
                local_bh_enable();
+
+               /* We need to add the filters after enqueuing the packet.
+                * Otherwise, there's high latency in sending back the
+                * timestamp, causing ptp4l timeouts
+                */
+               efx_ptp_insert_unicast_filter(efx, skb);
+               dev_consume_skb_any(skb);
        } else {
                WARN_ONCE(1, "PTP channel has no timestamped tx queue\n");
                dev_kfree_skb_any(skb);
 /* Transmit a PTP packet, via the MCDI interface, to the wire. */
 static void efx_ptp_xmit_skb_mc(struct efx_nic *efx, struct sk_buff *skb)
 {
+       MCDI_DECLARE_BUF(txtime, MC_CMD_PTP_OUT_TRANSMIT_LEN);
        struct efx_ptp_data *ptp_data = efx->ptp_data;
        struct skb_shared_hwtstamps timestamps;
-       int rc = -EIO;
-       MCDI_DECLARE_BUF(txtime, MC_CMD_PTP_OUT_TRANSMIT_LEN);
        size_t len;
+       int rc;
 
        MCDI_SET_DWORD(ptp_data->txbuf, PTP_IN_OP, MC_CMD_PTP_OP_TRANSMIT);
        MCDI_SET_DWORD(ptp_data->txbuf, PTP_IN_PERIPH_ID, 0);
 
        skb_tstamp_tx(skb, ×tamps);
 
-       rc = 0;
+       /* Add the filters after sending back the timestamp to avoid delaying it
+        * or ptp4l may timeout.
+        */
+       efx_ptp_insert_unicast_filter(efx, skb);
 
 fail:
        dev_kfree_skb_any(skb);
        local_bh_enable();
 }
 
+static bool efx_ptp_filter_exists(struct list_head *filter_list,
+                                 struct efx_filter_spec *spec)
+{
+       struct efx_ptp_rxfilter *rxfilter;
+
+       list_for_each_entry(rxfilter, filter_list, list) {
+               if (rxfilter->ether_type == spec->ether_type &&
+                   rxfilter->loc_port == spec->loc_port &&
+                   !memcmp(rxfilter->loc_host, spec->loc_host, sizeof(spec->loc_host)))
+                       return true;
+       }
+
+       return false;
+}
+
 static void efx_ptp_remove_filters(struct efx_nic *efx,
                                   struct list_head *filter_list)
 {
        struct efx_ptp_rxfilter *rxfilter;
        int rc;
 
+       if (efx_ptp_filter_exists(filter_list, spec))
+               return 0;
+
        rxfilter = kzalloc(sizeof(*rxfilter), GFP_KERNEL);
        if (!rxfilter)
                return -ENOMEM;
                goto fail;
 
        rxfilter->handle = rc;
+       rxfilter->ether_type = spec->ether_type;
+       rxfilter->loc_port = spec->loc_port;
+       memcpy(rxfilter->loc_host, spec->loc_host, sizeof(spec->loc_host));
        list_add(&rxfilter->list, filter_list);
 
        return 0;
        return rc;
 }
 
+static bool efx_ptp_valid_unicast_event_pkt(struct sk_buff *skb)
+{
+       if (skb->protocol == htons(ETH_P_IP)) {
+               return ip_hdr(skb)->daddr != htonl(PTP_ADDR_IPV4) &&
+                       ip_hdr(skb)->protocol == IPPROTO_UDP &&
+                       udp_hdr(skb)->source == htons(PTP_EVENT_PORT);
+       } else if (skb->protocol == htons(ETH_P_IPV6)) {
+               struct in6_addr mcast_addr = {{PTP_ADDR_IPV6}};
+
+               return !ipv6_addr_equal(&ipv6_hdr(skb)->daddr, &mcast_addr) &&
+                       ipv6_hdr(skb)->nexthdr == IPPROTO_UDP &&
+                       udp_hdr(skb)->source == htons(PTP_EVENT_PORT);
+       }
+       return false;
+}
+
+static int efx_ptp_insert_unicast_filter(struct efx_nic *efx,
+                                        struct sk_buff *skb)
+{
+       struct efx_ptp_data *ptp = efx->ptp_data;
+       int rc;
+
+       if (!efx_ptp_valid_unicast_event_pkt(skb))
+               return -EINVAL;
+
+       if (skb->protocol == htons(ETH_P_IP)) {
+               __be32 addr = ip_hdr(skb)->saddr;
+
+               rc = efx_ptp_insert_ipv4_filter(efx, &ptp->rxfilters_ucast,
+                                               addr, PTP_EVENT_PORT);
+               if (rc < 0)
+                       goto out;
+
+               rc = efx_ptp_insert_ipv4_filter(efx, &ptp->rxfilters_ucast,
+                                               addr, PTP_GENERAL_PORT);
+       } else if (efx_ptp_use_mac_tx_timestamps(efx)) {
+               /* IPv6 PTP only supported by devices with MAC hw timestamp */
+               struct in6_addr *addr = &ipv6_hdr(skb)->saddr;
+
+               rc = efx_ptp_insert_ipv6_filter(efx, &ptp->rxfilters_ucast,
+                                               addr, PTP_EVENT_PORT);
+               if (rc < 0)
+                       goto out;
+
+               rc = efx_ptp_insert_ipv6_filter(efx, &ptp->rxfilters_ucast,
+                                               addr, PTP_GENERAL_PORT);
+       } else {
+               return -EOPNOTSUPP;
+       }
+
+out:
+       return rc;
+}
+
 static int efx_ptp_start(struct efx_nic *efx)
 {
        struct efx_ptp_data *ptp = efx->ptp_data;