[PATCH 16/20] net: dsa: xilinx: drive per-MAC PTP TX/RX hardware paths
Nagadheeraj Rottela <[email protected]>
| Newsgroups | org.kernel.vger.netdev,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
PTP event frames bypass the switch fabric. Each MAC has its own PTP TX and PTP RX register windows in the per-MAC register space, plus two dedicated interrupts. A frame captured in a MAC's PTP RX buffer goes directly to the matching user port. A frame pushed to a MAC's PTP TX buffer transmits out the same port without ever entering the conduit or switch. Publish the TX path to the tag protocol through ds->tagger_data in setup(). Using a function pointer through tagger_data rather than an exported symbol avoids a link-time dependency from the tag module to the switch module. TX timestamping accepts HWTSTAMP_TX_OFF and HWTSTAMP_TX_ON. Accept any PTP v2 event RX filter and promote it to HWTSTAMP_FILTER_PTP_V2_L2_EVENT. The per-MAC frame-filter block cannot narrow the filter by message type. Co-developed-by: Srinivas Neeli <[email protected]> Signed-off-by: Srinivas Neeli <[email protected]> Signed-off-by: Nagadheeraj Rottela <[email protected]> --- MAINTAINERS | 1 + drivers/net/dsa/xilinx/xilinx_tsn.c | 24 ++ drivers/net/dsa/xilinx/xilinx_tsn.h | 76 +++++ drivers/net/dsa/xilinx/xilinx_tsn_ptp.c | 357 +++++++++++++++++++++++- include/linux/dsa/xlnx_tsn.h | 25 ++ net/dsa/tag_xlnx_tsn.c | 29 +- 6 files changed, 510 insertions(+), 2 deletions(-) create mode 100644 include/linux/dsa/xlnx_tsn.h diff --git a/MAINTAINERS b/MAINTAINERS index 0e91cb2a307d..c44b6fdd5b4a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29615,6 +29615,7 @@ S: Maintained F: Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml F: drivers/net/dsa/xilinx/ F: drivers/net/ethernet/xilinx/tsn/ +F: include/linux/dsa/xlnx_tsn.h F: net/dsa/tag_xlnx_tsn.c XILINX UARTLITE SERIAL DRIVER diff --git a/drivers/net/dsa/xilinx/xilinx_tsn.c b/drivers/net/dsa/xilinx/xilinx_tsn.c index 318d8b332208..e48c81f18d6e 100644 --- a/drivers/net/dsa/xilinx/xilinx_tsn.c +++ b/drivers/net/dsa/xilinx/xilinx_tsn.c @@ -18,6 +18,7 @@ #include <linux/phy.h> #include <linux/phylink.h> #include <linux/platform_device.h> +#include <linux/dsa/xlnx_tsn.h> #include <net/dsa.h> #include "xilinx_tsn.h" @@ -679,8 +680,25 @@ static int xlnx_tsn_setup(struct dsa_switch *ds) if (ret) goto err_nb; + ret = xlnx_tsn_port_ptp_init(sw, XLNX_TSN_PORT_MAC1, + "ptp_rx_mac1", "ptp_tx_mac1"); + if (ret) + goto err_ptp_exit; + + ret = xlnx_tsn_port_ptp_init(sw, XLNX_TSN_PORT_MAC2, + "ptp_rx_mac2", "ptp_tx_mac2"); + if (ret) + goto err_ptp_mac1; + + sw->tagger_data.ptp_tx = xlnx_tsn_ptp_tx; + ds->tagger_data = &sw->tagger_data; + return 0; +err_ptp_mac1: + xlnx_tsn_port_ptp_exit(sw, XLNX_TSN_PORT_MAC1); +err_ptp_exit: + xlnx_tsn_ptp_exit(sw); err_nb: unregister_netdevice_notifier(&sw->nb); err_mdio: @@ -692,7 +710,10 @@ static void xlnx_tsn_teardown(struct dsa_switch *ds) { struct xlnx_tsn *sw = ds->priv; struct dsa_port *dp; + int port; + for (port = XLNX_TSN_PORT_MAC1; port <= XLNX_TSN_PORT_MAC2; port++) + xlnx_tsn_port_ptp_exit(sw, port); xlnx_tsn_ptp_exit(sw); unregister_netdevice_notifier(&sw->nb); xlnx_tsn_mdio_unregister_all(sw); @@ -709,6 +730,9 @@ static const struct dsa_switch_ops xlnx_tsn_switch_ops = { .teardown = xlnx_tsn_teardown, .port_set_mac_address = xlnx_tsn_port_set_mac_address, .port_stp_state_set = xlnx_tsn_port_stp_state_set, + .port_hwtstamp_get = xlnx_tsn_port_hwtstamp_get, + .port_hwtstamp_set = xlnx_tsn_port_hwtstamp_set, + .get_ts_info = xlnx_tsn_get_ts_info, .phylink_get_caps = xlnx_tsn_phylink_get_caps, }; diff --git a/drivers/net/dsa/xilinx/xilinx_tsn.h b/drivers/net/dsa/xilinx/xilinx_tsn.h index 6063763b5c17..d46150535775 100644 --- a/drivers/net/dsa/xilinx/xilinx_tsn.h +++ b/drivers/net/dsa/xilinx/xilinx_tsn.h @@ -9,10 +9,14 @@ #include <linux/bits.h> #include <linux/if_ether.h> #include <linux/io.h> +#include <linux/net_tstamp.h> #include <linux/notifier.h> #include <linux/ptp_clock_kernel.h> +#include <linux/skbuff.h> #include <linux/spinlock.h> #include <linux/types.h> +#include <linux/workqueue.h> +#include <linux/dsa/xlnx_tsn.h> #include <net/dsa.h> #define XLNX_TSN_NUM_PORTS 3 @@ -127,7 +131,44 @@ enum tsn_port_state { #define TSN_TIMER_PULSES_PER_PPS 128 #define TSN_TIMER_GTX_CLK_FREQ 125000000U +/* Per-MAC PTP TX / RX register windows, sitting inside each per-MAC + * reg space. Each PTP TX slot is 256 B wide; the first 8 B hold the + * cmd1/cmd2 header, leaving 248 B for frame data. HW provides 8 slots. + * The PTP RX buffer mirrors the layout with a 252 B usable area and + * an 8 B HW timestamp footer. + */ +#define TSN_PTP_TX_CONTROL_OFFSET 0x00012000 +#define TSN_PTP_RX_CONTROL_OFFSET 0x00012004 + +#define TSN_PTP_RX_BASE_OFFSET 0x00010000 +#define TSN_PTP_RX_PACKET_FIELD_MASK GENMASK(11, 8) +#define TSN_PTP_RX_PACKET_CLEAR BIT(0) + +#define TSN_PTP_TX_BASE_OFFSET 0x00011000 +#define TSN_PTP_TX_HWBUF_SIZE 0x100 +#define TSN_PTP_TX_BUFFERS 8 +#define TSN_PTP_TX_BUFFER_OFFSET(i) (TSN_PTP_TX_BASE_OFFSET + \ + (i) * TSN_PTP_TX_HWBUF_SIZE) +#define TSN_PTP_TX_CMD_FIELD_LEN 8 +#define TSN_PTP_TX_MAX_FRAME_SIZE (TSN_PTP_TX_HWBUF_SIZE - \ + TSN_PTP_TX_CMD_FIELD_LEN) +#define TSN_PTP_TX_BUFFER_CMD2_FIELD 0x4 + +#define TSN_PTP_TX_FRAME_WAITING_MASK GENMASK(15, 8) +#define TSN_PTP_TX_BUFFERS_FULL_MASK BIT(TSN_PTP_TX_BUFFERS - 1) +#define TSN_PTP_TX_PACKET_FIELD_MASK GENMASK(18, 16) + +#define TSN_PTP_HW_TSTAMP_SIZE 8 +#define TSN_PTP_RX_HWBUF_SIZE 256 +#define TSN_PTP_RX_FRAME_SIZE 252 +#define TSN_PTP_HW_TSTAMP_OFFSET (TSN_PTP_RX_HWBUF_SIZE - \ + TSN_PTP_HW_TSTAMP_SIZE) + +#define TSN_PTP_MSG_TYPE_MASK BIT(3) + +struct kernel_ethtool_ts_info; struct mii_bus; +struct netlink_ext_ack; struct xlnx_tsn; /** @@ -137,11 +178,32 @@ struct xlnx_tsn; * @regs: per-MAC register window, from reg-name "macN" * @mii_bus: MDIO bus registered under the "mdio-macN" DT child, * or NULL if absent + * @ptp_tx_irq: per-MAC PTP TX-completion interrupt + * @ptp_rx_irq: per-MAC PTP RX interrupt + * @ptp_tx_lock: serialises the PTP TX slot allocator and the TX + * completion path + * @ptp_txq: in-flight PTP TX frames awaiting timestamp completion. + * Each frame's slot index is kept in skb->cb[0]. + * @tx_tstamp_work: work item queued by the PTP TX IRQ to drain + * ptp_txq and deliver timestamps through skb_tstamp_tx() + * @ptp_rx_hw_pointer: HW write pointer snapshot read in the RX ISR + * @ptp_rx_sw_pointer: SW read pointer; drained until it catches up + * @hwtstamp_tx_type: current SO_TIMESTAMPING TX type for this port + * @hwtstamp_rx_filter: current SO_TIMESTAMPING RX filter for this port */ struct xlnx_tsn_mac { struct xlnx_tsn *sw; void __iomem *regs; struct mii_bus *mii_bus; + int ptp_tx_irq; + int ptp_rx_irq; + spinlock_t ptp_tx_lock; /* serialises PTP TX slot alloc + completion */ + struct sk_buff_head ptp_txq; + struct work_struct tx_tstamp_work; + u32 ptp_rx_hw_pointer; + u32 ptp_rx_sw_pointer; + int hwtstamp_tx_type; + int hwtstamp_rx_filter; }; /** @@ -167,6 +229,8 @@ struct xlnx_tsn_mac { * used as the starting point for adjust_by_scaled_ppm() * @pps_enable: user requested PPS event delivery * @countpulse: timer-tick counter, reset to zero every TSN_TIMER_PULSES_PER_PPS ticks + * @tagger_data: PTP TX callback descriptor handed to the tag protocol + * via @dsa_switch.tagger_data */ struct xlnx_tsn { struct dsa_switch ds; @@ -183,6 +247,7 @@ struct xlnx_tsn { u64 rtc_value; int pps_enable; int countpulse; + struct xlnx_tsn_tagger_data tagger_data; }; static inline void mac_iow(struct xlnx_tsn_mac *m, u32 off, u32 val) @@ -197,5 +262,16 @@ static inline u32 mac_ior(struct xlnx_tsn_mac *m, u32 off) int xlnx_tsn_ptp_init(struct xlnx_tsn *sw); void xlnx_tsn_ptp_exit(struct xlnx_tsn *sw); +int xlnx_tsn_port_ptp_init(struct xlnx_tsn *sw, int port, + const char *rx_name, const char *tx_name); +void xlnx_tsn_port_ptp_exit(struct xlnx_tsn *sw, int port); +void xlnx_tsn_ptp_tx(struct dsa_port *dp, struct sk_buff *skb); +int xlnx_tsn_port_hwtstamp_get(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config); +int xlnx_tsn_port_hwtstamp_set(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack); +int xlnx_tsn_get_ts_info(struct dsa_switch *ds, int port, + struct kernel_ethtool_ts_info *info); #endif /* _XILINX_TSN_H */ diff --git a/drivers/net/dsa/xilinx/xilinx_tsn_ptp.c b/drivers/net/dsa/xilinx/xilinx_tsn_ptp.c index 75c177f752f1..740d92e3e80d 100644 --- a/drivers/net/dsa/xilinx/xilinx_tsn_ptp.c +++ b/drivers/net/dsa/xilinx/xilinx_tsn_ptp.c @@ -1,18 +1,26 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * AMD/Xilinx TSN Endpoint Ethernet MAC DSA switch driver: - * PTP hardware clock. + * PTP hardware clock and per-MAC PTP TX/RX paths. */ +#include <linux/bitfield.h> #include <linux/cleanup.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/ktime.h> #include <linux/math64.h> +#include <linux/net_tstamp.h> +#include <linux/netdevice.h> #include <linux/of_irq.h> #include <linux/ptp_clock_kernel.h> +#include <linux/skbuff.h> #include <linux/spinlock.h> #include <linux/time64.h> +#include <linux/unaligned.h> +#include <linux/workqueue.h> #include <net/dsa.h> #include "xilinx_tsn.h" @@ -233,3 +241,350 @@ void xlnx_tsn_ptp_exit(struct xlnx_tsn *sw) ptp_clock_unregister(sw->ptp_clock); sw->ptp_clock = NULL; } + +static void memcpy_toio_32(struct xlnx_tsn_mac *m, unsigned long off, + const u8 *data, size_t len) +{ + while (len >= 4) { + mac_iow(m, off, get_unaligned((const u32 *)data)); + off += 4; + data += 4; + len -= 4; + } + + if (len) { + u32 leftover = 0; + u8 *dst = (u8 *)&leftover; + + while (len--) + *dst++ = *data++; + mac_iow(m, off, leftover); + } +} + +static void memcpy_fromio_32(struct xlnx_tsn_mac *m, unsigned long off, + u8 *data, size_t len) +{ + while (len >= 4) { + put_unaligned(mac_ior(m, off), (u32 *)data); + off += 4; + data += 4; + len -= 4; + } + + if (len) { + u32 leftover = mac_ior(m, off); + u8 *src = (u8 *)&leftover; + + while (len--) + *data++ = *src++; + } +} + +static void xlnx_tsn_read_tstamp(struct xlnx_tsn_mac *m, + struct skb_shared_hwtstamps *hwtstamps, + unsigned int off) +{ + u32 captured_ns, captured_sec; + + memset(hwtstamps, 0, sizeof(*hwtstamps)); + + captured_ns = mac_ior(m, off + 4); + captured_sec = mac_ior(m, off); + + hwtstamps->hwtstamp = ktime_set(captured_sec, captured_ns); +} + +void xlnx_tsn_ptp_tx(struct dsa_port *dp, struct sk_buff *skb) +{ + struct xlnx_tsn *sw = dp->ds->priv; + u32 frame_waiting, cmd1, cmd2 = 0; + struct xlnx_tsn_mac *m; + u8 free_index; + + m = &sw->mac[dp->index]; + + if (unlikely(skb->len > TSN_PTP_TX_MAX_FRAME_SIZE)) { + dev_kfree_skb_any(skb); + return; + } + + scoped_guard(spinlock_irqsave, &m->ptp_tx_lock) { + frame_waiting = FIELD_GET(TSN_PTP_TX_FRAME_WAITING_MASK, + mac_ior(m, TSN_PTP_TX_CONTROL_OFFSET)); + if (frame_waiting & TSN_PTP_TX_BUFFERS_FULL_MASK) { + dev_kfree_skb_any(skb); + return; + } + + free_index = fls(frame_waiting); + cmd1 = skb->len; + + mac_iow(m, TSN_PTP_TX_BUFFER_OFFSET(free_index), cmd1); + mac_iow(m, TSN_PTP_TX_BUFFER_OFFSET(free_index) + + TSN_PTP_TX_BUFFER_CMD2_FIELD, cmd2); + memcpy_toio_32(m, + TSN_PTP_TX_BUFFER_OFFSET(free_index) + + TSN_PTP_TX_CMD_FIELD_LEN, + skb->data, skb->len); + + skb->cb[0] = free_index; + __skb_queue_tail(&m->ptp_txq, skb); + + if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + + skb_tx_timestamp(skb); + mac_iow(m, TSN_PTP_TX_CONTROL_OFFSET, BIT(free_index)); + } +} + +static void xlnx_tsn_ptp_recv(struct xlnx_tsn *sw, int port) +{ + struct net_device *user = dsa_to_port(&sw->ds, port)->user; + struct xlnx_tsn_mac *m = &sw->mac[port]; + unsigned long frame_base; + struct sk_buff *skb; + u16 msg_len; + u8 msg_type; + + if (!user || !netif_running(user)) + return; + + while ((m->ptp_rx_hw_pointer & 0xf) != (m->ptp_rx_sw_pointer & 0xf)) { + m->ptp_rx_sw_pointer++; + + frame_base = TSN_PTP_RX_BASE_OFFSET + + (m->ptp_rx_sw_pointer & 0xf) * + TSN_PTP_RX_HWBUF_SIZE; + + skb = netdev_alloc_skb(user, TSN_PTP_RX_FRAME_SIZE); + if (!skb) { + DEV_STATS_INC(user, rx_dropped); + continue; + } + + memcpy_fromio_32(m, frame_base, skb->data, + TSN_PTP_RX_FRAME_SIZE); + + msg_type = *(u8 *)(skb->data + ETH_HLEN) & 0xf; + msg_len = get_unaligned_be16(skb->data + ETH_HLEN + 2); + + if (msg_len + ETH_HLEN > TSN_PTP_RX_FRAME_SIZE) { + dev_kfree_skb_any(skb); + DEV_STATS_INC(user, rx_length_errors); + continue; + } + + skb_put(skb, msg_len + ETH_HLEN); + skb->protocol = eth_type_trans(skb, user); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + if (READ_ONCE(m->hwtstamp_rx_filter) != HWTSTAMP_FILTER_NONE && + !(msg_type & TSN_PTP_MSG_TYPE_MASK)) + xlnx_tsn_read_tstamp(m, skb_hwtstamps(skb), + frame_base + + TSN_PTP_HW_TSTAMP_OFFSET); + + dev_sw_netstats_rx_add(user, skb->len); + netif_rx(skb); + } +} + +static irqreturn_t xlnx_tsn_ptp_rx_isr(int irq, void *data) +{ + struct xlnx_tsn_mac *m = data; + struct xlnx_tsn *sw = m->sw; + int port = m - sw->mac; + + m->ptp_rx_hw_pointer = FIELD_GET(TSN_PTP_RX_PACKET_FIELD_MASK, + mac_ior(m, TSN_PTP_RX_CONTROL_OFFSET)); + xlnx_tsn_ptp_recv(sw, port); + + return IRQ_HANDLED; +} + +static void xlnx_tsn_tx_tstamp_work(struct work_struct *work) +{ + struct xlnx_tsn_mac *m = container_of(work, struct xlnx_tsn_mac, + tx_tstamp_work); + struct skb_shared_hwtstamps hwtstamps; + unsigned long ts_off; + struct sk_buff *skb; + u8 tx_packet, index; + + guard(spinlock_irqsave)(&m->ptp_tx_lock); + + tx_packet = FIELD_GET(TSN_PTP_TX_PACKET_FIELD_MASK, + mac_ior(m, TSN_PTP_TX_CONTROL_OFFSET)); + + while ((skb = __skb_dequeue(&m->ptp_txq)) != NULL) { + index = skb->cb[0]; + + /* HW writes ascending slot indices into the TX status field + * as frames depart. Any queued skb with index > tx_packet + * has not been timestamped yet, so requeue it and stop. + */ + if (index > tx_packet) { + __skb_queue_head(&m->ptp_txq, skb); + break; + } + + ts_off = TSN_PTP_TX_BUFFER_OFFSET(index) + + TSN_PTP_HW_TSTAMP_OFFSET; + + if (skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS) { + xlnx_tsn_read_tstamp(m, &hwtstamps, ts_off); + skb_tstamp_tx(skb, &hwtstamps); + } + consume_skb(skb); + } +} + +static irqreturn_t xlnx_tsn_ptp_tx_isr(int irq, void *data) +{ + struct xlnx_tsn_mac *m = data; + + mac_ior(m, TSN_PTP_TX_CONTROL_OFFSET); + schedule_work(&m->tx_tstamp_work); + + return IRQ_HANDLED; +} + +int xlnx_tsn_port_ptp_init(struct xlnx_tsn *sw, int port, + const char *rx_name, const char *tx_name) +{ + struct xlnx_tsn_mac *m = &sw->mac[port]; + int ret; + + skb_queue_head_init(&m->ptp_txq); + spin_lock_init(&m->ptp_tx_lock); + INIT_WORK(&m->tx_tstamp_work, xlnx_tsn_tx_tstamp_work); + m->ptp_rx_hw_pointer = 0; + m->ptp_rx_sw_pointer = 0xff; + m->hwtstamp_tx_type = HWTSTAMP_TX_OFF; + m->hwtstamp_rx_filter = HWTSTAMP_FILTER_NONE; + + m->ptp_rx_irq = of_irq_get_byname(sw->dev->of_node, rx_name); + if (m->ptp_rx_irq <= 0) + return dev_err_probe(sw->dev, m->ptp_rx_irq ? : -ENXIO, + "failed to get %s IRQ\n", rx_name); + + m->ptp_tx_irq = of_irq_get_byname(sw->dev->of_node, tx_name); + if (m->ptp_tx_irq <= 0) + return dev_err_probe(sw->dev, m->ptp_tx_irq ? : -ENXIO, + "failed to get %s IRQ\n", tx_name); + + mac_iow(m, TSN_PTP_RX_CONTROL_OFFSET, TSN_PTP_RX_PACKET_CLEAR); + + ret = request_irq(m->ptp_rx_irq, xlnx_tsn_ptp_rx_isr, 0, rx_name, m); + if (ret) + return dev_err_probe(sw->dev, ret, + "failed to request %s IRQ %d\n", + rx_name, m->ptp_rx_irq); + + ret = request_irq(m->ptp_tx_irq, xlnx_tsn_ptp_tx_isr, 0, tx_name, m); + if (ret) { + free_irq(m->ptp_rx_irq, m); + return dev_err_probe(sw->dev, ret, + "failed to request %s IRQ %d\n", + tx_name, m->ptp_tx_irq); + } + + return 0; +} + +void xlnx_tsn_port_ptp_exit(struct xlnx_tsn *sw, int port) +{ + struct xlnx_tsn_mac *m = &sw->mac[port]; + struct sk_buff *skb; + + if (m->ptp_tx_irq > 0) + free_irq(m->ptp_tx_irq, m); + + if (m->ptp_rx_irq > 0) + free_irq(m->ptp_rx_irq, m); + + cancel_work_sync(&m->tx_tstamp_work); + + scoped_guard(spinlock_irqsave, &m->ptp_tx_lock) + while ((skb = __skb_dequeue(&m->ptp_txq)) != NULL) + dev_kfree_skb_any(skb); +} + +int xlnx_tsn_port_hwtstamp_get(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config) +{ + struct xlnx_tsn *sw = ds->priv; + struct xlnx_tsn_mac *m; + + m = &sw->mac[port]; + + if (port == XLNX_TSN_CPU_PORT) + return -EOPNOTSUPP; + + config->tx_type = m->hwtstamp_tx_type; + config->rx_filter = m->hwtstamp_rx_filter; + + return 0; +} + +int xlnx_tsn_port_hwtstamp_set(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) +{ + struct xlnx_tsn *sw = ds->priv; + struct xlnx_tsn_mac *m; + + m = &sw->mac[port]; + + if (port == XLNX_TSN_CPU_PORT) + return -EOPNOTSUPP; + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + case HWTSTAMP_TX_ON: + break; + default: + return -ERANGE; + } + + /* The per-MAC RX filter is left at its power-on default, which + * captures L2 gPTP event frames (ethertype 0x88f7) only. PTP over + * UDP is not supported. The filter cannot narrow by message type, + * so any L2 PTPv2 event request is promoted to + * HWTSTAMP_FILTER_PTP_V2_L2_EVENT. + */ + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + break; + default: + return -ERANGE; + } + + m->hwtstamp_tx_type = config->tx_type; + WRITE_ONCE(m->hwtstamp_rx_filter, config->rx_filter); + + return 0; +} + +int xlnx_tsn_get_ts_info(struct dsa_switch *ds, int port, + struct kernel_ethtool_ts_info *info) +{ + struct xlnx_tsn *sw = ds->priv; + + info->phc_index = sw->ptp_clock ? ptp_clock_index(sw->ptp_clock) : -1; + info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON); + info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | + BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT); + + return 0; +} diff --git a/include/linux/dsa/xlnx_tsn.h b/include/linux/dsa/xlnx_tsn.h new file mode 100644 index 000000000000..34a451f4568a --- /dev/null +++ b/include/linux/dsa/xlnx_tsn.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * AMD/Xilinx TSN Endpoint Ethernet MAC: shared tagger / switch + * private interface. + */ +#ifndef _NET_DSA_XLNX_TSN_H +#define _NET_DSA_XLNX_TSN_H + +#include <linux/skbuff.h> +#include <net/dsa.h> + +/** + * struct xlnx_tsn_tagger_data - per-switch context for the tag protocol + * @ptp_tx: callback that copies a PTP event frame into the per-port + * hardware TX buffer and holds the skb until the TX IRQ + * delivers the timestamp. Consumes one reference to @skb. + * + * Set on @dsa_switch.tagger_data by the switch driver in setup() + * and read by the tag protocol's xmit hook. + */ +struct xlnx_tsn_tagger_data { + void (*ptp_tx)(struct dsa_port *dp, struct sk_buff *skb); +}; + +#endif /* _NET_DSA_XLNX_TSN_H */ diff --git a/net/dsa/tag_xlnx_tsn.c b/net/dsa/tag_xlnx_tsn.c index de352aa3d9a8..6f487ad7bcf9 100644 --- a/net/dsa/tag_xlnx_tsn.c +++ b/net/dsa/tag_xlnx_tsn.c @@ -3,14 +3,41 @@ * AMD/Xilinx TSN Endpoint Ethernet MAC tag protocol. */ +#include <linux/dsa/xlnx_tsn.h> +#include <linux/if_vlan.h> + #include "tag.h" #define XLNX_TSN_NAME "xlnx_tsn" +/* PTP frames must go directly into the egress MAC's hardware TX buffer, + * not through the switch fabric or conduit DMA. Intercept here before + * dsa_enqueue_skb() takes the frame and hand off our reference: ptp_tx() + * consumes it, freeing the skb once the HW timestamp is read (or on + * error). + * + * VLAN-tagged PTP is not supported. Match on the L2 ethertype and + * skip any VLAN-tagged frame. + */ static struct sk_buff *xlnx_tsn_xmit(struct sk_buff *skb, struct net_device *dev) { - return skb; + struct xlnx_tsn_tagger_data *tagger_data; + struct dsa_port *dp; + + dp = dsa_user_to_port(dev); + tagger_data = dp->ds->tagger_data; + + if (!tagger_data || !tagger_data->ptp_tx) + return skb; + + if (eth_hdr(skb)->h_proto != htons(ETH_P_1588) || + skb_vlan_tag_present(skb)) + return skb; + + tagger_data->ptp_tx(dp, skb); + + return NULL; } static struct sk_buff *xlnx_tsn_rcv(struct sk_buff *skb, -- 2.34.1