From: Henrik Austad <[email protected]>
The driver is directed via ConfigFS as we need userspace to handle
stream-reservation (MSRP), discovery and enumeration (IEEE 1722.1) and
whatever other management is needed. This also includes running an
appropriate PTP daemon (TSN favors gPTP).
Once we have all the required attributes, we can create link using
mkdir, and use write() to set the attributes. Once ready, specify the
'shim' (basically a thin wrapper between TSN and another subsystem) and
we start pushing out frames.
The network part: it ties directly into the Rx-handler for receive and
writes skb's using dev_queue_xmit(). This could probably be improved.
2 new fields in netdev_ops have been introduced, and the Intel
igb-driver has been updated (as this an AVB-capable NIC which is
available as a PCI-e card).
What remains (tl;dr: a lot) a.k.a "Known problems" or "working on it!"
- tie to (g)PTP properly, currently using ktime_get() for presentation
time
- get time from shim into TSN
- let shim create/manage buffer
- redo parts of the link-stuff using RCUs, the current setup is a bit
clumsy.
- The igb-driver does not work properly when compiled with IGB_TSN, some
details in setting the register values needs to be figured out. I am
working on this, but as it stands, the best bet is to load tsn using
in_debug=1 to bypass the capability-check. I have had e1000 and sky2
running for several days without crashing, igb crashes and burns
violently.
- The ALSA driver does not handle multiple devices very well and is a
work in progress.
* v2: changes since v1
Changes since v1
- updated to latest upstream kernel (v4.8)
- set dedicated enabled-attribute and let shim be stored in own (support
future plan for enabling per-shim attributes)
- fixed endianess issue in bitfields used in tsn-structs
- Updated some of the trace-events to use trace_class
- Fix various silly typos
- Handle disabling of link from hrtimer a bit more gracefully (that
actually works-ish).
- use old skb and size of skb when that is set (Reporte by Nikita)
- Move PCP-codes to NIC and not in the link itself
- Allow TSN-capable card to be loaded even when in debug-mode (and do
not enforce TSN behaviour)
- Start hooking into ALSA's get_time_info hooks (very much incomplete)
- use threads for sending frames, wake from hrtimer-callback.
This also queues up awaiting timers if we fail to complete the
transmit before another timer arrives, it will immediately execute
another iteration, so no events should be lost. That being said,
should this happen, it is a clear bug as we really should complete
well before the next interval.
- Cleanup link-locking and differentiate between Talker and Listener (as
Listener grab link-lock from IRQ context)
- Change list-lock to spinlock as we may need to take a link-lock whilst
holding the master list-lock.
- Do a more careful dance holding the spinlocks to regions only doing
actual update.
Network driver (I210 only)
- bring up all Tx-/Rx-queues when igb is in TSN-mode regardless of how
many CPUs the system has for I210
- Correctly calculate the idle_slope in I210's configure hook
- Update igb-driver with queue-select and return correct queue when
sending TSN-frames
- add IGB_FLAG_QAV_PRIO flag to igb_adapter (to handle proper config of
tx-ring when adapter is brought up.
- add TXDCTL logic (part of preparatory work for TSN) to igb-driver
- Improve SR(A|B) accountingo
ALSA Shim
- Allow userspace to grab much smaller chunks of data (down to a single
Class A frame for S16_LE 2ch 48kHz).
- Create the card with index/id pattern to avoid collision with other
cards.
* v1
Before reading on - this is not even beta, but I'd really appreciate if
people would comment on the overall architecture and perhaps provide
some pointers to where I should improve/fix/update
- thanks!
This is a very early RFC for a TSN-driver in the kernel. It has been
floating around in my repo for a while and I would appreciate some
feedback on the overall design to avoid doing some major blunders.
There are at least one AVB-driver (the AV-part of TSN) in the kernel
already. This driver aims to solve a wider scope as TSN can do much more
than just audio. A very basic ALSA-driver is added to the end that
allows you to play music between 2 machines using aplay in one end and
arecord | aplay on the other (some fiddling required) We have plans for
doing the same for v4l2 eventually (but there are other fishes to fry
first). The same goes for a TSN_SOCK type approach as well.
Henrik Austad (9):
igb: add missing fields to TXDCTL-register
TSN: add documentation
TSN: Add the standard formerly known as AVB to the kernel
Adding TSN-driver to Intel I210 controller
Add TSN header for the driver
Add TSN machinery to drive the traffic from a shim over the network
Add TSN event-tracing
AVB ALSA - Add ALSA shim for TSN
MAINTAINERS: add TSN/AVB-entries
Documentation/TSN/tsn.txt | 345 ++++++++
MAINTAINERS | 14 +
drivers/media/Kconfig | 15 +
drivers/media/Makefile | 2 +-
drivers/media/avb/Makefile | 5 +
drivers/media/avb/avb_alsa.c | 793 +++++++++++++++++
drivers/media/avb/tsn_iec61883.h | 152 ++++
drivers/net/ethernet/intel/Kconfig | 18 +
drivers/net/ethernet/intel/igb/Makefile | 2 +-
drivers/net/ethernet/intel/igb/e1000_82575.h | 4 +
drivers/net/ethernet/intel/igb/igb.h | 26 +
drivers/net/ethernet/intel/igb/igb_main.c | 39 +-
drivers/net/ethernet/intel/igb/igb_tsn.c | 468 ++++++++++
include/linux/netdevice.h | 44 +
include/linux/tsn.h | 952 +++++++++++++++++++++
include/trace/events/tsn.h | 333 ++++++++
net/Kconfig | 1 +
net/Makefile | 1 +
net/tsn/Kconfig | 32 +
net/tsn/Makefile | 6 +
net/tsn/tsn_configfs.c | 673 +++++++++++++++
net/tsn/tsn_core.c | 1189 ++++++++++++++++++++++++++
net/tsn/tsn_header.c | 162 ++++
net/tsn/tsn_internal.h | 397 +++++++++
net/tsn/tsn_net.c | 392 +++++++++
25 files changed, 6061 insertions(+), 4 deletions(-)
create mode 100644 Documentation/TSN/tsn.txt
create mode 100644 drivers/media/avb/Makefile
create mode 100644 drivers/media/avb/avb_alsa.c
create mode 100644 drivers/media/avb/tsn_iec61883.h
create mode 100644 drivers/net/ethernet/intel/igb/igb_tsn.c
create mode 100644 include/linux/tsn.h
create mode 100644 include/trace/events/tsn.h
create mode 100644 net/tsn/Kconfig
create mode 100644 net/tsn/Makefile
create mode 100644 net/tsn/tsn_configfs.c
create mode 100644 net/tsn/tsn_core.c
create mode 100644 net/tsn/tsn_header.c
create mode 100644 net/tsn/tsn_internal.h
create mode 100644 net/tsn/tsn_net.c
--
2.7.4
From: Henrik Austad <[email protected]>
The current list of E1000_TXDCTL-registers is incomplete. This adds the
missing parts for the Transmit Descriptor Control (TXDCTL) register.
The rest of these values (threshold for descriptor read/write) for
TXDCTL seems to be defined in igb/igb.h, not sure why this is split
though.
It seems that this was left out in the commit that added support for
82575 Gigabit Ethernet driver 9d5c8243 (igb: PCI-Express 82575 Gigabit
Ethernet driver).
Cc: [email protected]
Cc: Jeff Kirsher <[email protected]>
Cc: [email protected]
Signed-off-by: Henrik Austad <[email protected]>
---
drivers/net/ethernet/intel/igb/e1000_82575.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/net/ethernet/intel/igb/e1000_82575.h b/drivers/net/ethernet/intel/igb/e1000_82575.h
index acf0605..7faa482 100644
--- a/drivers/net/ethernet/intel/igb/e1000_82575.h
+++ b/drivers/net/ethernet/intel/igb/e1000_82575.h
@@ -158,7 +158,11 @@ struct e1000_adv_tx_context_desc {
/* Additional Transmit Descriptor Control definitions */
#define E1000_TXDCTL_QUEUE_ENABLE 0x02000000 /* Enable specific Tx Queue */
+
+/* Transmit Software Flush, sw-triggered desc writeback */
+#define E1000_TXDCTL_SWFLSH 0x04000000
/* Tx Queue Arbitration Priority 0=low, 1=high */
+#define E1000_TXDCTL_PRIORITY 0x08000000
/* Additional Receive Descriptor Control definitions */
#define E1000_RXDCTL_QUEUE_ENABLE 0x02000000 /* Enable specific Rx Queue */
--
2.7.4
From: Henrik Austad <[email protected]>
This adds support for loading the igb.ko module with tsn
capabilities. This requires a 2-step approach. First enabling TSN in
.config, then load the module with use_tsn=1.
Once enabled and loaded, the controller will be placed in "Qav-mode"
which is when the credit-based shaper is available, 3 of the queues are
removed from regular traffic, max payload is set to 1522 octets (no
jumboframes allowed).
It dumps the registers of interest before and after, so this clutters
kern.log a bit if it is loaded with debug_tsn=1.
Regardless of number of online CPUs, it will enable *all* for Tx-queues as
2 is required for Qav traffic. This has not been tested extensively, so
there may be some instabilities in this.
Improved SR(A|B) accounting:
Use the idleslope-bins to keep track of how much time is reserved for
each class. This can then be used to strip the vlan-tag on the NIC when
the last stream goes (and also allow for reconfiguration of PCP when the
NIC is not sending TSN traffic).
Note: currently this driver is *not* stable, it is still a work in
progress, some points to keep tabs on:
- Set hicred to unlim (for testing this is ok and we avoid some nasty
calculations)
- once we configure it for TSN, enable credit shaping, do not wait for
first link to be configured (nobody else should use these queues after
being configured).
- enable all Tx-/Rx-queues in TSN-mode regardless of num_online_cpus()
- Add 802.1Qav Prio-bit in adapter->flags
Cc: Jeff Kirsher <[email protected]>
Cc: Jesse Brandeburg <[email protected]>
Cc: [email protected]
Cc: "David S. Miller" <[email protected]>
Signed-off-by: Henrik Austad <[email protected]>
---
drivers/net/ethernet/intel/Kconfig | 18 ++
drivers/net/ethernet/intel/igb/Makefile | 2 +-
drivers/net/ethernet/intel/igb/igb.h | 26 ++
drivers/net/ethernet/intel/igb/igb_main.c | 39 ++-
drivers/net/ethernet/intel/igb/igb_tsn.c | 468 ++++++++++++++++++++++++++++++
5 files changed, 550 insertions(+), 3 deletions(-)
create mode 100644 drivers/net/ethernet/intel/igb/igb_tsn.c
diff --git a/drivers/net/ethernet/intel/Kconfig b/drivers/net/ethernet/intel/Kconfig
index c0e1743..d4382b4 100644
--- a/drivers/net/ethernet/intel/Kconfig
+++ b/drivers/net/ethernet/intel/Kconfig
@@ -99,6 +99,24 @@ config IGB
To compile this driver as a module, choose M here. The module
will be called igb.
+config IGB_TSN
+ tristate "TSN Support for Intel(R) 82575/82576 i210 Network Controller"
+ depends on IGB && TSN
+ ---help---
+ This driver supports TSN (AVB) on Intel I210 network controllers.
+
+ When enabled, it will allow the module to be loaded with
+ "use_tsn" which will initialize the controller to A/V-mode
+ instead of legacy-mode. This will take 3 of the tx-queues and
+ place them in 802.1Q QoS mode and enable the credit-based
+ shaper for 2 of the queues.
+
+ If built with this option, but not loaded with use_tsn, the
+ only difference is a slightly larger module, no extra
+ code paths are called.
+
+ If unsure, say No
+
config IGB_HWMON
bool "Intel(R) PCI-Express Gigabit adapters HWMON support"
default y
diff --git a/drivers/net/ethernet/intel/igb/Makefile b/drivers/net/ethernet/intel/igb/Makefile
index 5bcb2de..1a9b776 100644
--- a/drivers/net/ethernet/intel/igb/Makefile
+++ b/drivers/net/ethernet/intel/igb/Makefile
@@ -33,4 +33,4 @@ obj-$(CONFIG_IGB) += igb.o
igb-objs := igb_main.o igb_ethtool.o e1000_82575.o \
e1000_mac.o e1000_nvm.o e1000_phy.o e1000_mbx.o \
- e1000_i210.o igb_ptp.o igb_hwmon.o
+ e1000_i210.o igb_ptp.o igb_hwmon.o igb_tsn.o
diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h
index d11093d..474a5b4 100644
--- a/drivers/net/ethernet/intel/igb/igb.h
+++ b/drivers/net/ethernet/intel/igb/igb.h
@@ -394,6 +394,7 @@ struct igb_nfc_filter {
};
/* board specific private data structure */
+
struct igb_adapter {
unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)];
@@ -519,6 +520,17 @@ struct igb_adapter {
/* lock for RX network flow classification filter */
spinlock_t nfc_lock;
bool etype_bitmap[MAX_ETYPE_FILTER];
+
+#if IS_ENABLED(CONFIG_IGB_TSN)
+ /* Reserved BW for class A and B */
+ s32 sra_idleslope_res;
+ s32 srb_idleslope_res;
+ u8 pcp_hi;
+ u8 pcp_lo;
+ u8 tsn_ready:1;
+ u8 tsn_vlan_added:1;
+ u8 res:6;
+#endif /* IGB_TSN */
};
/* flags controlling PTP/1588 function */
@@ -540,6 +552,7 @@ struct igb_adapter {
#define IGB_FLAG_HAS_MSIX BIT(13)
#define IGB_FLAG_EEE BIT(14)
#define IGB_FLAG_VLAN_PROMISC BIT(15)
+#define IGB_FLAG_QAV_PRIO BIT(16)
/* Media Auto Sense */
#define IGB_MAS_ENABLE_0 0X0001
@@ -603,6 +616,19 @@ void igb_ptp_rx_pktstamp(struct igb_q_vector *q_vector, unsigned char *va,
struct sk_buff *skb);
int igb_ptp_set_ts_config(struct net_device *netdev, struct ifreq *ifr);
int igb_ptp_get_ts_config(struct net_device *netdev, struct ifreq *ifr);
+/* This should be the only place where we add ifdeffery
+ * to include tsn-stuff or not. Everything else is located in igb_tsn.c
+ */
+#if IS_ENABLED(CONFIG_IGB_TSN)
+void igb_tsn_init(struct igb_adapter *adapter);
+int igb_tsn_capable(struct net_device *netdev);
+int igb_tsn_link_configure(struct net_device *netdev, enum sr_class sr_class,
+ u16 framesize, u16 vid, u8 add_link, u8 pcp_hi, u8 pcp_lo);
+u16 igb_tsn_select_queue(struct net_device *netdev, struct sk_buff *skb,
+ void *accel_priv, select_queue_fallback_t fallback);
+#else
+static inline void igb_tsn_init(struct igb_adapter *adapter) { }
+#endif /* CONFIG_IGB_TSN */
void igb_set_flag_queue_pairs(struct igb_adapter *, const u32);
#ifdef CONFIG_IGB_HWMON
void igb_sysfs_exit(struct igb_adapter *adapter);
diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
index 9affd7c..0283864 100644
--- a/drivers/net/ethernet/intel/igb/igb_main.c
+++ b/drivers/net/ethernet/intel/igb/igb_main.c
@@ -256,6 +256,12 @@ static int debug = -1;
module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)");
+static int use_tsn = 0;
+#if IS_ENABLED(CONFIG_IGB_TSN)
+MODULE_PARM_DESC(use_tsn, "use_tsn (0=off, 1=enabled)");
+module_param(use_tsn, int, 0);
+#endif
+
struct igb_reg_info {
u32 ofs;
char *name;
@@ -675,6 +681,9 @@ static int __init igb_init_module(void)
{
int ret;
+ if (use_tsn != 1)
+ use_tsn = 0;
+
pr_info("%s - version %s\n",
igb_driver_string, igb_driver_version);
pr_info("%s\n", igb_copyright);
@@ -2161,6 +2170,11 @@ static const struct net_device_ops igb_netdev_ops = {
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = igb_netpoll,
#endif
+#if IS_ENABLED(CONFIG_IGB_TSN)
+ .ndo_tsn_capable = igb_tsn_capable,
+ .ndo_tsn_link_configure = igb_tsn_link_configure,
+ .ndo_select_queue = igb_tsn_select_queue,
+#endif /* CONFIG_IGB_TSN */
.ndo_fix_features = igb_fix_features,
.ndo_set_features = igb_set_features,
.ndo_fdb_add = igb_ndo_fdb_add,
@@ -2682,6 +2696,9 @@ static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
/* do hw tstamp init after resetting */
igb_ptp_init(adapter);
+ if (use_tsn)
+ igb_tsn_init(adapter);
+
dev_info(&pdev->dev, "Intel(R) Gigabit Ethernet Network Connection\n");
/* print bus type/speed/width info, not applicable to i354 */
if (hw->mac.type != e1000_i354) {
@@ -3009,6 +3026,21 @@ static void igb_init_queue_configuration(struct igb_adapter *adapter)
adapter->rss_queues = min_t(u32, max_rss_queues, num_online_cpus());
+#if IS_ENABLED(CONFIG_IGB_TSN)
+ /* For I210: If we are using TSN, we don't want to use num_online_cpus(),
+ * as we need 4 Tx-queues.
+ *
+ * Rx is another matter, but in time we probably want to assign
+ * Rx-0 to class A, Rx-1 to B, -2 to PTP/MSRP control etc and -3
+ * to all other traffic.
+ */
+ if (use_tsn && hw->mac.type == e1000_i210) {
+ adapter->rss_queues = max_rss_queues;
+ pr_info("igb_init_queue_configuration: rss_queues=%u\n",
+ adapter->rss_queues);
+ }
+#endif /* IGB_TSN */
+
igb_set_flag_queue_pairs(adapter, max_rss_queues);
}
@@ -3397,7 +3429,8 @@ void igb_configure_tx_ring(struct igb_adapter *adapter,
txdctl |= IGB_TX_PTHRESH;
txdctl |= IGB_TX_HTHRESH << 8;
txdctl |= IGB_TX_WTHRESH << 16;
-
+ if (ring->flags & IGB_FLAG_QAV_PRIO)
+ txdctl |= E1000_TXDCTL_PRIORITY;
txdctl |= E1000_TXDCTL_QUEUE_ENABLE;
wr32(E1000_TXDCTL(reg_idx), txdctl);
}
@@ -5345,8 +5378,10 @@ static netdev_tx_t igb_xmit_frame(struct sk_buff *skb,
/* The minimum packet size with TCTL.PSP set is 17 so pad the skb
* in order to meet this minimum size requirement.
*/
- if (skb_put_padto(skb, 17))
+ if (skb_put_padto(skb, 17)) {
+ pr_err("%s: skb_put_padto FAILED. skb->len < 17\n", __func__);
return NETDEV_TX_OK;
+ }
return igb_xmit_frame_ring(skb, igb_tx_queue_mapping(adapter, skb));
}
diff --git a/drivers/net/ethernet/intel/igb/igb_tsn.c b/drivers/net/ethernet/intel/igb/igb_tsn.c
new file mode 100644
index 0000000..dd5d9ed
--- /dev/null
+++ b/drivers/net/ethernet/intel/igb/igb_tsn.c
@@ -0,0 +1,468 @@
+/*
+ * Copyright(c) 2015-2016 Henrik Austad <[email protected]>
+ * Cisco Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+/* FIXME: This should probably be handled by some Makefile-magic */
+
+#if IS_ENABLED(CONFIG_IGB_TSN)
+#include "igb.h"
+#include <linux/module.h>
+
+/* NOTE: keep the defines not present in e1000_regs.h to avoid
+ * cluttering too many files. Once we are pretty stable, these will move
+ * into it's proper home. Until then, make merge a bit easier by
+ * avoiding it
+ */
+
+/* Qav regs */
+#define E1000_IRPBS 0x02404 /* Rx Packet Buffer Size - RW */
+#define E1000_ITPBS 0x03404 /* Tx buffer size assignment */
+#define E1000_TQAVCTRL 0x03570 /* Tx Qav Control */
+#define E1000_DTXMXPKTSZ 0x0355C /* DMA TX Maximum Packet Size */
+
+/* Qav defines. */
+#define E1000_TQAVCH_UNLIM_CREDIT 0xFFFFFFFF
+#define E1000_TQAVCH_ZERO_CREDIT 0x80000000
+#define E1000_LINK_RATE 0x7735
+
+/* 0:15 idleSlope
+ * 16:29 reserved
+ * 30 reserved
+ * 31 queue mode, 0=strict, 1=SR mode
+ */
+#define E1000_TQAVCC_QUEUEMODE 0x80000000
+#define E1000_TQAVCC_IDLE_SLOPE_MASK 0x0000ffff
+
+/* Transmit mode, 0=legacy, 1=QAV */
+#define E1000_TQAVCTRL_TXMODE 0x00000001
+/* report DMA time of tx packets */
+#define E1000_TQAVCTRL_1588_STAT_EN 0x00000004
+/* data fetch arbitration */
+#define E1000_TQAVCTRL_DATA_FETCH_ARB 0x00000010
+/* data tx arbitration */
+#define E1000_TQAVCTRL_DATA_TRAN_ARB 0x00000100
+/* data launch time valid */
+#define E1000_TQAVCTRL_DATA_TRAN_TIM 0x00000200
+/* stall SP to guarantee SR */
+#define E1000_TQAVCTRL_SP_WAIT_SR 0x00000400
+
+/* ... and associated shift value */
+#define E1000_TQAVCTRL_FETCH_TM_SHIFT (16)
+
+/* QAV Tx mode control registers where _n can be 0 or 1. */
+#define E1000_TQAVCC(_idx) (0x03004 + 0x40 * (_idx))
+
+/* Tx Qav High Credit - See 7.2.7.6 for calculations
+ * intel 8.12.18
+ */
+#define E1000_TQAVHC(_idx) (0x0300C + 0x40 * (_idx))
+
+/* Queues priority masks where _n and _p can be 0-3. */
+
+#define MAX_FRAME_SIZE 1522
+#define MIN_FRAME_SIZE 64
+
+static int debug_tsn = -1;
+module_param(debug_tsn, int, 0);
+MODULE_PARM_DESC(debug_tsn, "debug_tsn (0=off, 1=enabled)");
+
+/* For a full list of the registers dumped here, see sec 8.1.3 in the
+ * i210 controller datasheet.
+ */
+static inline void _tsn_dump_regs(struct igb_adapter *adapter)
+{
+ u32 val = 0;
+ struct device *dev;
+ struct e1000_hw *hw = &adapter->hw;
+
+ /* do not dump regs if we're not debugging driver */
+ if (debug_tsn != 1)
+ return;
+
+ dev = &adapter->pdev->dev;
+ dev_info(dev, "num_tx_queues=%d (netdev=%d, real=%d), num_rx_queues=%d (netdev=%d, real=%d)\n",
+ adapter->num_tx_queues, adapter->netdev->num_tx_queues, adapter->netdev->real_num_tx_queues,
+ adapter->num_rx_queues, adapter->netdev->num_rx_queues, adapter->netdev->real_num_rx_queues);
+
+ /* 0x0008 - E1000_STATUS Device status register */
+ val = rd32(E1000_STATUS);
+ dev_info(&adapter->pdev->dev, "\n");
+ dev_info(dev, "Status: FullDuplex=%s, LinkUp=%s, speed=0x%x\n",
+ val & 0x1 ? "FD" : "HD",
+ val & 0x2 ? "LU" : "LD",
+ val & 0xc0 >> 6);
+
+ /* E1000_VET vlan ether type */
+ val = rd32(E1000_VET);
+ dev_info(dev, "VLAN ether type: VET.VET=0x%04x, VET.VET_EXT=0x%04x\n",
+ val & 0xffff, (val >> 16) & 0xffff);
+
+ /* E1000_RXPBS (RXPBSIZE) Rx Packet Buffer Size */
+ val = rd32(E1000_RXPBS);
+ dev_info(dev, "Rx Packet buffer: RXPBSIZE=%dkB, Bmc2ospbsize=%dkB, cfg_ts_en=%s\n",
+ val & 0x1f,
+ (val >> 6) & 0x1f,
+ (val & (1 << 31)) ? "cfg_ts_en" : "cfg_ts_dis");
+
+ /* Transmit stuff */
+ /* E1000_TXPBS (TXPBSIZE) Tx Packet Buffer Size - RW */
+ val = rd32(E1000_TXPBS);
+ dev_info(dev, "Tx Packet buffer: Txpb0size=%dkB, Txpb1size=%dkB, Txpb2size=%dkB, Txpb3size=%dkB, os2Bmcpbsize=%dkB\n",
+ val & 0x3f, (val >> 6) & 0x3f, (val >> 12) & 0x3f,
+ (val >> 18) & 0x3f, (val >> 24) & 0x3f);
+
+ /* E1000_TCTL (TCTL) Tx control - RW*/
+ val = rd32(E1000_TCTL);
+ dev_info(dev, "Tx control reg: TxEnable=%s, CT=0x%X\n",
+ val & 2 ? "EN" : "DIS", (val >> 3) & 0x3F);
+
+ /* TQAVHC : Transmit Qav High credits 0x300C + 0x40*n - RW */
+ val = rd32(E1000_TQAVHC(0));
+ dev_info(dev, "E1000_TQAVHC0: %0x08x\n", val);
+ val = rd32(E1000_TQAVHC(1));
+ dev_info(dev, "E1000_TQAVHC1: %0x08x\n", val);
+
+ /* TQAVCC[0-1]: Transmit Qav 0x3004 + 0x40*n - RW */
+ val = rd32(E1000_TQAVCC(0));
+ dev_info(dev, "E1000_TQAVCC0: idleSlope=0x%02x, QueueMode=%s\n",
+ val % 0xff,
+ val > 31 ? "Stream reservation" : "Strict priority");
+ val = rd32(E1000_TQAVCC(1));
+ dev_info(dev, "E1000_TQAVCC1: idleSlope=0x%02x, QueueMode=%s\n",
+ val % 0xff,
+ val > 31 ? "Stream reservation" : "Strict priority");
+
+ /* TQAVCTRL : Transmit Qav control - RW */
+ val = rd32(E1000_TQAVCTRL);
+ dev_info(dev, "E1000_TQAVCTRL: TransmitMode=%s,1588_STAT_EN=%s,DataFetchARB=%s,DataTranARB=%s,DataTranTIM=%s,SP_WAIT_SR=%s,FetchTimDelta=%dns (0x%04x)\n",
+ (val & 0x0001) ? "Qav" : "Legacy",
+ (val & 0x0004) ? "En" : "Dis",
+ (val & 0x0010) ? "Most Empty" : "Round Robin",
+ (val & 0x0100) ? "Credit Shaper" : "Strict priority",
+ (val & 0x0200) ? "Valid" : "N/A",
+ (val & 0x0400) ? "Wait" : "nowait",
+ (val >> 16) * 32, (val >> 16));
+}
+
+/* Place the NIC in Qav-mode.
+ *
+ * This will result in a _single_ queue for normal BE traffic, the rest
+ * will be grabbed by the Qav-machinery and kept for strict priority
+ * transmission.
+ *
+ * I210 Datasheet Sec 7.2.7.7 gives a lot of information.
+ */
+void igb_tsn_init(struct igb_adapter *adapter)
+{
+ struct e1000_hw *hw = &adapter->hw;
+ u32 txpbsize;
+ u32 tqavctrl;
+
+ if (debug_tsn < 0 || debug_tsn > 1)
+ debug_tsn = 0;
+
+ if (!adapter->pdev) {
+ adapter->tsn_ready = 0;
+ return;
+ }
+
+ switch (adapter->pdev->device) {
+ case 0x1533: /* E1000_DEV_ID_I210_COPPER */
+ case 0x1536: /* E1000_DEV_ID_I210_FIBER */
+ case 0x1537: /* E1000_DEV_ID_I210_SERDES: */
+ case 0x1538: /* E1000_DEV_ID_I210_SGMII: */
+ case 0x157b: /* E1000_DEV_ID_I210_COPPER_FLASHLESS: */
+ case 0x157c: /* E1000_DEV_ID_I210_SERDES_FLASHLESS: */
+ break;
+ default:
+ /* not a known IGB-TSN capable device */
+ adapter->tsn_ready = 0;
+ return;
+ }
+ _tsn_dump_regs(adapter);
+
+ if (adapter->num_tx_queues != 4) {
+ pr_err("IGB_TSN: ERROR, not enough TX-queues available, need 4, got %d\n",
+ adapter->num_tx_queues);
+ return;
+ }
+
+ /* setup the Transmit Descriptor Control (see 8.12.15)
+ *
+ * Set a flag for priority-queue and call igb_configure_tx_ring() in igb_main.c
+ */
+ adapter->tx_ring[0]->flags |= IGB_FLAG_QAV_PRIO;
+ adapter->tx_ring[1]->flags |= IGB_FLAG_QAV_PRIO;
+ igb_configure_tx_ring(adapter, adapter->tx_ring[0]);
+ igb_configure_tx_ring(adapter, adapter->tx_ring[1]);
+
+ /* Set Tx packet buffer size assignment, see 7.2.7.7 in i210
+ * PB0: 8kB (default 20 kB)
+ * PB1: 8kB (default 0 pkB)
+ * PB2: 4kB (default 0 kB)
+ * PB3: 4kB (default 0 kB)
+ *
+ * os2bmcsize: 2kB (default 4 kB)
+
+ * UPDATE: set os2bmcpbsize to 0, then we can drop setting RX
+ * packet buffer
+ *
+ * sumTx: 24kB (26kB)
+ *
+ * Rxpbsize: 0x20 (32kB default 34kB
+ * bmc2ossize: 0x02 (default 0x02)
+ * sumRx: 34kB
+ *
+ * Total 60kB (see 4.5.9)
+ *
+ * See 8.3.1 && 8.3.2 for fields
+ */
+ txpbsize = (0 << 30 | 0x00 << 24 | 0x04 << 18 | 0x04 << 12 | 0x08 << 6 | 0x08);
+ wr32(E1000_ITPBS, txpbsize);
+
+ /* Since we dropped os2bmcsize, we do not have to change the
+ * default here after all */
+ /* wr32(E1000_IRPBS, 0x02 << 6 | 0x20); */
+
+ /* DMA Tx maximum packet size, the largest frame DMA should transport
+ * do not allow frames larger than 1522 + preample. Reg expects
+ * size in 64B increments. 802.1BA 6.3
+ * Round up to 1536 to handle 64B increments
+ *
+ * Initial value: 0x98 (152 => 9728 bytes)
+ */
+ wr32(E1000_DTXMXPKTSZ, 1536 >> 6);
+
+ /* For now, only set CreditBased shaper for A and B, do not set
+ * idleSlope as we have not yet gotten any streams. Set HiCredit
+ * to be unlimitied (this violates the 75% 'default' boundary
+ *
+ * 8.12.19
+ */
+ wr32(E1000_TQAVCC(0), E1000_TQAVCC_QUEUEMODE);
+ wr32(E1000_TQAVCC(1), E1000_TQAVCC_QUEUEMODE);
+ wr32(E1000_TQAVHC(0), E1000_TQAVCH_UNLIM_CREDIT);
+ wr32(E1000_TQAVHC(1), E1000_TQAVCH_UNLIM_CREDIT);
+
+ /* Place card in Qav-mode, use tx-queue 0,1 for Qav
+ * (Credit-based shaper), 2,3 for standard priority (and
+ * best-effort) traffic.
+ *
+ * i210 8.12.19 and 8.12.21
+ *
+ * - Fetch: most empty and time based (not round-robin)
+ * - Transmit: Credit based shaper for SR queues
+ * - Data launch time valid (in Qav mode) is off (we do not want
+ * time-triggered launch)
+ * - Wait for SR queues to ensure that launch time is always valid.
+ * - Set ~10us wait-time-delta, 32ns granularity
+ */
+ tqavctrl = E1000_TQAVCTRL_TXMODE | \
+ E1000_TQAVCTRL_DATA_FETCH_ARB | \
+ E1000_TQAVCTRL_DATA_TRAN_ARB | \
+ E1000_TQAVCTRL_SP_WAIT_SR | \
+ 320 << E1000_TQAVCTRL_FETCH_TM_SHIFT;
+ wr32(E1000_TQAVCTRL, tqavctrl);
+
+ /* reset Tx Descriptor tail and head for the queues */
+ wr32(E1000_TDT(0), 0);
+ wr32(E1000_TDT(1), 0);
+ wr32(E1000_TDH(0), 0);
+ wr32(E1000_TDH(1), 0);
+
+ _tsn_dump_regs(adapter);
+ dev_info(&adapter->pdev->dev, "\n");
+
+ adapter->sra_idleslope_res = 0;
+ adapter->srb_idleslope_res = 0;
+ adapter->tsn_ready = 1;
+ adapter->tsn_vlan_added = 0;
+
+ dev_info(&adapter->pdev->dev, "%s: setup done\n", __func__);
+}
+
+int igb_tsn_capable(struct net_device *netdev)
+{
+ struct igb_adapter *adapter;
+
+ if (!netdev)
+ return -EINVAL;
+ adapter = netdev_priv(netdev);
+ return adapter->tsn_ready == 1;
+}
+
+/* igb_tsn_link_configure - configure NIC to handle a new stream
+ *
+ * @netdev: pointer to NIC device
+ * @class: the class for the stream used to find the correct queue.
+ * @framesize: size of each frame, *including* headers (not preamble)
+ * @vid: VLAN ID
+ *
+ * NOTE: the sr_class only instructs the driver which queue to use, not
+ * what priority the network expects for a given class. This is
+ * something userspace must find out and then let the tsn-driver set in
+ * the frame before xmit.
+ *
+ * FIXME: remove bw-req from a stream that goes away.
+ */
+int igb_tsn_link_configure(struct net_device *netdev, enum sr_class class,
+ u16 framesize, u16 vid, u8 add_link, u8 pcp_hi, u8 pcp_lo)
+{
+ s32 idle_slope = 0;
+ s32 frames_pr_ms;
+ s32 new_is = 0;
+ u32 tqavcc;
+ int err;
+
+ struct igb_adapter *adapter;
+ struct e1000_hw *hw;
+
+ if (!netdev)
+ return -EINVAL;
+ adapter = netdev_priv(netdev);
+ hw = &adapter->hw;
+
+ if (!igb_tsn_capable(netdev)) {
+ pr_err("%s: NIC not capable\n", __func__);
+ return -EINVAL;
+ }
+
+ if (framesize > MAX_FRAME_SIZE || framesize < MIN_FRAME_SIZE) {
+ pr_err("%s: framesize (%u) must be [%d,%d]\n", __func__,
+ framesize, MIN_FRAME_SIZE, MAX_FRAME_SIZE);
+ return -EINVAL;
+ }
+
+ /*
+ * FIXME: This should disable EEE and (possibly) enable it when
+ * removing the last link.
+ *
+ * FIXME: This is only done the first time and have the
+ * potentional of never being reset as we do not count the
+ * number of configured links.
+ *
+ * This means that if all links goes away and the network
+ * reconfigures the domain to use different PCPs, we will drop
+ * that.
+ *
+ * FIXME: this update is racy
+ */
+ if (add_link && !adapter->tsn_vlan_added) {
+ rtnl_lock();
+ pr_info("%s: adding VLAN %u to HW filter on device %s\n",
+ __func__, vid, netdev->name);
+ err = vlan_vid_add(netdev, htons(ETH_P_8021Q), vid);
+ if (err != 0)
+ pr_err("%s: error adding vlan %u, res=%d\n",
+ __func__, vid, err);
+ rtnl_unlock();
+ adapter->pcp_hi = pcp_hi & 0x7;
+ adapter->pcp_lo = pcp_lo & 0x7;
+ adapter->tsn_vlan_added = 1;
+ }
+
+ /* we currently drop hicred (we have set this to unlim to ease calculation) */
+ /* Grab current values of idle_slope
+ */
+ switch(class) {
+ case SR_CLASS_A:
+ tqavcc = rd32(E1000_TQAVCC(0));
+ frames_pr_ms = 8;
+ break;
+ case SR_CLASS_B:
+ tqavcc = rd32(E1000_TQAVCC(1));
+ frames_pr_ms = 4;
+ break;
+ default:
+ pr_err("igb_tsn: unknown traffic-class, aborting configuration\n");
+ return -EINVAL;
+ }
+ idle_slope = tqavcc & E1000_TQAVCC_IDLE_SLOPE_MASK;
+
+ /* Calculate new idle slope and add to appropriate idle_slope
+ * idle_slope = BW * linkrate * 2 (0r 0.2 for 100Mbit)
+ * BW: % of total bandwidth
+ *
+ * E1000_LINK_RATE: 0x7735
+ * LINE_SPEED: 1e9
+ */
+ new_is = (s32)framesize * frames_pr_ms * E1000_LINK_RATE * 2 * 8;
+ if (new_is % 1000000)
+ new_is += 1000000;
+ new_is /= 1000000;
+
+ pr_info("Framesize=%u,E1000_LINK_RATE=%u,class=%s,new_is=%s%d,current_is=%u\n",
+ framesize,E1000_LINK_RATE,
+ (class == SR_CLASS_A ? "A" : "B"),
+ (add_link ? "+" : "-"),
+ new_is, idle_slope);
+ if (!add_link)
+ new_is *= -1;
+ idle_slope += new_is;
+
+ tqavcc &= ~E1000_TQAVCC_IDLE_SLOPE_MASK;
+ tqavcc |= idle_slope & E1000_TQAVCC_IDLE_SLOPE_MASK;
+ if (class == SR_CLASS_A) {
+ wr32(E1000_TQAVCC(0), tqavcc);
+ adapter->sra_idleslope_res += (s16)new_is;
+ } else {
+ wr32(E1000_TQAVCC(1), tqavcc);
+ adapter->srb_idleslope_res += (s16)new_is;
+ }
+
+ if (adapter->sra_idleslope_res == 0 && adapter->srb_idleslope_res == 0) {
+ /* removing last stream going through NIC, drop vlan and
+ * make it possible to change PCP */
+ rtnl_lock();
+ vlan_vid_del(netdev, htons(ETH_P_8021Q), vid);
+ adapter->tsn_vlan_added = 0;
+ rtnl_unlock();
+ }
+ _tsn_dump_regs(netdev_priv(netdev));
+ pr_info("igb_tsn_link_configure: done setting up TSN, idle_slope: %u,for frame: %u\n",
+ idle_slope, new_is);
+
+ return 0;
+}
+
+u16 igb_tsn_select_queue(struct net_device *netdev, struct sk_buff *skb,
+ void *accel_priv, select_queue_fallback_t fallback)
+{
+ struct igb_adapter *adapter = netdev_priv(netdev);
+ if (!adapter)
+ return fallback(netdev, skb);
+
+ /* we only return the special queue(s) for tsn-traffic, and gPTP
+ * otherwise we pick the last queue
+ */
+ if (igb_tsn_capable(netdev)) {
+ switch (vlan_get_protocol(skb)) {
+ case htons(ETH_P_TSN):
+ if (skb->priority == adapter->pcp_hi)
+ return 0;
+ if (skb->priority == adapter->pcp_lo)
+ return 1;
+ pr_err("igb_tsn select queu: unknown PCP:%u, expected either hi=%u or lo=%u\n",
+ skb->priority, adapter->pcp_hi, adapter->pcp_lo);
+ break;
+ case htons(ETH_P_1588):
+ /* PTP should be sent via tx-queue 2 */
+ return 2;
+ default:
+ /* rest via 3 */
+ return 3;
+ }
+ }
+ return fallback(netdev, skb);
+}
+#endif /* #if IS_ENABLED(CONFIG_IGB_TSN) */
--
2.7.4
From: Henrik Austad <[email protected]>
This exposes a *very* rudimentary and simplistic ALSA driver that hooks
into TSN to create a device for userspace.
It currently only supports 44.1/48kHz sampling, 2ch, S16_LE
Userspace is supposed to reserve bandwidth, find StreamID etc.
To use as a Talker:
mkdir /config/tsn/test/eth0/talker
cd /config/tsn/test/eth0/talker
echo 65535 > buffer_size
echo 08:00:27:08:9f:c3 > remote_mac
echo 42 > stream_id
echo alsa > shim
echo on > enabled
aplay -Ddefault:CARD=avb music.wav
or
arecord -r48000 -c2 -f S16_LE | aplay -Ddefault:CARD=avb
alternatively, if the device is set as Listener;
arecord -r48000 -c2 -f S16_LE -Ddefault:CARD=avb > file.wav
Cc: Mauro Carvalho Chehab <[email protected]>
Cc: Takashi Iwai <[email protected]>
Cc: Mark Brown <[email protected]>
Signed-off-by: Henrik Austad <[email protected]>
---
drivers/media/Kconfig | 15 +
drivers/media/Makefile | 2 +-
drivers/media/avb/Makefile | 5 +
drivers/media/avb/avb_alsa.c | 793 +++++++++++++++++++++++++++++++++++++++
drivers/media/avb/tsn_iec61883.h | 152 ++++++++
5 files changed, 966 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/avb/Makefile
create mode 100644 drivers/media/avb/avb_alsa.c
create mode 100644 drivers/media/avb/tsn_iec61883.h
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
index 7b85402..8250aff 100644
--- a/drivers/media/Kconfig
+++ b/drivers/media/Kconfig
@@ -221,3 +221,18 @@ source "drivers/media/tuners/Kconfig"
source "drivers/media/dvb-frontends/Kconfig"
endif # MEDIA_SUPPORT
+
+config MEDIA_AVB_ALSA
+ tristate "ALSA part of AVB over TSN"
+ depends on TSN
+ help
+
+ Enable the ALSA device that hoooks into TSN and allows the
+ computer to send ethernet frames over the network carrying
+ audio-data to selected hosts.
+
+ This must be configured by userspace as MSRP and IEEE 1722.1
+ (discovery and enumeration) is not implemented within the
+ kernel.
+
+ If unsure, say N
\ No newline at end of file
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index 0deaa93..9dfee62 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -34,4 +34,4 @@ obj-y += rc/
obj-y += common/ platform/ pci/ usb/ mmc/ firewire/ spi/
obj-$(CONFIG_VIDEO_DEV) += radio/
-
+obj-$(CONFIG_MEDIA_AVB_ALSA) += avb/
diff --git a/drivers/media/avb/Makefile b/drivers/media/avb/Makefile
new file mode 100644
index 0000000..5d6302c
--- /dev/null
+++ b/drivers/media/avb/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the ALSA shim in AVB/TSN
+#
+
+obj-$(CONFIG_MEDIA_AVB_ALSA) += avb_alsa.o
diff --git a/drivers/media/avb/avb_alsa.c b/drivers/media/avb/avb_alsa.c
new file mode 100644
index 0000000..bd202f5
--- /dev/null
+++ b/drivers/media/avb/avb_alsa.c
@@ -0,0 +1,793 @@
+/* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights
+ * reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <linux/platform_device.h>
+#include <sound/pcm_params.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <linux/tsn.h>
+#include "tsn_iec61883.h"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for AVB soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for AVB soundcard.");
+
+struct avb_chip {
+ struct snd_card *card;
+ struct tsn_link *link;
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *substream;
+
+ /* Need a reference to this when we unregister the platform
+ * driver.
+ */
+ struct platform_device *device;
+
+ /* on first copy, we set a few values, use this to make sure we
+ * only do this once.
+ */
+ u8 first_copy;
+
+ u8 sample_size;
+ u8 channels;
+
+ /* current idx in 10ms set of frames
+ * class A: 80
+ * class B: 40
+ *
+ * This is mostly relevant for 44.1kHz samplefreq
+ */
+ u8 num_10ms_series;
+
+ u32 sample_freq;
+};
+
+/* currently, only playback is implemented in TSN layer
+ *
+
+ * FIXMEs: (should be set according to the active TSN link)
+ * - format
+ * - rates
+ * - channels
+ *
+ * report absolute hardware link audio time, not reset on startup
+ * (SNDRV_PCM_INFO_HAS_LINK_ABSOLUTE_ATIME). We are using gPTP time for
+ * arrival/transmit of frames, this will be handled by
+ * tsn_(read|write)_buffer_net()
+ */
+static struct snd_pcm_hardware snd_avb_hw = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_HAS_LINK_ABSOLUTE_ATIME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* we currently expect either class A or B, where A has the
+ * smallest frames. For 44.1, you can get 11 sample-sets in one
+ * frame, and for 2ch S16_LE, this gives us 44 bytes.
+ */
+ .period_bytes_min = 44,
+ .period_bytes_max = 32768,
+ .buffer_bytes_max = 32768,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static size_t snd_avb_copy_size(struct tsn_link *link);
+
+
+static int _set_chip_values(struct avb_chip *avb_chip,
+ struct snd_pcm_runtime *runtime)
+{
+ if (!avb_chip->first_copy)
+ return 0;
+
+
+ /*
+ * first copy, we now know that runtime has all the correct
+ * values set, we can grab channels and rate. Sample_size
+ * (runtime->format) is currently hard-coded to S16_LE.
+ */
+ avb_chip->channels = runtime->channels;
+ avb_chip->sample_freq = runtime->rate;
+ avb_chip->sample_size = 16;
+
+ if (snd_avb_copy_size(avb_chip->link) > avb_chip->link->max_payload_size) {
+ pr_err("%s: Resulting payload-size is larger (%zd) than available (%u)\n",
+ __func__, snd_avb_copy_size(avb_chip->link),
+ avb_chip->link->max_payload_size);
+ return -EINVAL;
+ }
+ avb_chip->first_copy = 0;
+ return 0;
+}
+
+static int _snd_avb_open(struct avb_chip *avb_chip,
+ struct snd_pcm_runtime *runtime)
+{
+ /*
+ * We do not know what some of these values are until we see the
+ * first copy. We set to sane defaults where we don't have exact
+ * content.
+ */
+ avb_chip->channels = 0;
+ avb_chip->sample_size = 0;
+ avb_chip->sample_freq = 0;
+ avb_chip->num_10ms_series = 0;
+ avb_chip->first_copy = 1;
+
+ runtime->hw = snd_avb_hw;
+ runtime->buffer_size = avb_chip->link->buffer_size;
+ return 0;
+}
+
+/*
+ * bytes_to_frames()
+ * frames_to_bytes()
+ *
+ * frames_to_bytes(runtime, runtrime->period_size);
+ *
+ * Interrupt callbacks:
+ * The field traonsfer_ack_begin and transfer_ack_end are called at the
+ * beginning and at the end of snd_pcm_period_elapsed(), respectively.
+ */
+static int snd_avb_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+ int ret = 0;
+
+ /*
+ * we've opened the PCM before probe returned properly and
+ * stored link in the struct.
+ */
+ if (!avb_chip || !avb_chip->link) {
+ pr_err("%s: Chip-data or link not available, cannot continue\n",
+ __func__);
+ return -EINVAL;
+ }
+ if (!avb_chip->link->estype_talker) {
+ pr_info("Link (%llu) not registered as Talker, cannot do playback\n",
+ avb_chip->link->stream_id);
+ return -EINVAL;
+ }
+
+ ret = _snd_avb_open(avb_chip, runtime);
+ if (ret < 0) {
+ pr_err("%s: Could not open playback-device (requested %d ch, %zd buffer)",
+ __func__, avb_chip->channels,
+ avb_chip->link->buffer_size);
+ return ret;
+ }
+ pr_info("%s: %d channel PCM stream opened successfully, buffersize: %zd\n",
+ __func__, avb_chip->channels, avb_chip->link->buffer_size);
+ return 0;
+}
+
+static int snd_avb_playback_close(struct snd_pcm_substream *substream)
+{
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+
+ tsn_lb_disable(avb_chip->link);
+
+ pr_info("%s: something happened\n", __func__);
+ return 0;
+}
+
+/*
+ * snd_avb snd_avb.0: BUG: ,
+ * pos = 12288,
+ * buffer size = 8192,
+ * period size = 2048
+ *
+ * Playback is when we *send* data to a remote speaker
+ */
+static int snd_avb_playback_copy(struct snd_pcm_substream *substream,
+ int channel,
+ snd_pcm_uframes_t pos,
+ void *src,
+ snd_pcm_uframes_t count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+ size_t bytes;
+ int ret;
+
+ /*
+ * From alsadoc:
+ *
+ * You need to check the channel argument, and if it's -1, copy
+ * the whole channels. Otherwise, you have to copy only the
+ * specified channel. Please check isa/gus/gus_pcm.c as an
+ * example.
+ */
+ if (channel != -1) {
+ pr_err("%s: partial copy not supportet\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = _set_chip_values(avb_chip, runtime);
+ if (ret != 0)
+ return ret;
+
+ bytes = frames_to_bytes(runtime, count);
+ ret = tsn_buffer_write(avb_chip->link, src, bytes);
+ if (ret != bytes) {
+ pr_err("%s: Incorrect copy (%zd, %d) corruption possible\n",
+ __func__, bytes, ret);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int snd_avb_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+ int ret = 0;
+
+ if (!avb_chip || !avb_chip->link) {
+ pr_err("%s: Chip-data or link not available, cannot continue\n",
+ __func__);
+ return -EINVAL;
+ }
+ if (avb_chip->link->estype_talker) {
+ pr_info("Link (%llu) registered as Talker, cannot capture\n",
+ avb_chip->link->stream_id);
+ return -EINVAL;
+ }
+ ret = _snd_avb_open(avb_chip, runtime);
+ if (ret < 0) {
+ pr_err("%s: Could not open capture-device (requested %d ch, %zd buffer)",
+ __func__, avb_chip->channels,
+ avb_chip->link->buffer_size);
+ return ret;
+ }
+ tsn_lb_enable(avb_chip->link);
+ pr_info("%s: %d channel PCM stream opened successfully, buffersize: %zd\n",
+ __func__, avb_chip->channels, avb_chip->link->buffer_size);
+ return 0;
+}
+
+static int snd_avb_capture_close(struct snd_pcm_substream *substream)
+{
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+
+ if (!avb_chip || !avb_chip->link)
+ return -EINVAL;
+ pr_err("%s: closing stream\n", __func__);
+
+ tsn_lb_disable(avb_chip->link);
+
+ return 0;
+}
+
+static int snd_avb_capture_copy(struct snd_pcm_substream *substream,
+ int channel,
+ snd_pcm_uframes_t pos,
+ void *src,
+ snd_pcm_uframes_t count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+ size_t bytes;
+ int ret;
+
+ bytes = frames_to_bytes(runtime, count);
+ ret = tsn_buffer_read(avb_chip->link, src, bytes);
+ if (ret != bytes) {
+ pr_err("%s: incorrect copy (%zd, %d), corrupt capture possible\n",
+ __func__, bytes, ret);
+ tsn_lb_disable(avb_chip->link);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int snd_avb_silence(struct snd_pcm_substream *substream,
+ int channel, snd_pcm_uframes_t pos,
+ snd_pcm_uframes_t count)
+{
+ /* FIXME, should do more than nothing */
+ return 0;
+}
+
+/*
+ * Called when the client defines buffer_size, period_size, format etc
+ * for the pcm substream.
+ *
+ * This is where link->buffer is allocated and link->buffer_size is
+ * defined.
+ *
+ * We are called in the beginning of snd_pcm_hw_params in
+ * sound/core/pcm_native.c, we cannot override runtime-values as they
+ * are updated from hw_params.
+ */
+static int snd_avb_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+ unsigned int bsize = params_buffer_bytes(hw_params);
+ int ret = 0;
+
+ /* We need this reference for the refill callback so that we can
+ * call snd_pcm_period_elapsed();
+ */
+ avb_chip->substream = substream;
+ ret = tsn_set_buffer_size(avb_chip->link, bsize);
+ if (ret < 0) {
+ pr_err("%s: could not set buffer_size (alsa requested too large? (%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ avb_chip->num_10ms_series = 0;
+ pr_info("%s: successfully set hw-params\n", __func__);
+out:
+ return ret;
+}
+
+static int snd_avb_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+
+ if (!avb_chip || !avb_chip->link)
+ return -EINVAL;
+ tsn_clear_buffer_size(avb_chip->link);
+ pr_info("%s: something happened\n", __func__);
+ avb_chip->substream = NULL;
+ return 0;
+}
+
+static int snd_avb_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ /* verify that samplerate, freq and size is what we have set in
+ * the link.
+ */
+
+ return 0;
+}
+
+/*
+ * When the PCM stream is started, stopped, paused etc.
+ *
+ * Atomic function (some lock is being held by PCM layer)
+ */
+static int snd_avb_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ /* pr_err("%s: starting for some reason\n", __func__); */
+ tsn_lb_enable(avb_chip->link);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ /* memset buffer to 0 */
+ /* pr_err("%s: stopping for some reason\n", __func__); */
+ tsn_lb_disable(avb_chip->link);
+ break;
+ default:
+ pr_info("%s: cmd: %d (return -EINVAL)\n", __func__, cmd);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int snd_avb_get_time_info(struct snd_pcm_substream *substream,
+ struct timespec *system_ts, struct timespec *audio_ts,
+ struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
+ struct snd_pcm_audio_tstamp_report *audio_tstamp_report)
+{
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+ struct tsn_link *link = avb_chip->link;
+ u64 nsec;
+
+ snd_pcm_gettime(substream->runtime, system_ts);
+
+ /* We only support link-time from sample, absolute (no reset) */
+ if ((substream->runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_ABSOLUTE_ATIME) &&
+ (audio_tstamp_config->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_ABSOLUTE)) {
+ tsn_lock(link);
+ nsec = link->ts_net_ns;
+ tsn_unlock(link);
+
+ *audio_ts = ns_to_timespec(nsec);
+ audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_ABSOLUTE;
+
+ /* this should be accurate, but leave it as inaccurate
+ * until we know the level for gPTP-clock in the system
+ * accuracy_report = 1 (timestamp is accurate to within accuracy)
+ * accuracy: in ns
+ */
+ audio_tstamp_report->accuracy_report = 0;
+ audio_tstamp_report->accuracy = 42;
+ } else {
+ audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT;
+ }
+
+ return 0;
+}
+
+/*
+ * current hw-position in the buffer, in frames from 0 to buffer_size -1
+ *
+ * Need to know where the hw-pointer is and how this corresponds to the
+ * underlying TSN-buffer setup
+ *
+ * Atomic function (some lock is being held by PCM layer)
+ *
+ */
+static snd_pcm_uframes_t snd_avb_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct avb_chip *avb_chip = snd_pcm_substream_chip(substream);
+ struct tsn_link *link = avb_chip->link;
+ snd_pcm_uframes_t pointer;
+
+ if (link->estype_talker)
+ pointer = bytes_to_frames(substream->runtime,
+ link->tail - link->buffer);
+ else
+ pointer = bytes_to_frames(substream->runtime,
+ link->head - link->buffer);
+ return pointer;
+}
+
+static struct snd_pcm_ops snd_avb_playback_ops = {
+ .open = snd_avb_playback_open,
+ .close = snd_avb_playback_close,
+ .copy = snd_avb_playback_copy,
+ .silence = snd_avb_silence,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_avb_pcm_hw_params,
+ .hw_free = snd_avb_pcm_hw_free,
+ .prepare = snd_avb_pcm_prepare,
+ .trigger = snd_avb_pcm_trigger,
+ .pointer = snd_avb_pcm_pointer,
+ .get_time_info = snd_avb_get_time_info,
+};
+
+static struct snd_pcm_ops snd_avb_capture_ops = {
+ .open = snd_avb_capture_open,
+ .close = snd_avb_capture_close,
+ .copy = snd_avb_capture_copy,
+ .silence = snd_avb_silence,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_avb_pcm_hw_params,
+ .hw_free = snd_avb_pcm_hw_free,
+ .prepare = snd_avb_pcm_prepare,
+ .trigger = snd_avb_pcm_trigger,
+ .pointer = snd_avb_pcm_pointer,
+ .get_time_info = snd_avb_get_time_info,
+};
+
+/*
+ * Callback for tsn_core for moving data into the buffer.
+ *
+ * This should be a wrapper (replace it with) the refill-functionality ALSA use.
+ */
+static size_t snd_avb_refill(struct tsn_link *link)
+{
+ struct avb_chip *avb_chip = link->media_chip;
+
+ if (avb_chip && avb_chip->substream) {
+ snd_pcm_period_elapsed(avb_chip->substream);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static size_t snd_avb_drain(struct tsn_link *link)
+{
+ struct avb_chip *avb_chip = link->media_chip;
+
+ if (avb_chip && avb_chip->substream) {
+ snd_pcm_period_elapsed(avb_chip->substream);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static size_t snd_avb_hdr_size(struct tsn_link *link)
+{
+ /* return the size of the iec61883-6 audio header */
+ return _iec61883_hdr_len();
+}
+
+static size_t snd_avb_copy_size(struct tsn_link *link)
+{
+ struct avb_chip *chip = link->media_chip;
+ /* use values in avb_chip, not link */
+ size_t framesize = (chip->sample_size >> 3) * chip->channels;
+ size_t numframes = 0;
+
+ if (!chip->sample_freq)
+ return link->max_payload_size;
+
+ /* size of each frame (samples per frame, sample-size && class)
+ * sample_size: 16 -> 2
+ * spframe : 12 (class b)
+ * channels: 2
+ *
+ * framesize: 2*12*2 -> 48
+ */
+
+ switch (chip->sample_freq) {
+ case 44100:
+ /*
+ * Class B: 40 frames, first 12 bytes, next 39 should be 11
+ */
+ if (link->class == SR_CLASS_B) {
+ numframes = (chip->num_10ms_series ? 11 : 12);
+ chip->num_10ms_series++;
+ if (chip->num_10ms_series > 39)
+ chip->num_10ms_series = 0;
+ } else {
+ /* Class A slightly more involved
+ * Need 41 6 bytes and 39 5 bytes
+ *
+ * If 0th is set to 6, remaining odd idx should
+ * be 6, even (except 0th) to be 6
+ */
+ numframes = 5;
+ if (!chip->num_10ms_series ||
+ (chip->num_10ms_series % 0x2))
+ numframes++;
+ chip->num_10ms_series++;
+ if (chip->num_10ms_series > 79)
+ chip->num_10ms_series = 0;
+ }
+ break;
+ case 48000:
+ numframes = (link->class == SR_CLASS_A ? 6 : 12);
+ break;
+ default:
+ pr_err("Unsupported sample_freq (%d), disabling link\n",
+ chip->sample_freq);
+ tsn_lb_disable(link);
+ return -EINVAL;
+ }
+ return numframes * framesize;
+}
+
+static void snd_avb_assemble_iidc(struct tsn_link *link,
+ struct avtpdu_header *header, size_t bytes)
+{
+ _iec61883_hdr_assemble(header, bytes);
+}
+
+static int snd_avb_validate_iidc(struct tsn_link *link,
+ struct avtpdu_header *header)
+{
+ return _iec61883_hdr_verify(header);
+}
+
+static void *snd_avb_get_payload_data(struct tsn_link *link,
+ struct avtpdu_header *header)
+{
+ return _iec61883_payload(header);
+}
+
+static int snd_avb_new_pcm(struct avb_chip *avb_chip, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(avb_chip->card, "AVB PCM", device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+ pcm->private_data = avb_chip;
+ strcpy(pcm->name, "AVB PCM");
+ avb_chip->pcm = pcm;
+
+ /* only playback at the moment, once we implement capture, we
+ * need to grab the Talker/Listener from TSN link
+ */
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_avb_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_avb_capture_ops);
+
+ return 0;
+
+}
+
+static int snd_avb_probe(struct platform_device *devptr)
+{
+ int err;
+ struct snd_card *card;
+ struct avb_chip *avb_chip;
+ int dev = devptr->id;
+
+ pr_info("%s: starting\n", __func__);
+
+ err = snd_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct avb_chip), &card);
+ if (err < 0) {
+ pr_err("%s: trouble creating new card -> %d\n",
+ __func__, err);
+ return err;
+ }
+ avb_chip = card->private_data;
+ avb_chip->card = card;
+
+
+ /* create PCM device*/
+ err = snd_avb_new_pcm(avb_chip, 0);
+ if (err < 0) {
+ pr_err("%s: could not create new PCM device\n", __func__);
+ goto err_out;
+ }
+
+ /* register card */
+ pr_info("%s: ready to register card\n", __func__);
+ strcpy(card->driver, "Avb");
+ strcpy(card->shortname, "Avb");
+ sprintf(card->longname, "Avb %i", devptr->id + 1);
+ err = snd_card_register(card);
+ if (err < 0) {
+ pr_err("%s: Could not register card -> %d\n",
+ __func__, err);
+ snd_card_free(card);
+ return err;
+ }
+
+ if (err == 0) {
+ platform_set_drvdata(devptr, card);
+ pr_info("%s: Successfully initialized %s\n",
+ __func__, card->shortname);
+ return 0;
+ }
+err_out:
+ snd_card_free(card);
+ return err;
+}
+
+/*
+ * We are here as a result from being removed via
+ * tsn_link->shim_ops->media_close, which is snd_avb_close()
+ */
+static int snd_avb_remove(struct platform_device *devptr)
+{
+ struct snd_card *card = platform_get_drvdata(devptr);
+ struct avb_chip *avb_chip = card->private_data;
+
+ /* Make sure link holds no ref to this now dead card */
+ if (avb_chip && avb_chip->link) {
+ avb_chip->link->media_chip = NULL;
+ avb_chip->link = NULL;
+ }
+
+ /* call into link->ops->media_close() ? */
+ snd_card_free(card);
+ return 0;
+}
+
+static struct platform_driver snd_avb_driver = {
+ .probe = snd_avb_probe,
+ .remove = snd_avb_remove,
+ .driver = {
+ .name = "snd_avb",
+ .pm = NULL, /* don't care about Power Management */
+ },
+};
+
+static int snd_avb_close(struct tsn_link *link)
+{
+ struct avb_chip *avb_chip = link->media_chip;
+
+ if (!link->media_chip)
+ return 0;
+
+ pr_info("%s: Removing device\n", __func__);
+
+ platform_device_unregister(avb_chip->device);
+ /* platform unregister will call into snd_avb_remove */
+ platform_driver_unregister(&snd_avb_driver);
+
+ /* update link to remove pointer to now invalid memory */
+ link->media_chip = NULL;
+ return 0;
+}
+
+static int snd_avb_new(struct tsn_link *link)
+{
+ struct avb_chip *avb_chip;
+ struct snd_card *card;
+ struct platform_device *device;
+ int err;
+
+ err = platform_driver_register(&snd_avb_driver);
+ if (err < 0) {
+ pr_info("%s: trouble registering driver %d, unreg. partial driver and abort.\n",
+ __func__, err);
+ return err;
+ }
+
+ /*
+ * We only register a single card for now, look to
+ * /sys/devices/platform/snd_avb.0 for content.
+ *
+ * Probe will be triggered if name is same as .name in platform_driver
+ */
+ device = platform_device_register_simple("snd_avb", 0, NULL, 0);
+ if (IS_ERR(device)) {
+ pr_info("%s: ERROR registering simple platform-device\n",
+ __func__);
+ platform_driver_unregister(&snd_avb_driver);
+ return -ENODEV;
+ }
+
+ /* store data in driver so we can access it in .probe */
+ card = platform_get_drvdata(device);
+ if (card == NULL) {
+ pr_info("%s: Did not get anything from platform_get_drvdata()\n",
+ __func__);
+ platform_device_unregister(device);
+ return -ENODEV;
+ }
+ avb_chip = card->private_data;
+ avb_chip->device = device;
+ avb_chip->link = link;
+
+ link->media_chip = avb_chip;
+
+ return 0;
+}
+
+static struct tsn_shim_ops shim_ops = {
+ .shim_name = "alsa",
+ .probe = snd_avb_new,
+ .buffer_refill = snd_avb_refill,
+ .buffer_drain = snd_avb_drain,
+ .media_close = snd_avb_close,
+ .hdr_size = snd_avb_hdr_size,
+ .copy_size = snd_avb_copy_size,
+ .assemble_header = snd_avb_assemble_iidc,
+ .validate_header = snd_avb_validate_iidc,
+ .get_payload_data = snd_avb_get_payload_data,
+};
+
+static int __init avb_alsa_init(void)
+{
+ if (tsn_shim_register_ops(&shim_ops)) {
+ pr_err("Could not register ALSA-shim with TSN\n");
+ return -EINVAL;
+ }
+ pr_info("AVB ALSA added OK\n");
+ return 0;
+}
+
+static void __exit avb_alsa_exit(void)
+{
+ tsn_shim_deregister_ops(&shim_ops);
+}
+
+module_init(avb_alsa_init);
+module_exit(avb_alsa_exit);
+MODULE_AUTHOR("Henrik Austad");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TSN ALSA shim driver");
diff --git a/drivers/media/avb/tsn_iec61883.h b/drivers/media/avb/tsn_iec61883.h
new file mode 100644
index 0000000..167bfd8
--- /dev/null
+++ b/drivers/media/avb/tsn_iec61883.h
@@ -0,0 +1,152 @@
+#ifndef TSN_IEC61883_H
+#define TSN_IEC61883_H
+#include <linux/tsn.h>
+
+/*
+ * psh:
+ * tag:2
+ * channel:6
+ * tcode:4
+ * sy:4
+ * See IEEE 1722.1 :: 6.2 for details
+ */
+struct iec61883_tag {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 tag:2;
+ u8 channel:6;
+ u8 tcode:4;
+ u8 sy:4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 channel:6;
+ u8 tag:2;
+ u8 sy:4;
+ u8 sy:4;
+#else
+#error "Unknown Endianness, cannot determine bitfield ordering"
+#endif
+} __packed;
+
+struct iec61883_audio_header {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 sid:6;
+ u8 cip_1:2;
+
+ u8 dbs:8;
+
+ u8 rsv:2; /* reserved */
+ u8 sph:1;
+ u8 qpc:3;
+ u8 fn:2;
+
+ u8 dbc;
+
+ u8 fmt:6;
+ u8 cip_2:2;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 cip_1:2;
+ u8 sid:6;
+
+ u8 dbs:8;
+
+ u8 fn:2;
+ u8 qpc:3;
+ u8 sph:1;
+ u8 rsv:2; /* reserved */
+
+ u8 dbc;
+
+ u8 cip_2:2;
+ u8 fmt:6;
+#else
+#error "Unknown Endianness, cannot determine bitfield ordering"
+#endif
+ u8 fdf;
+ u16 syt;
+ u8 payload[0];
+} __packed;
+
+static inline size_t _iec61883_hdr_len(void)
+{
+ return sizeof(struct iec61883_audio_header);
+}
+
+static inline int _iec61883_hdr_verify(struct avtpdu_header *hdr)
+{
+ struct iec61883_audio_header *dh;
+ struct iec61883_tag *psh;
+
+ if (hdr->subtype != TSN_61883_IIDC)
+ return -EINVAL;
+ dh = (struct iec61883_audio_header *)&hdr->data;
+ psh = (struct iec61883_tag *)&hdr->psh;
+
+ /* Verify 61883 header */
+ if (psh->tag != 1 || psh->channel != 31 ||
+ psh->tcode != 0xA || psh->sy != 0)
+ return -EINVAL;
+
+ /* check flags that should be static from frame to frame */
+ if (dh->cip_1 != 0 || dh->sid != 0x3f || dh->qpc != 0 || dh->fn != 0 ||
+ dh->sph != 0 || dh->cip_2 != 2)
+ return -EINVAL;
+
+ if (dh->dbs != ntohs(hdr->sd_len)*2 || dh->dbc != hdr->seqnr)
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline void _iec61883_hdr_assemble(struct avtpdu_header *hdr,
+ size_t bytes)
+{
+ struct iec61883_tag *psh;
+ struct iec61883_audio_header *dh;
+
+ if (bytes > 0x7f)
+ pr_warn("%s: hdr->dbs will overflow, malformed frame will be the result\n",
+ __func__);
+
+
+ hdr->subtype = TSN_61883_IIDC;
+
+ /* IIDC 61883 header */
+ psh = (struct iec61883_tag *)&hdr->psh;
+ psh->tag = 1;
+ psh->channel = 31; /* 0x1f */
+ psh->tcode = 0xA;
+ psh->sy = 0;
+
+ dh = (struct iec61883_audio_header *)&hdr->data;
+ dh->cip_1 = 0;
+ dh->sid = 63; /* 0x3f */
+ dh->dbs = (u8)(bytes*2); /* number of quadlets of data in AVTPDU */
+ dh->qpc = 0;
+ dh->fn = 0;
+ dh->sph = 0;
+ dh->dbc = hdr->seqnr;
+ dh->cip_2 = 2;
+
+ /*
+ * FMT (Format ID): same as specified in iec 61883-1:2003
+ *
+ * For IEC 61883-6, it shall be 0x10 (16) to define Audio and
+ * Music data
+ */
+ dh->fmt = 0x10;
+
+ /* FIXME: find value
+ * Could be sampling-freq, but 8 bits give 0 - 65kHz sampling.
+ */
+ dh->fdf = 0;
+
+ dh->syt = 0xFFFF;
+}
+
+static inline void *_iec61883_payload(struct avtpdu_header *hdr)
+{
+ struct iec61883_audio_header *dh = (struct iec61883_audio_header *)&hdr->data;
+ /* TODO: add some basic checks before returning payload ? */
+ return &dh->payload;
+}
+
+#endif /* TSN_IEC61883_H */
--
2.7.4
From: Henrik Austad <[email protected]>
In short summary:
* tsn_core.c is the main driver of tsn, all new links go through
here and all data to/form the shims are handled here
core also manages the shim-interface.
* tsn_configfs.c is the API to userspace. TSN is driven from userspace
and a link is created, configured, enabled, disabled and removed
purely from userspace. All attributes requried must be determined by
userspace, preferrably via IEEE 1722.1 (discovery and enumeration).
New is that setting a shim will not automatically enable it, this is to
allow shims to expose own attributes via ConfigFS. It will also make the
steps a bit more obvious.
* tsn_header.c small part that handles the actual header of the frames
we send. Kept out of core for cleanliness.
* tsn_net.c handles operations towards the networking layer. A *very*
simple hook for handling backpressure in the tx-queue is added, but this
is currently nowhere near sufficient.
The current driver is under development. This means that from the moment it
is enabled (with a registered shim), it will send traffic, either 0-traffic
(frames of reserved length but with payload 0) or actual traffic. This will
change once the driver stabilizes.
We also use a kthread to handle the lifting when transmitting frames. This
should remove some of the old timeouts and issues we had when doing all of
this via the hrtimer callback. Should a new timer fire before we are done,
it will be queued up and handled immediately. Note that this is a bug (we
*really* should be done before the next 1ms tick happens.
For more detail, see Documentation/networking/tsn/
Cc: "David S. Miller" <[email protected]>
Signed-off-by: Henrik Austad <[email protected]>
---
net/Makefile | 1 +
net/tsn/Makefile | 6 +
net/tsn/tsn_configfs.c | 673 +++++++++++++++++++++++++++
net/tsn/tsn_core.c | 1189 ++++++++++++++++++++++++++++++++++++++++++++++++
net/tsn/tsn_header.c | 162 +++++++
net/tsn/tsn_internal.h | 397 ++++++++++++++++
net/tsn/tsn_net.c | 392 ++++++++++++++++
7 files changed, 2820 insertions(+)
create mode 100644 net/tsn/Makefile
create mode 100644 net/tsn/tsn_configfs.c
create mode 100644 net/tsn/tsn_core.c
create mode 100644 net/tsn/tsn_header.c
create mode 100644 net/tsn/tsn_internal.h
create mode 100644 net/tsn/tsn_net.c
diff --git a/net/Makefile b/net/Makefile
index 4cafaa2..a0f7d41 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -81,3 +81,4 @@ obj-y += l3mdev/
endif
obj-$(CONFIG_QRTR) += qrtr/
obj-$(CONFIG_NET_NCSI) += ncsi/
+obj-$(CONFIG_TSN) += tsn/
diff --git a/net/tsn/Makefile b/net/tsn/Makefile
new file mode 100644
index 0000000..0d87687
--- /dev/null
+++ b/net/tsn/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for the Linux TSN subsystem
+#
+
+obj-$(CONFIG_TSN) += tsn.o
+tsn-objs :=tsn_core.o tsn_configfs.o tsn_net.o tsn_header.o
diff --git a/net/tsn/tsn_configfs.c b/net/tsn/tsn_configfs.c
new file mode 100644
index 0000000..9ace1aa
--- /dev/null
+++ b/net/tsn/tsn_configfs.c
@@ -0,0 +1,673 @@
+/*
+ * ConfigFS interface to TSN
+ * Copyright (C) 2015- Henrik Austad <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/configfs.h>
+#include <linux/netdevice.h>
+#include <linux/rtmutex.h>
+#include <linux/tsn.h>
+#include "tsn_internal.h"
+
+static inline struct tsn_link *to_tsn_link(struct config_item *item)
+{
+ /* this line causes checkpatch to WARN. making checkpatch happy,
+ * makes code messy..
+ */
+ return item ? container_of(to_config_group(item), struct tsn_link, group) : NULL;
+}
+
+static inline struct tsn_nic *to_tsn_nic(struct config_group *group)
+{
+ return group ? container_of(group, struct tsn_nic, group) : NULL;
+}
+
+static inline struct tsn_nic *item_to_tsn_nic(struct config_item *item)
+{
+ return item ? container_of(to_config_group(item), struct tsn_nic, group) : NULL;
+}
+
+/* -----------------------------------------------
+ * Tier2 attributes
+ *
+ * The content of the links userspace can see/modify
+ * -----------------------------------------------
+*/
+static ssize_t _tsn_max_payload_size_show(struct config_item *item,
+ char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%u\n", (u32)link->max_payload_size);
+}
+
+static ssize_t _tsn_max_payload_size_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ u16 mpl_size = 0;
+ int ret = 0;
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("ERROR: Cannot change Payload size on link\n");
+ return -EINVAL;
+ }
+ ret = kstrtou16(page, 0, &mpl_size);
+ if (ret)
+ return ret;
+
+ /* 802.1BA-2011 6.4 payload must be <1500 octets (excluding
+ * headers, tags etc) However, this is not directly mappable to
+ * how some hw handles things, so to be conservative, we
+ * restrict it down to [26..1485]
+ *
+ * This is also the _payload_ size, which does not include the
+ * AVTPDU header. This is an upper limit to how much raw data
+ * the shim can transport in each frame.
+ */
+ if (!tsnh_payload_size_valid(mpl_size, link->shim_header_size)) {
+ pr_err("%s: payload (%u) should be [26..1480] octets.\n",
+ __func__, (u32)mpl_size);
+ return -EINVAL;
+ }
+ link->max_payload_size = mpl_size;
+ return count;
+}
+
+static ssize_t _tsn_shim_header_size_show(struct config_item *item,
+ char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%u\n", (u32)link->shim_header_size);
+}
+
+static ssize_t _tsn_shim_header_size_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ u16 hdr_size = 0;
+ int ret = 0;
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("ERROR: Cannot change shim-header size on link\n");
+ return -EINVAL;
+ }
+
+ ret = kstrtou16(page, 0, &hdr_size);
+ if (ret)
+ return ret;
+
+ if (!tsnh_payload_size_valid(link->max_payload_size, hdr_size))
+ return -EINVAL;
+
+ link->shim_header_size = hdr_size;
+ return count;
+}
+
+static ssize_t _tsn_stream_id_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%llu\n", link->stream_id);
+}
+
+static ssize_t _tsn_stream_id_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ u64 sid;
+ int ret = 0;
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("ERROR: Cannot change StreamID on link\n");
+ return -EINVAL;
+ }
+ ret = kstrtou64(page, 0, &sid);
+ if (ret)
+ return ret;
+
+ if (sid == link->stream_id)
+ return count;
+
+ if (tsn_find_by_stream_id(sid)) {
+ pr_warn("Cannot set sid to %llu - exists\n", sid);
+ return -EEXIST;
+ }
+ if (sid != link->stream_id)
+ tsn_readd_link(link, sid);
+ return count;
+}
+
+static ssize_t _tsn_buffer_size_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%zu\n", link->buffer_size);
+}
+
+static ssize_t _tsn_buffer_size_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ u32 tmp;
+ int ret = 0;
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("ERROR: Cannot change Buffer Size on link\n");
+ return -EINVAL;
+ }
+
+ ret = kstrtou32(page, 0, &tmp);
+ /* only allow buffers !0 and smaller than 8MB for now */
+ if (!ret && tmp) {
+ pr_info("%s: update buffer_size from %zu to %u\n",
+ __func__, link->buffer_size, tmp);
+ link->buffer_size = (size_t)tmp;
+ return count;
+ }
+ return -EINVAL;
+}
+
+static ssize_t _tsn_class_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%s\n", (link->class == SR_CLASS_A ? "A" : "B"));
+}
+
+static ssize_t _tsn_class_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ char class[2] = { 0 };
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("ERROR: Cannot change Class-type on link\n");
+ return -EINVAL;
+ }
+ if (strncpy(class, page, 1)) {
+ if (strcmp(class, "a") == 0 || strcmp(class, "A") == 0)
+ link->class = SR_CLASS_A;
+ else if (strcmp(class, "b") == 0 || strcmp(class, "B") == 0)
+ link->class = SR_CLASS_B;
+ return count;
+ }
+
+ pr_err("%s: Could not copy new class into buffer\n", __func__);
+ return -EINVAL;
+}
+
+static ssize_t _tsn_vlan_id_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%u\n", link->vlan_id);
+}
+
+static ssize_t _tsn_vlan_id_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ u16 vlan_id;
+ int ret = 0;
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("ERROR: Cannot change VLAN-ID on link\n");
+ return -EINVAL;
+ }
+ ret = kstrtou16(page, 0, &vlan_id);
+ if (ret)
+ return ret;
+ if (vlan_id > 0xfff)
+ return -EINVAL;
+ link->vlan_id = vlan_id & 0xfff;
+ return count;
+}
+
+static ssize_t _tsn_end_station_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%s\n",
+ (link->estype_talker ? "Talker" : "Listener"));
+}
+
+static ssize_t _tsn_end_station_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ char estype[9] = {0};
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("ERROR: Cannot change End-station type on link.\n");
+ return -EINVAL;
+ }
+ if (strncpy(estype, page, 8)) {
+ if (strncmp(estype, "Talker", 6) == 0 ||
+ strncmp(estype, "talker", 6) == 0) {
+ link->estype_talker = 1;
+ return count;
+ } else if (strncmp(estype, "Listener", 8) == 0 ||
+ strncmp(estype, "listener", 8) == 0) {
+ link->estype_talker = 0;
+ return count;
+ }
+ }
+ return -EINVAL;
+}
+static ssize_t _tsn_shim_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%s\n", tsn_shim_get_active(link));
+}
+
+static ssize_t _tsn_shim_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ size_t len;
+ ssize_t ret;
+ char shim_name[SHIM_NAME_SIZE + 1] = { 0 };
+ struct tsn_shim_ops *shim_ops = NULL;
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("TSN ERROR: cannot change shim on link (is active)\n");
+ return -EINVAL;
+ }
+
+ strncpy(shim_name, page, SHIM_NAME_SIZE);
+ len = strlen(shim_name);
+ while (len-- > 0) {
+ if (shim_name[len] == '\n')
+ shim_name[len] = 0x00;
+ }
+
+ /* the only shim we allow to set shim_ops to NULL is 'off' */
+ if (strncmp(shim_name, "off", 3) != 0) {
+ shim_ops = tsn_shim_find_by_name(shim_name);
+ if (!shim_ops) {
+ pr_info("TSN ERROR: could not enable desired shim, %s is not available\n",
+ shim_name);
+ return -EINVAL;
+ }
+ }
+
+ ret = tsn_set_shim_ops(link, shim_ops);
+ if (ret != 0) {
+ pr_err("TSN ERROR: Could not set shim-ops for link - %zd\n", ret);
+ return ret;
+ }
+ pr_info("TSN: Set new shim_ops (%s)\n", shim_name);
+ return count;
+}
+
+static ssize_t _tsn_enabled_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%s\n", tsn_link_is_on(link) ? "on" : "off");
+}
+
+static ssize_t _tsn_enabled_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ char link_state[8] = {0};
+ size_t len;
+ int ret = 0;
+
+ if (!link)
+ return -EINVAL;
+
+ strncpy(link_state, page, 7);
+ len = strlen(link_state);
+ while (len-- > 0) {
+ if (link_state[len] == '\n')
+ link_state[len] = 0x00;
+ }
+
+ /* only allowed state is off */
+ if (tsn_link_is_on(link) || tsn_link_is_err(link)) {
+ if (strncmp(link_state, "off", 3) != 0) {
+ pr_err("TSN ERROR: Invalid link_state for active link (%s), ignoring\n", link_state);
+ return -EINVAL;
+ }
+ tsn_teardown_link(link);
+ return count;
+ }
+ else if (strncmp(link_state, "on", 3) == 0) {
+ ret = tsn_prepare_link(link);
+ if (ret != 0) {
+ pr_err("TSN ERROR: Preparing link failed - %d\n", ret);
+ return ret;
+ }
+ return count;
+ }
+ return -EINVAL;
+}
+
+static ssize_t _tsn_remote_mac_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%pM\n", link->remote_mac);
+}
+
+static ssize_t _tsn_remote_mac_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ unsigned char mac[7] = {0};
+ int ret = 0;
+
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link)) {
+ pr_err("ERROR: Cannot change Remote MAC on link.\n");
+ return -EINVAL;
+ }
+ ret = sscanf(page, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
+ if (ret > 0) {
+ pr_info("Got MAC (%pM), copying to storage\n", &mac);
+ memcpy(link->remote_mac, mac, 6);
+ return count;
+ }
+ return -EINVAL;
+}
+
+static ssize_t _tsn_local_mac_show(struct config_item *item, char *page)
+{
+ struct tsn_link *link = to_tsn_link(item);
+
+ if (!link)
+ return -EINVAL;
+ return sprintf(page, "%pMq\n", link->nic->dev->perm_addr);
+}
+
+CONFIGFS_ATTR(_tsn_, max_payload_size);
+CONFIGFS_ATTR(_tsn_, shim_header_size);
+CONFIGFS_ATTR(_tsn_, stream_id);
+CONFIGFS_ATTR(_tsn_, buffer_size);
+CONFIGFS_ATTR(_tsn_, class);
+CONFIGFS_ATTR(_tsn_, vlan_id);
+CONFIGFS_ATTR(_tsn_, end_station);
+CONFIGFS_ATTR(_tsn_, shim);
+CONFIGFS_ATTR(_tsn_, enabled);
+CONFIGFS_ATTR(_tsn_, remote_mac);
+CONFIGFS_ATTR_RO(_tsn_, local_mac);
+static struct configfs_attribute *tsn_tier2_attrs[] = {
+ &_tsn_attr_max_payload_size,
+ &_tsn_attr_shim_header_size,
+ &_tsn_attr_stream_id,
+ &_tsn_attr_buffer_size,
+ &_tsn_attr_class,
+ &_tsn_attr_vlan_id,
+ &_tsn_attr_end_station,
+ &_tsn_attr_shim,
+ &_tsn_attr_enabled,
+ &_tsn_attr_remote_mac,
+ &_tsn_attr_local_mac,
+ NULL,
+};
+
+static struct config_item_type group_tsn_tier2_type = {
+ .ct_owner = THIS_MODULE,
+ .ct_attrs = tsn_tier2_attrs,
+ .ct_group_ops = NULL,
+};
+
+/* -----------------------------------------------
+ * Tier1
+ *
+ * The only interesting info at this level are the available links
+ * belonging to this nic. This will be the subdirectories. Apart from
+ * making/removing tier-2 folders, nothing else is required here.
+ */
+static struct config_group *group_tsn_1_make_group(struct config_group *group,
+ const char *name)
+{
+ struct tsn_nic *nic = to_tsn_nic(group);
+ struct tsn_link *link = tsn_create_and_add_link(nic);
+
+ if (!nic || !link)
+ return ERR_PTR(-ENOMEM);
+
+ config_group_init_type_name(&link->group, name, &group_tsn_tier2_type);
+
+ return &link->group;
+}
+
+static void group_tsn_1_drop_group(struct config_group *group,
+ struct config_item *item)
+{
+ struct tsn_link *link = to_tsn_link(item);
+ struct tsn_nic *nic = to_tsn_nic(group);
+
+ if (link) {
+ tsn_teardown_link(link);
+ tsn_remove_and_free_link(link);
+ }
+ pr_info("Dropping %s from NIC: %s\n", item->ci_name, nic->name);
+}
+
+
+static ssize_t _tsn_pcp_a_show(struct config_item *item, char *page)
+{
+ struct tsn_nic *nic = item_to_tsn_nic(item);
+
+ if (!nic)
+ return -EINVAL;
+ return sprintf(page, "0x%x\n", nic->pcp_a);
+}
+
+static ssize_t _tsn_pcp_a_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_nic *nic = item_to_tsn_nic(item);
+ int ret = 0;
+ u8 pcp;
+
+ if (!nic)
+ return -EINVAL;
+
+ /* FIXME: need to check for *any* active links */
+
+ ret = kstrtou8(page, 0, &pcp);
+ if (ret)
+ return ret;
+ if (pcp > 0x7)
+ return -EINVAL;
+ nic->pcp_a = pcp & 0x7;
+ return count;
+}
+
+static ssize_t _tsn_pcp_b_show(struct config_item *item, char *page)
+{
+ struct tsn_nic *nic = item_to_tsn_nic(item);
+
+ if (!nic)
+ return -EINVAL;
+ return sprintf(page, "0x%x\n", nic->pcp_b);
+}
+
+static ssize_t _tsn_pcp_b_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct tsn_nic *nic = item_to_tsn_nic(item);
+ int ret = 0;
+ u8 pcp;
+
+ if (!nic)
+ return -EINVAL;
+
+ /* FIXME: need to check for *any* active links */
+
+ ret = kstrtou8(page, 0, &pcp);
+ if (ret)
+ return ret;
+ if (pcp > 0x7)
+ return -EINVAL;
+ nic->pcp_b = pcp & 0x7;
+ return count;
+}
+
+CONFIGFS_ATTR(_tsn_, pcp_a);
+CONFIGFS_ATTR(_tsn_, pcp_b);
+
+static struct configfs_attribute *tsn_tier1_attrs[] = {
+ &_tsn_attr_pcp_a,
+ &_tsn_attr_pcp_b,
+ NULL,
+};
+
+static struct configfs_group_operations group_tsn_1_group_ops = {
+ .make_group = group_tsn_1_make_group,
+ .drop_item = group_tsn_1_drop_group,
+};
+
+static struct config_item_type group_tsn_tier1_type = {
+ .ct_group_ops = &group_tsn_1_group_ops,
+ .ct_attrs = tsn_tier1_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+/* -----------------------------------------------
+ * Tier0
+ *
+ * Top level. This will expose all the TSN-capable NICs as well as
+ * currently active StreamIDs and registered shims. 'Global' info goes
+ * here.
+ */
+static ssize_t _tsn_used_sids_show(struct config_item *item, char *page)
+{
+ return tsn_get_stream_ids(page, PAGE_SIZE);
+}
+
+static ssize_t _tsn_available_shims_show(struct config_item *item, char *page)
+{
+ return tsn_shim_export_probe_triggers(page);
+}
+
+static struct configfs_attribute tsn_used_sids = {
+ .ca_owner = THIS_MODULE,
+ .ca_name = "stream_ids",
+ .ca_mode = S_IRUGO,
+ .show = _tsn_used_sids_show,
+};
+
+static struct configfs_attribute available_shims = {
+ .ca_owner = THIS_MODULE,
+ .ca_name = "available_shims",
+ .ca_mode = S_IRUGO,
+ .show = _tsn_available_shims_show,
+};
+
+static struct configfs_attribute *group_tsn_attrs[] = {
+ &tsn_used_sids,
+ &available_shims,
+ NULL,
+};
+
+static struct config_item_type group_tsn_tier0_type = {
+ .ct_group_ops = NULL,
+ .ct_attrs = group_tsn_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+int tsn_configfs_init(struct tsn_list *tlist)
+{
+ int ret = 0;
+ struct tsn_nic *next;
+ struct configfs_subsystem *subsys;
+
+ if (!tlist || !tlist->num_avail)
+ return -EINVAL;
+
+ /* Tier-0 */
+ subsys = &tlist->tsn_subsys;
+ strncpy(subsys->su_group.cg_item.ci_namebuf, "tsn",
+ CONFIGFS_ITEM_NAME_LEN);
+ subsys->su_group.cg_item.ci_type = &group_tsn_tier0_type;
+
+ config_group_init(&subsys->su_group);
+ mutex_init(&subsys->su_mutex);
+
+ /* Tier-1
+ * (tsn-capable NICs), automatic subgroups
+ */
+ list_for_each_entry(next, &tlist->head, list) {
+ config_group_init_type_name(&next->group, next->name,
+ &group_tsn_tier1_type);
+ configfs_add_default_group(&next->group, &subsys->su_group);
+ }
+
+ /* This is the final step, once done, system is live, make sure
+ * init has completed properly
+ */
+ ret = configfs_register_subsystem(subsys);
+ if (ret) {
+ pr_err("Trouble registering TSN ConfigFS subsystem\n");
+ return ret;
+ }
+
+ pr_warn("configfs_init_module() OK\n");
+ return 0;
+}
+
+void tsn_configfs_exit(struct tsn_list *tlist)
+{
+ if (!tlist)
+ return;
+ configfs_unregister_subsystem(&tlist->tsn_subsys);
+ pr_warn("configfs_exit_module()\n");
+}
diff --git a/net/tsn/tsn_core.c b/net/tsn/tsn_core.c
new file mode 100644
index 0000000..f243b0f
--- /dev/null
+++ b/net/tsn/tsn_core.c
@@ -0,0 +1,1189 @@
+/*
+ * TSN Core main part of TSN driver
+ *
+ * Copyright (C) 2015- Henrik Austad <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/random.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/hashtable.h>
+#include <linux/netdevice.h>
+#include <linux/net.h>
+#include <linux/dma-mapping.h>
+#include <net/sock.h>
+#include <net/net_namespace.h>
+#include <linux/hrtimer.h>
+#include <linux/configfs.h>
+#include <linux/ktime.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/tsn.h>
+#include "tsn_internal.h"
+
+static struct tsn_list tlist;
+static int in_debug;
+static int on_cpu = -1;
+
+#define TLINK_HASH_BITS 8
+DEFINE_HASHTABLE(tlinks, TLINK_HASH_BITS);
+
+static LIST_HEAD(tsn_shim_ops);
+
+/* Called with link->lock held */
+
+/* _get_low_water - return the number of bytes that signal the low-water
+ * mark in the buffer.
+ *
+ * class B sends frames every 250us (4 per ms), A doubles that.
+ */
+#define LOW_WATER_MS 20
+static inline size_t _get_low_water(struct tsn_link *link)
+{
+ int numframes = LOW_WATER_MS * (link->class == SR_CLASS_A ? 8 : 4);
+
+ return link->max_payload_size * numframes;
+}
+
+/* Called with link->lock held */
+static inline size_t _get_high_water(struct tsn_link *link)
+{
+ size_t low_water = _get_low_water(link);
+
+ return max(link->used_buffer_size - low_water, low_water);
+}
+
+/**
+ * _tsn_set_buffer - register a memory region to use as the buffer
+ *
+ * This is used when we are operating in !external_buffer mode.
+ *
+ * TSN expects a ring-buffer and will update pointers to keep track of
+ * where we are. When the buffer is refilled, head and tail will be
+ * updated accordingly.
+ *
+ * @param link the link that should hold the buffer
+ * @param buffer the new buffer
+ * @param bufsize size of new buffer.
+ *
+ * @returns 0 on success, negative on error
+ *
+ * Must be called with tsn_lock() held.
+ */
+static int _tsn_set_buffer(struct tsn_link *link, void *buffer, size_t bufsize)
+{
+ if (link->buffer) {
+ pr_err("%s: Cannot add buffer, buffer already registred\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ trace_tsn_set_buffer(link, bufsize);
+ link->buffer = buffer;
+ link->head = link->buffer;
+ link->tail = link->buffer;
+ link->end = link->buffer + bufsize;
+ link->buffer_size = bufsize;
+ link->used_buffer_size = bufsize;
+ return 0;
+}
+
+/**
+ * _tsn_free_buffer - remove internal buffers
+ *
+ * This is the buffer where we store data before shipping it to TSN, or
+ * where incoming data is staged.
+ *
+ * @param link - the link that holds the buffer
+ *
+ * Must be called with tsn_lock() held.
+ */
+static void _tsn_free_buffer(struct tsn_link *link)
+{
+ if (!link)
+ return;
+ trace_tsn_free_buffer(link, link->buffer_size);
+ kfree(link->buffer);
+ link->buffer = NULL;
+ link->head = NULL;
+ link->tail = NULL;
+ link->end = NULL;
+}
+
+int tsn_set_buffer_size(struct tsn_link *link, size_t bsize)
+{
+ if (!link)
+ return -EINVAL;
+
+ if (bsize > link->buffer_size) {
+ pr_err("%s: requested buffer (%zd) larger than allocated memory (%zd)\n",
+ __func__, bsize, link->buffer_size);
+ return -ENOMEM;
+ }
+
+ tsn_lock(link);
+ link->used_buffer_size = bsize;
+ link->tail = link->buffer;
+ link->head = link->buffer;
+ link->end = link->buffer + link->used_buffer_size;
+ link->low_water_mark = _get_low_water(link);
+ link->high_water_mark = _get_high_water(link);
+ tsn_unlock(link);
+
+ pr_info("Set buffer_size, size: %zd, lowwater: %zd, highwater: %zd\n",
+ link->used_buffer_size, link->low_water_mark,
+ link->high_water_mark);
+ return 0;
+}
+EXPORT_SYMBOL(tsn_set_buffer_size);
+
+int tsn_clear_buffer_size(struct tsn_link *link)
+{
+ if (!link)
+ return -EINVAL;
+
+ tsn_lock(link);
+ link->tail = link->buffer;
+ link->head = link->buffer;
+ link->end = link->buffer + link->buffer_size;
+ memset(link->buffer, 0, link->used_buffer_size);
+ link->used_buffer_size = link->buffer_size;
+ link->low_water_mark = _get_low_water(link);
+ link->high_water_mark = _get_high_water(link);
+ tsn_unlock(link);
+ return 0;
+}
+EXPORT_SYMBOL(tsn_clear_buffer_size);
+
+void *tsn_set_external_buffer(struct tsn_link *link, void *buffer,
+ size_t buffer_size)
+{
+ void *old_buffer;
+
+ if (!link)
+ return NULL;
+ if (buffer_size < link->max_payload_size)
+ pr_warn("%s: buffer_size (%zu) < max_payload_size (%u)\n",
+ __func__, buffer_size, link->max_payload_size);
+
+ tsn_lock(link);
+ if (!link->external_buffer && link->buffer)
+ _tsn_free_buffer(link);
+
+ old_buffer = link->buffer;
+ link->external_buffer = 1;
+ link->buffer_size = buffer_size;
+ link->used_buffer_size = buffer_size;
+ link->buffer = buffer;
+ link->head = link->buffer;
+ link->tail = link->buffer;
+ link->end = link->buffer + link->used_buffer_size;
+ tsn_unlock(link);
+ return old_buffer;
+}
+EXPORT_SYMBOL(tsn_set_external_buffer);
+
+/* Caller must hold link->lock!
+ *
+ * Write data *into* buffer, either from net or from shim due to a
+ * closing underflow event.
+ */
+static void __tsn_buffer_write(struct tsn_link *link, void *src, size_t bytes)
+{
+ int rem = 0;
+
+ /* No Need To Wrap, if overflow we will overwrite without
+ * warning.
+ */
+ trace_tsn_buffer_write(link, bytes);
+ if (link->head + bytes < link->end) {
+ memcpy(link->head, src, bytes);
+ link->head += bytes;
+ } else {
+ rem = link->end - link->head;
+ memcpy(link->head, src, rem);
+ memcpy(link->buffer, (src + rem), bytes - rem);
+ link->head = link->buffer + (bytes - rem);
+ }
+}
+
+int tsn_buffer_write(struct tsn_link *link, void *src, size_t bytes)
+{
+ if (!link)
+ return -EINVAL;
+
+ if (tsn_link_is_err(link)) {
+ tsn_teardown_link(link);
+ return -EIO;
+ }
+
+ /* We should not do anything if link has gone inactive */
+ if (!tsn_link_is_on(link))
+ return 0;
+
+ __tsn_buffer_write(link, src, bytes);
+
+ /* Copied a batch of data and if link is disabled, it is now
+ * safe to enable it. Otherwise we will continue to send
+ * null-frames to remote.
+ */
+ if (!tsn_lb(link))
+ tsn_lb_enable(link);
+
+ return bytes;
+}
+EXPORT_SYMBOL(tsn_buffer_write);
+
+/**
+ * tsn_buffer_write_net - take data from a skbuff and write it into buffer
+ *
+ * When we receive a frame, we grab data from the skbuff and add it to
+ * link->buffer.
+ *
+ * Note that this routine does NOT CARE about channels, samplesize etc,
+ * it is a _pure_ copy that handles ringbuffer wraps etc.
+ *
+ * This function have side-effects as it will update internal tsn_link
+ * values and trigger refill() should the buffer run low.
+ *
+ * NOTE: called from tsn_rx_handler() -> tsnh_handle_du(), with
+ * tsn_lock held.
+ *
+ * @param link current link that holds the buffer
+ * @param buffer the buffer to copy from
+ * @param bytes number of bytes
+ * @returns Bytes copied into link->buffer, negative value upon error.
+ */
+int tsn_buffer_write_net(struct tsn_link *link, void *src, size_t bytes)
+{
+ size_t used;
+
+ if (!link)
+ return -EINVAL;
+
+ /* Driver has not been enabled yet, i.e. it is in state 'off' and we
+ * have no way of knowing the state of the buffers.
+ * Silently drop the data, pretend write went ok
+ */
+ trace_tsn_buffer_write_net(link, bytes);
+ if (!tsn_lb(link))
+ return bytes;
+
+ __tsn_buffer_write(link, src, bytes);
+
+ /* If we stored more data than high_water, we need to drain
+ *
+ * In ALSA, this will trigger a snd_pcm_period_elapsed() for the
+ * substream connected to this particular link.
+ */
+ used = _tsn_buffer_used(link);
+ if (used > link->high_water_mark) {
+ trace_tsn_buffer_drain(link, used);
+ link->ops->buffer_drain(link);
+ }
+
+ return bytes;
+}
+
+/* Note: this assumes that the frames will be sent out immediately and
+ * not kept in a queue somewhere awaiting enough credits. If that is the
+ * case, then this will probably fool the shim into thinking that the
+ * frames have been shipped out early.
+ *
+ * Ideally, this should be updated whenever the frame is actually transmittet.
+ *
+ * Workaround/idea:
+ * - find size of tx-queue on card
+ * - look at how many is to be sent
+ * - look at outgoing bw
+ * - find the actual rate of frames going out
+ * - use rate + time_now to determine time when frame will be shipped.
+ *
+ * Or, another approach; grab presentation time from frame and use that
+ * as basis for timestamp. If prsentation time is too far into the
+ * future, do not send this frame just yet. This also requires us to
+ * look at what timestamp previous frames have used.
+ */
+int tsn_update_net_time(struct tsn_link *link, u64 time_ns, int increment)
+{
+ u64 delta_ns;
+ u64 exp_avg;
+
+ if (time_ns < link->ts_net_ns || increment < 1)
+ return -EINVAL;
+
+ /* if increment > 1, we have sent a batch of frames, and */
+ delta_ns = time_ns - link->ts_net_ns;
+ if (increment > 1)
+ delta_ns = div_u64(delta_ns, increment);
+ do {
+ exp_avg = ((delta_ns * link->ts_exp_alpha) >> 14) + ((((1 << 14) - link->ts_exp_alpha) * link->ts_exp_avg) >> 14);
+ link->ts_exp_avg = exp_avg;
+ } while (--increment > 0);
+
+ link->ts_net_ns = time_ns;
+ link->ts_delta_ns = delta_ns;
+ trace_tsn_update_net_time(link);
+ return 0;
+}
+
+/* caller must hold link->lock!
+ *
+ * Read data *from* buffer, either to net or to shim due to a
+ * closing overflow event.
+ *
+ * Function will *not* care if you read past head and into unchartered
+ * territory, caller must ascertain validity of bytes.
+ */
+static void __tsn_buffer_read(struct tsn_link *link, void *dst, size_t bytes)
+{
+ int rem = 0;
+
+ trace_tsn_buffer_read(link, bytes);
+ if ((link->tail + bytes) < link->end) {
+ memcpy(dst, link->tail, bytes);
+ link->tail += bytes;
+ } else {
+ rem = link->end - link->tail;
+ memcpy(dst, link->tail, rem);
+ memcpy(dst + rem, link->buffer, bytes - rem);
+ link->tail = link->buffer + bytes - rem;
+ }
+}
+
+/**
+ * tsn_buffer_read_net - read data from link->buffer and give to network layer
+ *
+ * When we send a frame, we grab data from the buffer and add it to the
+ * sk_buff->data, this is primarily done by the Tx-subsystem in tsn_net
+ * and is typically done in small chunks
+ *
+ * @param link current link that holds the buffer
+ * @param buffer the buffer to copy into, must be at least of size bytes
+ * @param bytes number of bytes.
+ *
+ * Note that this routine does NOT CARE about channels, samplesize etc,
+ * it is a _pure_ copy that handles ringbuffer wraps etc.
+ *
+ * This function have side-effects as it will update internal tsn_link
+ * values and trigger refill() should the buffer run low.
+ *
+ * NOTE: expects to be called with locks held
+ *
+ * @return Bytes copied into link->buffer, negative value upon error.
+ */
+int tsn_buffer_read_net(struct tsn_link *link, void *buffer, size_t bytes)
+{
+ size_t used;
+
+ if (!link)
+ return -EINVAL;
+
+ /* link is currently inactive, e.g. we send frames, but without
+ * content. This is a debug-feature, if we don't have data to
+ * send, we should not send zero-frames.
+ *
+ * This can be done before we ship data, or if we are muted
+ * (without expressively stating that over 1722.1
+ */
+ if (!tsn_lb(link)) {
+ memset(buffer, 0, bytes);
+ goto out;
+ }
+
+ __tsn_buffer_read(link, buffer, bytes);
+
+ /* Trigger refill from client app */
+ used = _tsn_buffer_used(link);
+ if (used < link->low_water_mark) {
+ trace_tsn_refill(link, used);
+ link->ops->buffer_refill(link);
+ }
+out:
+ return bytes;
+}
+
+int tsn_buffer_read(struct tsn_link *link, void *buffer, size_t bytes)
+{
+ if (!link)
+ return -EINVAL;
+
+ /* We should not do anything if link has gone inactive */
+ if (!tsn_link_is_on(link))
+ return 0;
+
+ tsn_lock(link);
+ __tsn_buffer_read(link, buffer, bytes);
+ tsn_unlock(link);
+ return bytes;
+}
+EXPORT_SYMBOL(tsn_buffer_read);
+
+static int _tsn_send_batch(struct tsn_link *link)
+{
+ int ret = 0;
+ size_t num_frames = (link->class == SR_CLASS_A ? 8 : 4);
+ u64 ts_base_ns = ktime_to_ns(ktime_get()) + (link->class == SR_CLASS_A ? 2000000 : 50000000);
+ u64 ts_delta_ns = (link->class == SR_CLASS_A ? 125000 : 250000);
+
+ trace_tsn_send_batch(link, num_frames, ts_base_ns, ts_delta_ns);
+ ret = tsn_net_send_set(link, num_frames, ts_base_ns, ts_delta_ns);
+ if (ret < 0)
+ return ret;
+ link->frames_sent += ret;
+
+ /* we sent ret number of frames, update timestamp with that. */
+ tsn_update_net_time(link, ktime_to_ns(ktime_get()), ret);
+
+ return 0;
+}
+
+static int tsn_worker_fn(void *data)
+{
+ struct tsn_link *link;
+ struct hlist_node *tmp;
+ int bkt = 0;
+ int err;
+ struct sched_param param = { .sched_priority = 10 };
+ struct tsn_list *list = (struct tsn_list *)data;
+ if (!data)
+ return -EINVAL;
+
+ /* FIXME: set affinity */
+ /* set sched_rr and prio */
+ sched_setscheduler(current, SCHED_RR, ¶m);
+
+ pr_info("tsn_worker ready to run\n");
+ while (!kthread_should_stop() && tsn_core_running(list)) {
+ if (list->should_run <= 0) {
+ sched_out:
+ /* task_interruptible */
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule();
+ }
+ if (!tsn_core_running(list) || kthread_should_stop())
+ break;
+
+ if (list->should_run <= 0)
+ goto sched_out;
+
+
+ hash_for_each_safe(tlinks, bkt, tmp, link, node) {
+ /* use the periodic wakeup to test if any of the
+ * links has failed. If it has, clear it and
+ * continue */
+ if (tsn_link_is_err(link)) {
+ tsn_teardown_link(link);
+ continue;
+ }
+
+ /* if the link is not on, we can ignore the link
+ * for one iteration before we start sending
+ * frames, we accept this race. */
+ if (!tsn_link_is_on(link))
+ continue;
+
+ tsn_lock(link);
+
+ /* In case we are killed while waiting for the lock */
+ if (kthread_should_stop()) {
+ tsn_unlock(link);
+ return 0;
+ }
+
+ /* FIXME: this should iterate over each link and
+ * send *one* frame pr link until all links are
+ * exhausted for this period, otherwise the
+ * first link in a run will starve the other
+ * links.
+ */
+ if (tsn_link_is_on(link) && link->estype_talker) {
+ err = _tsn_send_batch(link);
+ if (err)
+ tsn_link_err(link);
+ }
+
+ tsn_unlock(link);
+ }
+ list->should_run--;
+ }
+ pr_info("tsn_worker_fn done, wrapping up and dying\n");
+ return 0;
+}
+
+static int tsn_worker_init(struct tsn_list *list)
+{
+ /* create wait-queue */
+ list->tsn_thread = kthread_create(tsn_worker_fn, list, "tsn_worker");
+ if (!list->tsn_thread)
+ return -ENOMEM;
+
+ /* prod the thread to make it ready if hrtimer calls it immediately */
+ wake_up_process(list->tsn_thread);
+ return 0;
+}
+
+static void tsn_worker_exit(struct tsn_list *list)
+{
+ atomic_set(&list->running, 0);
+ list->should_run = 0;
+ kthread_stop(list->tsn_thread);
+}
+
+static enum hrtimer_restart tsn_hrtimer_callback(struct hrtimer *hrt)
+{
+ struct tsn_list *list = container_of(hrt, struct tsn_list, tsn_timer);
+ if (!tsn_core_running(list))
+ return HRTIMER_NORESTART;
+
+ hrtimer_forward_now(hrt, ns_to_ktime(list->period_ns));
+
+ /* tsn_thread ready? */
+ if (tsn_core_running(list)) {
+ /* kick worker */
+ list->should_run++;
+ if (list->tsn_thread != TASK_RUNNING)
+ wake_up_process(list->tsn_thread);
+ }
+
+ return HRTIMER_RESTART;
+}
+
+static long tsn_hrtimer_init(struct tsn_list *list)
+{
+ /* Run every 1ms, _tsn_send_batch will figure out how many
+ * frames to send for active frames
+ */
+ hrtimer_init(&list->tsn_timer, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL | HRTIMER_MODE_PINNED);
+
+ list->tsn_timer.function = tsn_hrtimer_callback;
+ hrtimer_cancel(&list->tsn_timer);
+
+ hrtimer_start(&list->tsn_timer, ns_to_ktime(list->period_ns),
+ HRTIMER_MODE_REL);
+
+ atomic_set(&list->running, 1);
+ return 0;
+}
+
+static void tsn_hrtimer_exit(struct tsn_list *list)
+{
+ atomic_set(&list->running, 0);
+ hrtimer_cancel(&list->tsn_timer);
+}
+
+int tsn_set_shim_ops(struct tsn_link *link, struct tsn_shim_ops *shim_ops)
+{
+ if (!link)
+ return -EINVAL;
+ if (tsn_link_is_on(link))
+ return -EINVAL;
+
+ tsn_lock(link);
+ link->ops = shim_ops;
+ tsn_unlock(link);
+ return 0;
+}
+
+/**
+ * tsn_prepare_link - prepare link for role as Talker/Receiver
+ *
+ * Iow; this will start shipping data through the network-layer.
+ *
+ * @link: the actual link
+ *
+ * Current status: each link will get a periodic hrtimer that interrupts
+ * and ships data every 1ms. This will change once we have proper driver
+ * for hw (i.e. i210 driver).
+ */
+int tsn_prepare_link(struct tsn_link *link)
+{
+ int ret = 0;
+ void *buffer;
+ struct net_device *netdev;
+
+ /* TODO: use separate buckets (lists/rbtrees/whatever) for
+ * SR_CLASS_A and SR_CLASS_B talker streams. hrtimer-callback should
+ * not iterate over all.
+ */
+
+ if (!link || !link->ops) {
+ pr_err("TSN ERROR: link (%p) or link->ops (%p) not set\n",
+ link, link ? link->ops : NULL);
+ return -EINVAL;
+ }
+
+
+ /* configure will calculate idle_slope based on framesize
+ * (header + payload)
+ *
+ * Only do this if NIC is capable (ie, in_debug and a standard NIC)
+ */
+ netdev = link->nic->dev;
+ if (link->nic->capable) {
+ pr_info("NIC has tsn_link_configre()\n");
+ tsn_lock(link);
+ ret = netdev->netdev_ops->ndo_tsn_link_configure(netdev, link->class,
+ tsnh_frame_len(link), link->vlan_id & 0xfff, 1, link->nic->pcp_a, link->nic->pcp_b);
+ tsn_unlock(link);
+ /* NICs that have TSN support but has not enabled it
+ * will fail at this stage when we load tsn with
+ * in_debug=1.
+ */
+ if (ret < 0)
+ goto err_out;
+ }
+
+ ret = link->ops->probe(link);
+ if (ret != 0) {
+ pr_err("%s: Could not probe shim (%d), cannot create link\n",
+ __func__, ret);
+ goto err_out;
+ }
+
+ if (!link->external_buffer) {
+ pr_info("TSN: allocating buffer, %zd bytes\n", link->buffer_size);
+ buffer = kmalloc(link->buffer_size, GFP_KERNEL);
+ if (!buffer) {
+ pr_err("%s: Could not allocate memory (%zu) for buffer\n",
+ __func__, link->buffer_size);
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ tsn_lock(link);
+ ret = _tsn_set_buffer(link, buffer, link->buffer_size);
+ tsn_unlock(link);
+ if (ret != 0) {
+ pr_err("%s: Could not set buffer for TSN, got %d\n",
+ __func__, ret);
+ goto err_out;
+ }
+ } else {
+ /* FIXME: not handled */
+ pr_info("TSN does not currently handle externally hosted buffers. This is on the TODO-list\n");
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ pr_info("Link is ready, marking it ON\n");
+ tsn_link_on(link);
+ return 0;
+
+err_out:
+ tsn_lock(link);
+ link->ops = NULL;
+ tsn_unlock(link);
+ pr_info("%s: FAILED - ret=%d\n", __func__, ret);
+ return ret;
+}
+
+int tsn_teardown_link(struct tsn_link *link)
+{
+ struct net_device *netdev;
+ u64 frames_sent;
+ int ret = 0;
+
+ if (!link)
+ return -EINVAL;
+
+ /* Careful dance, we currently grab this lock from hrtimer, so
+ * make sure we grab the lock and disable the link quickly
+ */
+ tsn_lock(link);
+ tsn_link_off(link);
+ tsn_lb_disable(link);
+ tsn_unlock(link);
+
+ /* Need to call media_close() without (spin-)locks held */
+ if (link->ops && link->ops->media_close)
+ link->ops->media_close(link);
+
+ /* we can now grab the link and not worry about blocking hrtimer
+ * callback as link has been disabled, i.e. we will stop sending frames to the network layer
+ */
+ pr_info("%s: closed shim, dropping rest of link\n", __func__);
+ tsn_lock(link);
+
+ if (!link->nic) {
+ pr_err("ERROR %s: link link->nic (%p) got yanked away, cannot tear down link properly\n",
+ __func__, link->nic);
+ tsn_unlock(link);
+ return 0;
+ }
+
+ netdev = link->nic->dev;
+ if (netdev->netdev_ops->ndo_tsn_link_configure) {
+ /* NOTE: this needs to be serialized
+ */
+ ret = netdev->netdev_ops->ndo_tsn_link_configure(netdev, link->class, tsnh_frame_len(link), link->vlan_id & 0XFFF, 0, link->nic->pcp_a, link->nic->pcp_b);
+ if (ret < 0)
+ pr_err("Could not de-configure link - %d\n", ret);
+ }
+ frames_sent = link->frames_sent;
+ link->frames_sent = 0;
+ link->ops = NULL;
+ _tsn_free_buffer(link);
+ tsn_unlock(link);
+
+ pr_info("%s: disabling all parts of link, %llu frames sent in total\n", __func__, frames_sent);
+ return 0;
+}
+
+int tsn_shim_register_ops(struct tsn_shim_ops *shim_ops)
+{
+ if (!shim_ops)
+ return -EINVAL;
+
+ if (!shim_ops->buffer_refill || !shim_ops->buffer_drain ||
+ !shim_ops->media_close || !shim_ops->copy_size ||
+ !shim_ops->validate_header || !shim_ops->assemble_header ||
+ !shim_ops->get_payload_data)
+ return -EINVAL;
+
+ INIT_LIST_HEAD(&shim_ops->head);
+ list_add_tail(&shim_ops->head, &tsn_shim_ops);
+ return 0;
+}
+EXPORT_SYMBOL(tsn_shim_register_ops);
+
+void tsn_shim_deregister_ops(struct tsn_shim_ops *shim_ops)
+{
+ struct tsn_link *link;
+ struct hlist_node *tmp;
+ int bkt;
+
+ hash_for_each_safe(tlinks, bkt, tmp, link, node) {
+ if (!link)
+ continue;
+ if (link->ops == shim_ops)
+ tsn_teardown_link(link);
+ }
+ list_del(&shim_ops->head);
+}
+EXPORT_SYMBOL(tsn_shim_deregister_ops);
+
+char *tsn_shim_get_active(struct tsn_link *link)
+{
+ if (!link || !link->ops)
+ return "None";
+ return link->ops->shim_name;
+}
+
+struct tsn_shim_ops *tsn_shim_find_by_name(const char *name)
+{
+ struct tsn_shim_ops *ops;
+
+ if (!name || list_empty(&tsn_shim_ops))
+ return NULL;
+
+ list_for_each_entry(ops, &tsn_shim_ops, head) {
+ if (strcmp(name, ops->shim_name) == 0)
+ return ops;
+ }
+ return NULL;
+}
+
+ssize_t tsn_shim_export_probe_triggers(char *page)
+{
+ struct tsn_shim_ops *ops;
+ ssize_t res = 0;
+
+ if (!page)
+ return 0;
+ res += snprintf((page + res), PAGE_SIZE - res, "none\n");
+ if (!list_empty(&tsn_shim_ops)) {
+ list_for_each_entry(ops, &tsn_shim_ops, head) {
+ res += snprintf((page + res), PAGE_SIZE - res, "%s\n",
+ ops->shim_name);
+ }
+ }
+ return res;
+}
+
+void tsn_lock_init(struct tsn_link *link)
+{
+ spin_lock_init(&link->tlock);
+ link->lflags = 0;
+ raw_spin_lock_init(&link->llock);
+}
+
+struct tsn_link *tsn_create_and_add_link(struct tsn_nic *nic)
+{
+ u64 sid = 0;
+ struct tsn_link *link = kzalloc(sizeof(*link), GFP_KERNEL);
+
+ if (!link)
+ return NULL;
+ if (!nic) {
+ kfree(link);
+ return NULL;
+ }
+
+ tsn_lock_init(link);
+ tsn_link_off(link);
+ tsn_lb_disable(link);
+ do {
+ sid = prandom_u32();
+ sid |= prandom_u32() << 31;
+ } while (tsn_find_by_stream_id(sid));
+ link->stream_id = sid;
+
+ /* There's a slim chance that we actually hit on the first frame
+ * of data, but if we do, remote seqnr is most likely 0. If this
+ * is not up to par,, fix in rx_handler
+ */
+ link->last_seqnr = 0xff;
+
+ /* class B audio 48kHz sampling, S16LE, 2ch and IEC61883-6 CIP
+ * header
+ */
+ link->max_payload_size = 48;
+ link->shim_header_size = 8;
+
+ /* Default VLAN ID is SR_PVID (2) unless otherwise supplied from
+ * MSRP, PCP is default 3 for class A, 2 for Class B (See IEEE
+ * 802.1Q-2011, table 6-6)
+ */
+ link->vlan_id = 0x2;
+ link->class = SR_CLASS_B;
+
+ link->buffer_size = 16536;
+ /* default: talker since listener isn't implemented yet. */
+ link->estype_talker = 1;
+
+ link->nic = nic;
+
+ link->ts_net_ns = ktime_to_ns(ktime_get());
+ link->ts_delta_ns = 250000; /* initial class i B */
+ link->ts_exp_alpha = 13107; /* ~80% */
+
+ /* Add the newly created link to the hashmap of all active links.
+ *
+ * test if sid is present in hashmap already (barf on that)
+ */
+
+ tsn_list_lock(&tlist);
+ hash_add(tlinks, &link->node, link->stream_id);
+ tsn_list_unlock(&tlist);
+ pr_info("%s: added link with stream_id: %llu\n",
+ __func__, link->stream_id);
+
+ return link;
+}
+
+ssize_t tsn_get_stream_ids(char *page, ssize_t len)
+{
+ struct tsn_link *link;
+ struct hlist_node *tmp;
+ char *buffer = page;
+ int bkt;
+
+ if (!page)
+ return 0;
+
+ if (hash_empty(tlinks))
+ return sprintf(buffer, "no links registered\n");
+
+ hash_for_each_safe(tlinks, bkt, tmp, link, node)
+ buffer += sprintf(buffer, "%llu\n", link->stream_id);
+
+ return (buffer - page);
+}
+
+struct tsn_link *tsn_find_by_stream_id(u64 sid)
+{
+ struct tsn_link *link;
+
+ if (hash_empty(tlinks))
+ return 0;
+
+ hash_for_each_possible(tlinks, link, node, sid) {
+ if (link->stream_id == sid)
+ return link;
+ }
+
+ return NULL;
+}
+
+void tsn_remove_link(struct tsn_link *link)
+{
+ if (!link)
+ return;
+ tsn_net_close(link);
+ tsn_list_lock(&tlist);
+ hash_del(&link->node);
+ if (link->ops) {
+ link->ops->media_close(link);
+ link->ops = NULL;
+ }
+
+ tsn_list_unlock(&tlist);
+}
+
+void tsn_remove_and_free_link(struct tsn_link *link)
+{
+ if (!link)
+ return;
+ tsn_remove_link(link);
+ kfree(link);
+}
+
+
+void tsn_readd_link(struct tsn_link *link, u64 newkey)
+{
+ if (!link)
+ return;
+ tsn_lock(link);
+ if (hash_hashed(&link->node)) {
+ pr_info("%s: updating link with stream_id %llu -> %llu\n",
+ __func__, link->stream_id, newkey);
+ tsn_remove_link(link);
+ }
+
+ link->stream_id = newkey;
+ tsn_unlock(link);
+
+ hash_add(tlinks, &link->node, link->stream_id);
+}
+
+static int _tsn_capable_nic(struct net_device *netdev, struct tsn_nic *nic)
+{
+ return -EINVAL;
+ if (!nic || !netdev || !netdev->netdev_ops ||
+ !netdev->netdev_ops->ndo_tsn_capable)
+ return -EINVAL;
+
+ if (netdev->netdev_ops->ndo_tsn_capable(netdev) > 0)
+ nic->capable = 1;
+
+ pr_info("%s: ndo_tsn_capable() present, got %s\n", __func__, nic->capable ? "Capable" : "Not capable");
+ return 0;
+}
+
+/* Identify all TSN-capable NICs in the system
+ */
+static int tsn_nic_probe(void)
+{
+ struct net *net;
+ struct net_device *netdev;
+ struct tsn_nic *nic;
+
+ net = &init_net;
+ rcu_read_lock();
+ for_each_netdev_rcu(net, netdev) {
+ pr_info("Found %s, alias %s on irq %d\n",
+ netdev->name,
+ netdev->ifalias,
+ netdev->irq);
+ pr_info("MAC: %pM", netdev->dev_addr);
+ if (netdev->tx_queue_len)
+ pr_info("Tx queue length: %lu\n", netdev->tx_queue_len);
+
+ /* GFP_KERNEL is preferred, but we are in rcu_read_lock()-region */
+ nic = kzalloc(sizeof(*nic), GFP_ATOMIC);
+
+ if (!nic) {
+ pr_err("Could not allocate memory for tsn_nic!\n");
+ return -ENOMEM;
+ }
+ nic->dev = netdev;
+ nic->txq = netdev->num_tx_queues;
+ nic->name = netdev->name;
+ nic->tsn_list = &tlist;
+ nic->dma_size = 1048576;
+
+ /* This is the default values for A and B specified in 802.1Q */
+ nic->pcp_a = 3;
+ nic->pcp_b = 2;
+
+ _tsn_capable_nic(netdev, nic);
+
+ /* if not capable and we are not in debug-mode, drop nic
+ * and continue
+ */
+ if (!nic->capable && !in_debug) {
+ pr_info("Invalid capabilities for NIC (%s), dropping from TSN list\n",
+ netdev->name);
+ kfree(nic);
+ continue;
+ }
+
+ INIT_LIST_HEAD(&nic->list);
+ tsn_list_lock(&tlist);
+ list_add_tail(&nic->list, &tlist.head);
+ tlist.num_avail++;
+ tsn_list_unlock(&tlist);
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
+static void tsn_free_nic_list(struct tsn_list *list)
+{
+ struct tsn_nic *tmp, *next;
+
+ tsn_list_lock(list);
+ list_for_each_entry_safe(tmp, next, &list->head, list) {
+ pr_info("Dropping %s from list\n", tmp->dev->name);
+ list_del(&tmp->list);
+ tmp->dev = NULL;
+ kfree(tmp);
+ }
+ tsn_list_unlock(list);
+}
+
+/* all active links are stored in hashmap 'tlinks'
+ */
+static void tsn_remove_all_links(void)
+{
+ int bkt;
+ struct tsn_link *link;
+ struct hlist_node *tmp;
+
+ hash_for_each_safe(tlinks, bkt, tmp, link, node) {
+ pr_info("%s removing a link\n", __func__);
+ if (!tsn_teardown_link(link)) {
+ tsn_lock(link);
+
+ tsn_unlock(link);
+ }
+ }
+
+ pr_info("%s: all links have been removed\n", __func__);
+}
+
+static int __init tsn_init_module(void)
+{
+ int ret = 0;
+
+ INIT_LIST_HEAD(&tlist.head);
+ spin_lock_init(&tlist.lock);
+
+ atomic_set(&tlist.running, 0);
+ tlist.period_ns = 1000000;
+
+ /* Find all NICs, attach a rx-handler for sniffing out TSN
+ * traffic on *all* of them.
+ */
+ tlist.num_avail = 0;
+ ret = tsn_nic_probe();
+ if (ret < 0) {
+ pr_err("%s: somethign went awry whilst probing for NICs, aborting\n",
+ __func__);
+ goto out;
+ }
+
+ if (!tlist.num_avail) {
+ pr_err("%s: No capable NIC found. Perhaps load with in_debug=1 ?\n",
+ __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* register Rx-callbacks for all (valid) NICs */
+ ret = tsn_net_add_rx(&tlist);
+ if (ret < 0) {
+ pr_err("%s: Could add Rx-handler, aborting\n", __func__);
+ goto error_rx_out;
+ }
+
+ /* init DMA regions etc */
+ ret = tsn_net_prepare_tx(&tlist);
+ if (ret < 0) {
+ pr_err("%s: could not prepare Tx, aborting\n", __func__);
+ goto error_tx_out;
+ }
+
+ /* init hashtable */
+ hash_init(tlinks);
+
+ /* init configfs */
+ ret = tsn_configfs_init(&tlist);
+ if (ret < 0) {
+ pr_err("%s: Could not initialize configfs properly (%d), aborting\n",
+ __func__, ret);
+ goto error_cfs_out;
+ }
+ pr_info("%s: configfs created\n", __func__);
+
+ /* creating worker and thread.
+ * hrtimer_init will wake tsn_worker when timer is ready
+ *
+ * FIXME: make it possible to pin both timer and worker to a
+ * specific core.
+ */
+ tlist.should_run = 0;
+ ret = tsn_worker_init(&tlist);
+ if (ret < 0) {
+ pr_err("Failed to create tsn_worker!\n");
+ goto error_hrt_out;
+ }
+
+ ret = tsn_hrtimer_init(&tlist);
+ if (ret < 0) {
+ pr_err("%s: could not init hrtimer properly, aborting\n",
+ __func__);
+ goto error_thread_out;
+ }
+
+ pr_info("TSN subsystem init OK\n");
+ return 0;
+
+error_thread_out:
+ tsn_worker_exit(&tlist);
+error_hrt_out:
+ tsn_remove_all_links();
+ tsn_configfs_exit(&tlist);
+error_cfs_out:
+ tsn_net_disable_tx(&tlist);
+error_tx_out:
+ tsn_net_remove_rx(&tlist);
+error_rx_out:
+ tsn_free_nic_list(&tlist);
+out:
+ return ret;
+}
+
+static void __exit tsn_exit_module(void)
+{
+ pr_warn("removing module TSN\n");
+
+ tsn_worker_exit(&tlist);
+
+ tsn_hrtimer_exit(&tlist);
+
+ tsn_remove_all_links();
+ tsn_configfs_exit(&tlist);
+
+ /* Unregister Rx-handlers if set */
+ tsn_net_remove_rx(&tlist);
+
+ tsn_net_disable_tx(&tlist);
+
+ tsn_free_nic_list(&tlist);
+
+ pr_warn("TSN exit\n");
+}
+module_param(in_debug, int, S_IRUGO);
+module_param(on_cpu, int, S_IRUGO);
+module_init(tsn_init_module);
+module_exit(tsn_exit_module);
+MODULE_AUTHOR("Henrik Austad");
+MODULE_LICENSE("GPL");
diff --git a/net/tsn/tsn_header.c b/net/tsn/tsn_header.c
new file mode 100644
index 0000000..1840783
--- /dev/null
+++ b/net/tsn/tsn_header.c
@@ -0,0 +1,162 @@
+/*
+ * Network header handling for TSN
+ *
+ * Copyright (C) 2015- Henrik Austad <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/tsn.h>
+#include <trace/events/tsn.h>
+
+#include "tsn_internal.h"
+
+#define AVTP_GPTP_TIMEMASK 0xFFFFFFFF
+
+static u32 tsnh_avtp_timestamp(u64 ptime_ns)
+{
+ /* See 1722-2011, 5.4.8
+ *
+ * (AS_sec * 1e9 + AS_ns) % 2^32
+ *
+ * Just use ktime_get_ns() and grab lower 32 bits of it.
+ */
+ /* u64 ns = ktime_to_ns(ktime_get()); */
+ u32 gptp_ts = ptime_ns & AVTP_GPTP_TIMEMASK;
+ return gptp_ts;
+}
+
+int tsnh_ch_init(struct avtp_ch *header)
+{
+ if (!header)
+ return -EINVAL;
+ header = memset(header, 0, sizeof(*header));
+
+ /* This should be changed when setting control / data
+ * content. Set to experimental to allow for strange content
+ * should callee not do job properly
+ */
+ header->subtype = TSN_EF_STREAM;
+
+ header->version = 0;
+ return 0;
+}
+
+
+int tsnh_validate_du_header(struct tsn_link *link, struct avtp_ch *ch,
+ struct sk_buff *skb)
+{
+ struct avtpdu_header *header = (struct avtpdu_header *)ch;
+ struct sockaddr_ll *sll;
+ u16 bytes;
+ u8 seqnr;
+
+ if (ch->cd)
+ return -EINVAL;
+
+ /* As a minimum, we should match the sender's MAC to the
+ * expected MAC before we pass the frame along.
+ *
+ * This does not give much in the way of security (a malicious
+ * user could fake this), but it should remove accidents and
+ * errors.
+ */
+ sll = (struct sockaddr_ll *)&skb->cb;
+ sll->sll_halen = dev_parse_header(skb, sll->sll_addr);
+ if (sll->sll_halen != 6)
+ return -EPROTO;
+ if (memcmp(link->remote_mac, &sll->sll_addr, 6))
+ return -EPROTO;
+
+ /* Current iteration of TSN has version 0b000 only */
+ if (ch->version)
+ return -EPROTO;
+
+ /* Invalid StreamID, should not have ended up here in the first
+ * place (since we do DU only), if invalid sid, how did we find
+ * the link?
+ */
+ if (!ch->sv)
+ return -EPROTO;
+
+ /* Check seqnr, if we have lost one frame, we _could_ insert an
+ * empty frame, but since we have frame-guarantee from 802.1Qav,
+ * we don't. Shim should handle missing frames should they occur
+ *
+ * (TODO: need to propagate seqnr to shim)
+ */
+ seqnr = (link->last_seqnr + 1) & 0xff;
+ if (header->seqnr != seqnr)
+ return -EPROTO;
+
+ bytes = ntohs(header->sd_len);
+ if (bytes == 0 || bytes > link->max_payload_size)
+ return -EINVAL;
+
+ /* let shim validate header here as well */
+ if (link->ops->validate_header &&
+ link->ops->validate_header(link, header) != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+int tsnh_assemble_du(struct tsn_link *link, struct avtpdu_header *header,
+ size_t bytes, u64 ts_pres_ns)
+{
+ if (!header || !link)
+ return -EINVAL;
+
+ tsnh_ch_init((struct avtp_ch *)header);
+ header->cd = 0;
+ header->sv = 1;
+ header->mr = 0;
+ header->gv = 0;
+ header->tv = 1;
+ header->tu = 0;
+ header->avtp_timestamp = htonl(tsnh_avtp_timestamp(ts_pres_ns));
+ header->gateway_info = 0;
+ header->sd_len = htons(bytes);
+
+ if (!link->ops) {
+ pr_err("%s: No available ops, cannot assemble data-unit\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ header->stream_id = cpu_to_be64(link->stream_id);
+ header->seqnr = link->last_seqnr++;
+ link->ops->assemble_header(link, header, bytes);
+
+ return 0;
+}
+
+int tsnh_handle_du(struct tsn_link *link, struct avtp_ch *ch)
+{
+ struct avtpdu_header *header = (struct avtpdu_header *)ch;
+ void *data;
+ u16 bytes;
+ int ret;
+
+ bytes = ntohs(header->sd_len);
+
+ trace_tsn_du(link, bytes);
+ /* bump seqnr */
+ data = link->ops->get_payload_data(link, header);
+ if (!data)
+ return -EINVAL;
+
+ link->last_seqnr = header->seqnr;
+ ret = tsn_buffer_write_net(link, data, bytes);
+ if (ret != bytes)
+ return ret;
+
+ return 0;
+}
diff --git a/net/tsn/tsn_internal.h b/net/tsn/tsn_internal.h
new file mode 100644
index 0000000..41e1775
--- /dev/null
+++ b/net/tsn/tsn_internal.h
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2015- Henrik Austad <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef _TSN_INTERNAL_H_
+#define _TSN_INTERNAL_H_
+#include <linux/tsn.h>
+
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+
+/* TODO:
+ * - hide tsn-structs and provide handlers
+ * - decouple config/net from core
+ */
+
+struct avtpdu_header;
+struct tsn_link;
+struct tsn_shim_ops;
+
+#define IS_TSN_FRAME(x) (ntohs(x) == ETH_P_TSN)
+#define IS_PTP_FRAME(x) (ntohs(x) == ETH_P_1588)
+#define IS_1Q_FRAME(x) (ntohs(x) == ETH_P_8021Q)
+
+/**
+ * tsn_add_link - create and add a new link to the system
+ *
+ * Note: this will not enable the link, just allocate most of the data
+ * required for the link. One notable exception being the buffer as we
+ * can modify the buffersize before we start the link.
+ *
+ * @param nic : the nic the link is tied to
+ * @returns the new link
+ */
+struct tsn_link *tsn_create_and_add_link(struct tsn_nic *nic);
+
+/**
+ * tsn_get_stream_ids - write all current Stream IDs into the page.
+ *
+ * @param page the page to write into
+ * @param len size of page
+ * @returns the number of bytes written
+ */
+ssize_t tsn_get_stream_ids(char *page, ssize_t len);
+
+/**
+ * tsn_find_by_stream_id - given a sid, find the corresponding link
+ *
+ * @param sid stream_id
+ * @returns tsn_link struct or NULL if not found
+ */
+struct tsn_link *tsn_find_by_stream_id(u64 sid);
+
+/**
+ * tsn_readd_link - make sure a link is moved to the correct bucket when
+ * stream_id is updated
+ *
+ * @link the TSN link
+ * @old_key previous key for which it can be located in the hashmap
+ *
+ */
+void tsn_readd_link(struct tsn_link *link, u64 old_key);
+
+/**
+ * tsn_remove_link: cleanup and remove from internal storage
+ *
+ * @link: the link to be removed
+ */
+void tsn_remove_link(struct tsn_link *link);
+
+/**
+ * tsn_remove_and_free_link: remove link and remove it from the list
+ *
+ * @param: the link to completely remove
+ */
+void tsn_remove_and_free_link(struct tsn_link *link);
+
+/**
+ * tsn_set_shim_ops - tie a shim to a link
+ *
+ * This will just set the shim-ops in the link.
+ *
+ * @link: active link
+ * @shim_ops: the shim to associate with this link
+ * @return: 0 on success, negative on error
+ */
+int tsn_set_shim_ops(struct tsn_link *link, struct tsn_shim_ops *shim_ops);
+
+/**
+ * tsn_prepare_link - make link ready for usage
+ *
+ * Caller is happy with the different knobs, this will create the link and start
+ * pushing the data.
+ *
+ * Requirement:
+ * - callback registered
+ * - State set to either Talker or Listener
+ *
+ * @param active link
+ * @return 0 on success, negative on error
+ */
+int tsn_prepare_link(struct tsn_link *link);
+int tsn_teardown_link(struct tsn_link *link);
+
+/**
+ * tsn_set_external_buffer - force an update of the buffer
+ *
+ * This will cause tsn_core to use an external buffer. If external
+ * buffering is already in use, this has the effect of forcing an update
+ * of the buffer.
+ *
+ * This will cause tsn_core to swap buffers. The current buffer is
+ * returned and the new is used in place.
+ *
+ * Note: If the new buffer is NULL or buffer_size is less than
+ * max_payload_size, the result can be interesting (by calling this
+ * function, you claim to know what you are doing and should pass sane
+ * values).
+ *
+ * This can also be used if you need to resize the buffer in use.
+ *
+ * Core will continue to use the tsn_shim_swap when the new buffer is
+ * full.
+ *
+ * @param link current link owning the buffer
+ * @param buffer new buffer to use
+ * @param buffer_size size of new buffer
+ * @return old buffer
+ */
+void *tsn_set_external_buffer(struct tsn_link *link, void *buffer,
+ size_t buffer_size);
+
+/**
+ * tsn_buffer_write_net - write data *into* link->buffer from the network layer
+ *
+ * Used by tsn_net and will typicall accept very small pieces of data.
+ *
+ * @param link the link associated with the stream_id in the frame
+ * @param src pointer to data in buffer
+ * @param bytes number of bytes to copy
+ * @return number of bytes copied into the buffer
+ */
+int tsn_buffer_write_net(struct tsn_link *link, void *src, size_t bytes);
+
+/**
+ * tsn_buffer_read_net - read data from link->buffer and give to network layer
+ *
+ * When we send a frame, we grab data from the buffer and add it to the
+ * sk_buff->data, this is primarily done by the Tx-subsystem in tsn_net
+ * and is typically done in small chunks
+ *
+ * @param link current link that holds the buffer
+ * @param buffer the buffer to copy into, must be at least of size bytes
+ * @param bytes number of bytes.
+ *
+ * Note that this routine does NOT CARE about channels, samplesize etc,
+ * it is a _pure_ copy that handles ringbuffer wraps etc.
+ *
+ * This function have side-effects as it will update internal tsn_link
+ * values and trigger refill() should the buffer run low.
+ *
+ * @return Bytes copied into link->buffer, negative value upon error.
+ */
+int tsn_buffer_read_net(struct tsn_link *link, void *buffer, size_t bytes);
+
+/**
+ * tsn_core_running(): test if the link is running
+ *
+ * By running, we mean that it is configured and a proper shim has been
+ * loaded. It does *not* mean that we are currently pushing data in any
+ * direction, see tsn_net_buffer_disabled() for this
+ *
+ * @param struct tsn_link active link
+ * @returns 1 if core is running
+ */
+static inline int tsn_core_running(struct tsn_list *list)
+{
+ if (list)
+ return atomic_read(&list->running);
+ return 0;
+}
+
+/**
+ * _tsn_buffer_used - how much of the buffer is filled with valid data
+ *
+ * - assumes link->running in state running
+ * - will ignore change changed state
+ *
+ * We write to head, read from tail.
+ */
+static inline size_t _tsn_buffer_used(struct tsn_link *link)
+{
+ return (link->head - link->tail) % link->used_buffer_size;
+}
+
+/* -----------------------------
+ * ConfigFS handling
+ */
+int tsn_configfs_init(struct tsn_list *tlist);
+void tsn_configfs_exit(struct tsn_list *tlist);
+
+/* -----------------------------
+ * TSN Header
+ */
+
+static inline size_t tsnh_len(void)
+{
+ /* include 802.1Q tag */
+ return sizeof(struct avtpdu_header);
+}
+
+static inline u16 tsnh_len_all(void)
+{
+ return (u16)tsnh_len() + VLAN_ETH_HLEN;
+}
+
+static inline u16 tsnh_frame_len(struct tsn_link *link)
+{
+ if (!link)
+ return 0;
+ pr_info("max_payload_size=%u, shim_header_size=%u, tsnh_len_all()=%u\n",
+ link->max_payload_size, link->shim_header_size, tsnh_len_all());
+ return link->max_payload_size + link->shim_header_size + tsnh_len_all();
+}
+
+static inline u16 tsnh_data_len(struct avtpdu_header *header)
+{
+ if (!header)
+ return 0;
+ return ntohs(header->sd_len);
+}
+
+/**
+ * tsnh_payload_size_valid - if the entire payload is within size-limit
+ *
+ * Ensure that max_payload_size and shim_header_size is within acceptable limits
+ *
+ * We need both values to calculate the payload size when reserving
+ * bandwidth, but only payload-size when instructing the shim to copy
+ * out data for us.
+ *
+ * @param max_payload_size requested payload to send in each frame (upper limit)
+ * @return 0 on invalid, 1 on valid
+ */
+static inline int tsnh_payload_size_valid(u16 max_payload_size,
+ u16 shim_hdr_size)
+{
+ /* VLAN_ETH_ZLEN 64 */
+ /* VLAN_ETH_FRAME_LEN 1518 */
+ u32 framesize = max_payload_size + tsnh_len_all() + shim_hdr_size;
+
+ return framesize >= VLAN_ETH_ZLEN && framesize <= VLAN_ETH_FRAME_LEN;
+}
+
+/**
+ * tsnh_validate_du_header - basic header validation
+ *
+ * This expects the parameters to be present and the link-lock to be
+ * held.
+ *
+ * @param header header to verify
+ * @param link owner of stream
+ * @param socket_buffer
+ * @return 0 on valid, negative on invalid/error
+ */
+int tsnh_validate_du_header(struct tsn_link *link, struct avtp_ch *ch,
+ struct sk_buff *skb);
+
+/**
+ * tsnh_assemble_du - assemble header and copy data from buffer
+ *
+ * It expects tsn-lock to be held when called
+ *
+ * This function will initialize the header and pass final init to
+ * shim->assemble_header before copying data into the buffer.
+ *
+ * It assumes that 'bytes' is a sane value, i.e. that it is a valid
+ * multiple of number of channels, sample size etc.
+ *
+ * @param link Current TSN link, also holds the buffer
+ *
+ * @param header header to assemble for data
+ *
+ * @param bytes Number of bytes to send in this frame
+ *
+ * @param ts_pres_ns current for when the frame should be presented or
+ * considered valid by the receiving end. In
+ * nanoseconds since epoch, will be converted to gPTP
+ * compatible timestamp.
+ *
+ * @return 0 on success, negative on error
+ */
+int tsnh_assemble_du(struct tsn_link *link, struct avtpdu_header *header,
+ size_t bytes, u64 ts_pres_ns);
+
+/**
+ * tsnh_handle_du - handle incoming data and store to media-buffer
+ *
+ * This assumes that the frame actually belongs to the link and that it
+ * has passed basic validation. It expects the link-lock to be held.
+ *
+ * @param link Link associated with stream_id
+ * @param header Header of incoming frame
+ * @return number of bytes copied to buffer or negative on error
+ */
+int tsnh_handle_du(struct tsn_link *link, struct avtp_ch *ch);
+
+static inline struct avtp_ch *tsnh_ch_from_skb(struct sk_buff *skb)
+{
+ if (!skb)
+ return NULL;
+ if (!IS_TSN_FRAME(eth_hdr(skb)->h_proto))
+ return NULL;
+
+ return (struct avtp_ch *)skb->data;
+}
+
+/**
+ * tsn_net_add_rx - add Rx handler for all NICs listed
+ *
+ * @param list tsn_list to add Rx handler to
+ * @return 0 on success, negative on error
+ */
+int tsn_net_add_rx(struct tsn_list *list);
+
+/**
+ * tsn_net_remove_rx - remove Rx-handlers for all tsn_nics
+ *
+ * Go through all NICs and remove those Rx-handlers we have
+ * registred. If someone else has added an Rx-handler to the NIC, we do
+ * not touch it.
+ *
+ * @param list list of all tsn_nics (with links)
+ */
+void tsn_net_remove_rx(struct tsn_list *list);
+
+/**
+ * tsn_net_open_tx - prepare all capable links for Tx
+ *
+ * This will prepare all NICs for Tx, and those marked as 'capable'
+ * will be initialized with DMA regions. Note that this is not the final
+ * step for preparing for Tx, it is only when we have active links that
+ * we know how much bandwidth we need and then can set the appropriate
+ * idleSlope params etc.
+ *
+ * @tlist: list of all available card
+ * @return: negative on error, on success the number of prepared NICS
+ * are returned.
+ */
+int tsn_net_prepare_tx(struct tsn_list *tlist);
+
+/**
+ * tsn_net_disable_tx - disable Tx on card
+ *
+ * This frees DMA-memory from capable NICs
+ *
+ * @param tsn_list: link to all available NICs used by TSN
+ */
+void tsn_net_disable_tx(struct tsn_list *tlist);
+
+/**
+ * tsn_net_close - close down link properly
+ *
+ * @param struct tsn_link * active link to close down
+ */
+void tsn_net_close(struct tsn_link *link);
+
+/**
+ * tsn_net_send_set - send a set of frames
+ *
+ * We want to assemble a number of sk_buffs at a time and ship them off
+ * in a single go and then go back to sleep. Pacing should be done by
+ * hardware, or if we are in in_debug, we don't really care anyway
+ *
+ * @param link : current TSN-link
+ * @param num : the number of frames to create
+ * @param ts_base_ns : base timestamp for when the frames should be
+ * considered valid
+ * @param ts_delta_ns : time between each frame in the set
+ *
+ * @returns then number of frames sent or negative on error
+ */
+int tsn_net_send_set(struct tsn_link *link, size_t num, u64 ts_base_ns,
+ u64 ts_delta_ns);
+
+#endif /* _TSN_INTERNAL_H_ */
diff --git a/net/tsn/tsn_net.c b/net/tsn/tsn_net.c
new file mode 100644
index 0000000..214c0d6
--- /dev/null
+++ b/net/tsn/tsn_net.c
@@ -0,0 +1,392 @@
+/*
+ * Network part of TSN
+ *
+ * Copyright (C) 2015- Henrik Austad <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/if_vlan.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+
+#include <linux/tsn.h>
+#include <trace/events/tsn.h>
+#include "tsn_internal.h"
+
+/**
+ * tsn_rx_handler - consume all TSN-tagged frames and forward to tsn_link.
+ *
+ * When registered, it will consume all TSN-tagged frames belonging to
+ * registered Stream IDs.
+ *
+ * Unknown StreamIDs will be passed through without being touched.
+ *
+ * @param pskb sk_buff with incomign data
+ * @returns RX_HANDLER_CONSUMED for TSN frames to known StreamIDs,
+ * RX_HANDLER_PASS for everything else.
+ */
+static rx_handler_result_t tsn_rx_handler(struct sk_buff **pskb)
+{
+ struct sk_buff *skb = *pskb;
+ const struct ethhdr *ethhdr = eth_hdr(skb);
+ struct avtp_ch *ch;
+ struct tsn_link *link;
+ rx_handler_result_t ret = RX_HANDLER_PASS;
+
+ ch = tsnh_ch_from_skb(skb);
+ if (!ch)
+ return RX_HANDLER_PASS;
+ /* We do not (currently) touch control_data frames. */
+ if (ch->cd)
+ return RX_HANDLER_PASS;
+
+ link = tsn_find_by_stream_id(be64_to_cpu(ch->stream_id));
+ if (!link)
+ return RX_HANDLER_PASS;
+
+ tsn_lock(link);
+
+ if (!tsn_link_is_on(link) || link->estype_talker)
+ goto out_unlock;
+
+ /* If link->ops is not set yet, there's nothing we can do, just
+ * ignore this frame
+ */
+ if (!link->ops)
+ goto out_unlock;
+
+ if (tsnh_validate_du_header(link, ch, skb))
+ goto out_unlock;
+
+ /* Update link network time
+ *
+ * TODO: using the time in the skb is flawed, we should use
+ * actual time from the NIC and then correlate that to timestamp
+ * in frame.
+ */
+ tsn_update_net_time(link, ktime_to_ns(skb_get_ktime(skb)), 1);
+ trace_tsn_rx_handler(link, ethhdr, be64_to_cpu(ch->stream_id));
+
+ /* Handle dataunit, if it failes, pass on the frame and let
+ * userspace pick it up.
+ */
+ if (tsnh_handle_du(link, ch) < 0)
+ goto out_unlock;
+
+ /* Done, data has been copied, free skb and return consumed */
+ consume_skb(skb);
+ ret = RX_HANDLER_CONSUMED;
+
+out_unlock:
+ tsn_unlock(link);
+ return ret;
+}
+
+int tsn_net_add_rx(struct tsn_list *tlist)
+{
+ struct tsn_nic *nic;
+
+ if (!tlist)
+ return -EINVAL;
+
+ /* Setup receive handler for TSN traffic.
+ *
+ * Receive will happen all the time, once a link is active as a
+ * Listener, we will add a hook into the receive-handler to
+ * steer the frames to the correct link.
+ *
+ * We try to add Rx-handlers to all the card listed in tlist (we
+ * assume core has filtered the NICs appropriatetly sothat only
+ * TSN-capable cards are present).
+ */
+ tsn_list_lock(tlist);
+ list_for_each_entry(nic, &tlist->head, list) {
+ rtnl_lock();
+ if (netdev_rx_handler_register(nic->dev, tsn_rx_handler, nic) < 0) {
+ pr_err("%s: could not attach an Rx-handler to %s, this link will not be able to accept TSN traffic\n",
+ __func__, nic->name);
+ rtnl_unlock();
+ continue;
+ }
+ rtnl_unlock();
+ pr_info("%s: attached rx-handler to %s\n",
+ __func__, nic->name);
+ nic->rx_registered = 1;
+ }
+ tsn_list_unlock(tlist);
+ return 0;
+}
+
+void tsn_net_remove_rx(struct tsn_list *tlist)
+{
+ struct tsn_nic *nic;
+
+ if (!tlist)
+ return;
+ tsn_list_lock(tlist);
+ list_for_each_entry(nic, &tlist->head, list) {
+ rtnl_lock();
+ if (nic->rx_registered)
+ netdev_rx_handler_unregister(nic->dev);
+ rtnl_unlock();
+ nic->rx_registered = 0;
+ pr_info("%s: RX-handler for %s removed\n",
+ __func__, nic->name);
+ }
+ tsn_list_unlock(tlist);
+}
+
+int tsn_net_prepare_tx(struct tsn_list *tlist)
+{
+ struct tsn_nic *nic;
+ struct device *dev;
+ int ret = 0;
+
+ if (!tlist)
+ return -EINVAL;
+
+ tsn_list_lock(tlist);
+ list_for_each_entry(nic, &tlist->head, list) {
+ if (!nic)
+ continue;
+ if (!nic->capable)
+ continue;
+
+ if (!nic->dev->netdev_ops)
+ continue;
+
+ dev = nic->dev->dev.parent;
+ nic->dma_mem = dma_alloc_coherent(dev, nic->dma_size,
+ &nic->dma_handle, GFP_KERNEL);
+ if (!nic->dma_mem) {
+ nic->capable = 0;
+ nic->dma_size = 0;
+ continue;
+ }
+ ret++;
+ }
+ tsn_list_unlock(tlist);
+ pr_info("%s: configured %d cards to use DMA\n", __func__, ret);
+ return ret;
+}
+
+void tsn_net_disable_tx(struct tsn_list *tlist)
+{
+ struct tsn_nic *nic;
+ struct device *dev;
+ int res = 0;
+
+ if (!tlist)
+ return;
+ tsn_list_lock(tlist);
+ list_for_each_entry(nic, &tlist->head, list) {
+ if (nic->capable && nic->dma_mem) {
+ dev = nic->dev->dev.parent;
+ dma_free_coherent(dev, nic->dma_size, nic->dma_mem,
+ nic->dma_handle);
+ res++;
+ }
+ }
+ tsn_list_unlock(tlist);
+ pr_info("%s: freed DMA regions from %d cards\n", __func__, res);
+}
+
+void tsn_net_close(struct tsn_link *link)
+{
+ /* struct tsn_rx_handler_data *rx_data; */
+
+ /* Careful! we need to make sure that we actually succeeded in
+ * registering the handler in open unless we want to unregister
+ * some random rx_handler..
+ */
+ if (!link->estype_talker) {
+ ;
+ /* Make sure we notify rx-handler so it doesn't write
+ * into NULL
+ */
+ }
+}
+
+static inline u16 _get_8021q_vid(struct tsn_link *link)
+{
+ u16 pcp = sr_class_to_pcp(link->nic, link->class);
+ /* If not explicitly provided, use SR_PVID 0x2*/
+ return (link->vlan_id & VLAN_VID_MASK) | ((pcp & 0x7) << 13);
+}
+
+static u16 __tsn_pick_tx(struct net_device *dev, struct sk_buff *skb)
+{
+ printk_once(KERN_ERR "TSN ERROR: sending frame via NIC without valid ndo_select_queue, defaulting to tx-ring 0\n");
+ return 0;
+}
+
+/* create and initialize a sk_buff with appropriate TSN Header values
+ *
+ * layout of frame:
+ * - Ethernet header
+ * dst (6) | src (6) | 802.1Q (4) | EtherType (2)
+ * - 1722 (sizeof struct avtpdu)
+ * - payload data
+ * - type header (e.g. iec61883-6 hdr)
+ * - payload data
+ *
+ * Required size:
+ * Ethernet: 18 -> VLAN_ETH_HLEN
+ * 1722: tsnh_len()
+ * payload: shim_hdr_size + data_bytes
+ *
+ * Note:
+ * - seqnr is not set
+ * - payload is not set
+ */
+static struct sk_buff *_skbuf_create_init(struct tsn_link *link,
+ size_t data_bytes,
+ size_t shim_hdr_size,
+ u64 ts_pres_ns)
+{
+ struct sk_buff *skb = NULL;
+ struct avtpdu_header *avtpdu;
+ struct net_device *dev = link->nic->dev;
+ int res = 0;
+ u16 queue_index = 0;
+ size_t hdr_len = VLAN_ETH_HLEN;
+ size_t avtpdu_len = tsnh_len() + shim_hdr_size;
+ u16 vlan_tci = _get_8021q_vid(link);
+
+ if (data_bytes > link->used_buffer_size) {
+ printk_once(KERN_ERR "%s: data_bytes (%zu) exceed buffer-size (%zd), reducing size\n",
+ __func__,data_bytes, link->used_buffer_size);
+ data_bytes = link->used_buffer_size;
+ }
+
+ skb = alloc_skb(hdr_len + avtpdu_len + data_bytes + dev->needed_tailroom,
+ GFP_ATOMIC);
+ if (!skb)
+ return NULL;
+
+ skb_reserve(skb, hdr_len + avtpdu_len);
+ skb->dev = link->nic->dev;
+ skb_reset_mac_header(skb);
+ skb->network_header = skb->mac_header + VLAN_ETH_HLEN;
+ skb->priority = sr_class_to_pcp(link->nic, link->class);
+
+ /* copy shim-data
+ *
+ * This all hinges on that the shim-header size is set correctly
+ * via configfs, if that value is off, then this will fall
+ * apart.
+ */
+ res = tsn_buffer_read_net(link, skb_put(skb, data_bytes), data_bytes);
+ if (res != data_bytes) {
+ pr_err("%s: Could not copy %zd bytes of data. Res: %d\n",
+ __func__, data_bytes, res);
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* set avtpdu- && shim-header.
+ * data_bytes is requried to set fields of header correctly
+ */
+ avtpdu = (struct avtpdu_header *)skb_push(skb, avtpdu_len);
+ res = tsnh_assemble_du(link, avtpdu, data_bytes, ts_pres_ns);
+ if (res < 0) {
+ pr_err("%s: Error initializing header (-> %d) , we are in an inconsistent state!\n",
+ __func__, res);
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* set ethenet header */
+ res = skb_vlan_push(skb, htons(ETH_P_8021Q), vlan_tci);
+ if (res) {
+ pr_err("%s: could not insert tag (0x%04x) && proto (0x%04x) in buffer, aborting -> %d\n",
+ __func__, vlan_tci, htons(ETH_P_8021Q), res);
+ return NULL;
+ }
+
+ skb->protocol = htons(ETH_P_TSN);
+ skb->pkt_type = PACKET_OUTGOING;
+
+ skb_shinfo(skb)->tx_flags |= SKBTX_HW_TSTAMP;
+ skb_set_mac_header(skb, 0);
+
+ /* We are using a ethernet-type frame (even though we could send
+ * TSN over other medium.
+ *
+ * - skb_push(skb, ETH_HLEN)
+ * - set header htons(header)
+ * - set source addr (netdev mac addr)
+ * - set dest addr
+ * - return ETH_HLEN
+ */
+ if (!dev_hard_header(skb, skb->dev, ETH_P_TSN, link->remote_mac, NULL, 6)) {
+ pr_err("%s: could not copy remote MAC to ether-frame\n", __func__);
+ kfree(skb);
+ return NULL;
+ }
+
+ /* Set txqueue, must set queue_mapping, via ndo_select_queue */
+ if (dev->netdev_ops->ndo_select_queue)
+ queue_index = dev->netdev_ops->ndo_select_queue(dev, skb, NULL, __tsn_pick_tx);
+ queue_index = netdev_cap_txqueue(dev, queue_index);
+ skb_set_queue_mapping(skb, queue_index);
+
+ skb->csum = skb_checksum(skb, 0, hdr_len + data_bytes, 0);
+ return skb;
+}
+
+/**
+ * Send a set of frames as efficiently as possible
+ */
+int tsn_net_send_set(struct tsn_link *link, size_t num, u64 ts_base_ns,
+ u64 ts_delta_ns)
+{
+ struct sk_buff *skb;
+ struct net_device *dev;
+ size_t data_size;
+ int res;
+ size_t sent = 0;
+ u64 ts_pres_ns = ts_base_ns;
+
+ if (!link)
+ return -EINVAL;
+ dev = link->nic->dev;
+
+ while (sent < num) {
+ data_size = tsn_shim_get_framesize(link);
+ skb = _skbuf_create_init(link, data_size,
+ tsn_shim_get_hdr_size(link),
+ ts_pres_ns);
+ if (!skb) {
+ pr_err("%s: could not allocate memory for skb\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+
+ trace_tsn_pre_tx(link, skb, data_size);
+ res = dev_queue_xmit(skb);
+ if (res != NET_XMIT_SUCCESS) {
+ printk_once(KERN_WARNING "TSN ERROR: dev_queue_xmit() FAILED -> %d\n", res);
+ return sent;
+ }
+ ts_pres_ns += ts_delta_ns;
+ sent++;
+ }
+ trace_tsn_post_tx_set(link, sent);
+ return sent;
+}
--
2.7.4
From: Henrik Austad <[email protected]>
Provide a fair debug-window into TSN. It tries to use TRACE_CLASS as much
as possible and moves as much as possible of the logic into TP_printk() to
minimize tracing overhead.
Cc: "David S. Miller" <[email protected]>
Cc: Steven Rostedt <[email protected]> (maintainer:TRACING)
Cc: Ingo Molnar <[email protected]> (maintainer:TRACING)
Signed-off-by: Henrik Austad <[email protected]>
---
include/trace/events/tsn.h | 333 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 333 insertions(+)
create mode 100644 include/trace/events/tsn.h
diff --git a/include/trace/events/tsn.h b/include/trace/events/tsn.h
new file mode 100644
index 0000000..522229c
--- /dev/null
+++ b/include/trace/events/tsn.h
@@ -0,0 +1,333 @@
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM tsn
+
+#if !defined(_TRACE_TSN_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_TSN_H
+
+#include <linux/tsn.h>
+#include <linux/tracepoint.h>
+
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+/* #include <linux/skbuff.h> */
+DECLARE_EVENT_CLASS(tsn_buffer_template,
+
+ TP_PROTO(struct tsn_link *link,
+ size_t bytes),
+
+ TP_ARGS(link, bytes),
+
+ TP_STRUCT__entry(
+ __field(u64, stream_id)
+ __field(size_t, size)
+ __field(size_t, bsize)
+ __field(void *, buffer)
+ __field(void *, head)
+ __field(void *, tail)
+ __field(void *, end)
+ ),
+
+ TP_fast_assign(
+ __entry->stream_id = link->stream_id;
+ __entry->size = bytes;
+ __entry->bsize = link->used_buffer_size;
+ __entry->buffer = link->buffer;
+ __entry->head = link->head;
+ __entry->tail = link->tail;
+ __entry->end = link->end;
+ ),
+
+ TP_printk("stream_id=%llu, copy=%zd, buffer: %zd, avail=%zd, [buffer=%p, head=%p, tail=%p, end=%p]",
+ __entry->stream_id, __entry->size, __entry->bsize,
+ (__entry->head - __entry->tail) % __entry->bsize,
+ __entry->buffer, __entry->head, __entry->tail, __entry->end)
+);
+
+DEFINE_EVENT(tsn_buffer_template, tsn_buffer_write,
+ TP_PROTO(struct tsn_link *link, size_t bytes),
+ TP_ARGS(link, bytes)
+);
+
+DEFINE_EVENT(tsn_buffer_template, tsn_buffer_write_net,
+ TP_PROTO(struct tsn_link *link, size_t bytes),
+ TP_ARGS(link, bytes)
+);
+
+DEFINE_EVENT(tsn_buffer_template, tsn_buffer_read,
+ TP_PROTO(struct tsn_link *link, size_t bytes),
+ TP_ARGS(link, bytes)
+);
+
+DEFINE_EVENT(tsn_buffer_template, tsn_buffer_read_net,
+ TP_PROTO(struct tsn_link *link, size_t bytes),
+ TP_ARGS(link, bytes)
+);
+
+
+DECLARE_EVENT_CLASS(tsn_buffer_update,
+
+ TP_PROTO(struct tsn_link *link,
+ size_t reported_avail),
+
+ TP_ARGS(link, reported_avail),
+
+ TP_STRUCT__entry(
+ __field(u64, stream_id)
+ __field(size_t, bsize)
+ __field(void *, head)
+ __field(void *, tail)
+ __field(size_t, reported_left)
+ __field(size_t, low_water)
+ ),
+
+ TP_fast_assign(
+ __entry->stream_id = link->stream_id;
+ __entry->bsize = link->used_buffer_size;
+ __entry->head = link->head;
+ __entry->tail = link->tail;
+ __entry->reported_left = reported_avail;
+ __entry->low_water = link->low_water_mark;
+ ),
+
+ TP_printk("stream_id=%llu, buffer_size=%zd, avail=%zd, reported=%zd, low_water=%zd",
+ __entry->stream_id, __entry->bsize,
+ (__entry->head - __entry->tail) % __entry->bsize,
+ __entry->reported_left, __entry->low_water)
+);
+
+/* Bytes will be "reported left", i.e. how much more space we have in
+ * the buffer before we wrap.
+ */
+DEFINE_EVENT(tsn_buffer_update, tsn_refill,
+ TP_PROTO(struct tsn_link *link, size_t bytes),
+ TP_ARGS(link, bytes)
+);
+
+DEFINE_EVENT(tsn_buffer_update, tsn_buffer_drain,
+ TP_PROTO(struct tsn_link *link, size_t bytes),
+ TP_ARGS(link, bytes)
+);
+
+TRACE_EVENT(tsn_send_batch,
+
+ TP_PROTO(struct tsn_link *link,
+ int num_send,
+ u64 ts_base_ns,
+ u64 ts_delta_ns),
+
+ TP_ARGS(link, num_send, ts_base_ns, ts_delta_ns),
+
+ TP_STRUCT__entry(
+ __field(u64, stream_id)
+ __field(int, seqnr)
+ __field(int, num_send)
+ __field(u64, ts_base_ns)
+ __field(u64, ts_delta_ns)
+ ),
+
+ TP_fast_assign(
+ __entry->stream_id = link->stream_id;
+ __entry->seqnr = (int)link->last_seqnr;
+ __entry->ts_base_ns = ts_base_ns;
+ __entry->ts_delta_ns = ts_delta_ns;
+ __entry->num_send = num_send;
+ ),
+
+ TP_printk("stream_id=%llu, seqnr=%d, num_send=%d, ts_base_ns=%llu, ts_delta_ns=%llu",
+ __entry->stream_id, __entry->seqnr, __entry->num_send, __entry->ts_base_ns, __entry->ts_delta_ns)
+);
+
+TRACE_EVENT(tsn_rx_handler,
+
+ TP_PROTO(struct tsn_link *link,
+ const struct ethhdr *ethhdr,
+ u64 sid),
+
+ TP_ARGS(link, ethhdr, sid),
+
+ TP_STRUCT__entry(
+ __field(char *, name)
+ __field(u16, proto)
+ __field(u64, sid)
+ __field(u64, link_sid)
+ ),
+ TP_fast_assign(
+ __entry->name = link->nic->name;
+ __entry->proto = ethhdr->h_proto;
+ __entry->sid = sid;
+ __entry->link_sid = link->stream_id;
+ ),
+
+ TP_printk("name=%s, proto: 0x%04x, stream_id=%llu, link->sid=%llu",
+ __entry->name, ntohs(__entry->proto), __entry->sid, __entry->link_sid)
+);
+
+TRACE_EVENT(tsn_du,
+
+ TP_PROTO(struct tsn_link *link,
+ size_t bytes),
+
+ TP_ARGS(link, bytes),
+
+ TP_STRUCT__entry(
+ __field(u64, link_sid)
+ __field(size_t, bytes)
+ ),
+ TP_fast_assign(
+ __entry->link_sid = link->stream_id;
+ __entry->bytes = bytes;
+ ),
+
+ TP_printk("stream_id=%llu,bytes=%zu",
+ __entry->link_sid, __entry->bytes)
+);
+
+DECLARE_EVENT_CLASS(tsn_buffer_mgmt_class,
+
+ TP_PROTO(struct tsn_link *link, size_t size),
+
+ TP_ARGS(link, size),
+
+
+ TP_STRUCT__entry(
+ __field(u64, stream_id)
+ __field(size_t, size)
+ ),
+
+ TP_fast_assign(
+ __entry->stream_id = link->stream_id;
+ __entry->size = size;
+ ),
+
+ TP_printk("stream_id=%llu,buffer_size=%zu",
+ __entry->stream_id, __entry->size)
+
+);
+
+DEFINE_EVENT(tsn_buffer_mgmt_class, tsn_set_buffer,
+ TP_PROTO(struct tsn_link *link, size_t size),
+ TP_ARGS(link, size)
+);
+
+DEFINE_EVENT(tsn_buffer_mgmt_class, tsn_free_buffer,
+ TP_PROTO(struct tsn_link *link, size_t size),
+ TP_ARGS(link, size)
+);
+
+
+/* TODO: too long, need cleanup.
+ */
+TRACE_EVENT(tsn_pre_tx,
+
+ TP_PROTO(struct tsn_link *link, struct sk_buff *skb, size_t bytes),
+
+ TP_ARGS(link, skb, bytes),
+
+ TP_STRUCT__entry(
+ __field(u64, stream_id)
+ __field(u32, vlan_tag)
+ __field(size_t, bytes)
+ __field(size_t, data_len)
+ __field(unsigned int, headlen)
+ __field(u16, protocol)
+ __field(u16, prot_native)
+ __field(int, tx_idx)
+ __field(u16, mac_len)
+ __field(u16, hdr_len)
+ __field(u16, vlan_tci)
+ __field(u16, mac_header)
+ __field(unsigned int, tail)
+ __field(unsigned int, end)
+ __field(unsigned int, truesize)
+ ),
+
+ TP_fast_assign(
+ __entry->stream_id = link->stream_id;
+ __entry->vlan_tag = (skb_vlan_tag_present(skb) ? skb_vlan_tag_get(skb) : 0);
+ __entry->bytes = bytes;
+ __entry->data_len = skb->data_len;
+ __entry->headlen = skb_headlen(skb);
+ __entry->protocol = vlan_get_protocol(skb);
+ __entry->prot_native = skb->protocol;
+ __entry->tx_idx = skb_get_queue_mapping(skb);
+
+ __entry->mac_len = skb->mac_len;
+ __entry->hdr_len = skb->hdr_len;
+ __entry->vlan_tci = skb->vlan_tci;
+ __entry->mac_header = skb->mac_header;
+ __entry->tail = (unsigned int)skb->tail;
+ __entry->end = (unsigned int)skb->end;
+ __entry->truesize = skb->truesize;
+ ),
+
+ TP_printk("stream_id=%llu,vlan_tag=0x%04x,data_size=%zd,data_len=%zd,headlen=%u,proto=0x%04x (0x%04x),tx_idx=%d,mac_len=%u,hdr_len=%u,vlan_tci=0x%02x,mac_header=0x%02x,tail=%u,end=%u,truesize=%u",
+ __entry->stream_id,
+ __entry->vlan_tag,
+ __entry->bytes,
+ __entry->data_len,
+ __entry->headlen,
+ ntohs(__entry->protocol),
+ ntohs(__entry->prot_native),
+ __entry->tx_idx,
+ __entry->mac_len,
+ __entry->hdr_len,
+ __entry->vlan_tci,
+ __entry->mac_header,
+ __entry->tail,
+ __entry->end,
+ __entry->truesize)
+ );
+
+TRACE_EVENT(tsn_post_tx_set,
+
+ TP_PROTO(struct tsn_link *link, size_t sent),
+
+ TP_ARGS(link, sent),
+
+ TP_STRUCT__entry(
+ __field(u64, stream_id)
+ __field(size_t, sent)
+ ),
+
+ TP_fast_assign(
+ __entry->stream_id=link->stream_id;
+ __entry->sent = sent;
+ ),
+
+ TP_printk("stream_id=%llu,sent=%zu", __entry->stream_id, __entry->sent)
+
+ );
+
+TRACE_EVENT(tsn_update_net_time,
+
+ TP_PROTO(struct tsn_link *link),
+
+ TP_ARGS(link),
+
+ TP_STRUCT__entry(
+ __field(u64, stream_id)
+ __field(u64, ts_ns)
+ __field(u64, delta_ns)
+ __field(u64, delta_avg_ns)
+ __field(u64, sent_total)
+ ),
+
+ TP_fast_assign(
+ __entry->stream_id = link->stream_id;
+ __entry->ts_ns = link->ts_net_ns;
+ __entry->delta_ns = link->ts_delta_ns;
+ __entry->delta_avg_ns = link->ts_exp_avg;
+ __entry->sent_total = link->frames_sent;
+ ),
+
+ TP_printk("stream_id=%llu,ts=%llu,ts_delta=%llu,ts_delta_avg=%llu,sent_total=%llu",
+ __entry->stream_id,
+ __entry->ts_ns,
+ __entry->delta_ns,
+ __entry->delta_avg_ns,
+ __entry->sent_total)
+ );
+
+#endif /* _TRACE_TSN_H || TRACE_HEADER_MULTI_READ */
+
+#include <trace/define_trace.h>
--
2.7.4
From: Henrik Austad <[email protected]>
Describe the overall design behind the TSN standard, the TSN-driver,
requirements to userspace and new functionality introduced.
Cc: "David S. Miller" <[email protected]>
Signed-off-by: Henrik Austad <[email protected]>
---
Documentation/TSN/tsn.txt | 345 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 345 insertions(+)
create mode 100644 Documentation/TSN/tsn.txt
diff --git a/Documentation/TSN/tsn.txt b/Documentation/TSN/tsn.txt
new file mode 100644
index 0000000..540246f
--- /dev/null
+++ b/Documentation/TSN/tsn.txt
@@ -0,0 +1,345 @@
+ Time Sensitive Networking (TSN)
+ -------------------------------
+
+[work in progress]
+
+1. Motivation
+=============
+
+TSN is a set of open standards, formerly known as 'AVB' (Audio/Video
+Bridging). It was renamed to TSN to better reflect that it can do much
+more than just media transport and extended to handle more types of
+traffic.
+
+TSN is a way to create reliable, deterministic streams across a network
+without loss of frames due to congestion in the network. By using gPTP
+(a specialized IEEE-1588v2 PTP profile), the time can be synchronized
+with sub-us granularity across all the connected devices in the AVB
+domain.
+
+In its current version, this driver only supports L2 traffic (i.e
+etherframes only), but later version is planned to handle L3. L2-L3
+traversing is currently being worked on by the IETF detnet working
+group.
+
+2. Intro to AVB/TSN
+===================
+
+The original standards were written with Audio/Video in mind, so the
+initial standards refer to this as 'AVB'. In later standards, this has
+changed to TSN, and AVB now refers to a service you can add on top of
+TSN. In some parts of the driver, this naming shines through, in
+particular for AVTP (AVB Transport Protocol), and this is to reflect the
+naming in the standards.
+
+In this document, we refer to the infrastructure part as TSN, and AVB to
+the ALSA/V4L2 shim which can be added on top of TSN to provide a
+media-service.
+
+TSN operates with 'streams', and one stream can contain pretty much
+whatever you like. An AVB stream carrying audio can carry multiple
+channels. The current revision of AVTP (defined in IEEE 1722 d16)
+defines many more types than media.
+
+A stream flows through the network from a Talker to a Listener. A Talker
+is a single End-station in the network, a Listener can be a single
+End-station (unicast) or a group of end-stdations (multicast).
+
+2.1 Domains
+
+2.1.1 SRP Domain
+
+An SRP domain is the set of entities in the network that support the
+Stream Reservation Protocol (IEEE 802.1Q-2014 Sec 35) and where all
+entities agree on the priority code points (PCP). A bridge will mark
+each port as either SRP capable or not capable.
+
+PCP is used to map a specific priority to a given traffic-class,
+typically class A or B.
+
+2.1.2 gPTP domain
+
+This is the set of all connected bridges and end-stations that support
+the gPTP protocol. gPTO is a PTPv2 profile.
+
+2.1.3 AVB Domain
+
+An AVB domain is the intersection of an SRP Domain and gPTP domain.
+
+
+2.2 End Station (ES)
+
+An TSN ES is where a stream either originates or ends -what others would
+call sources (Talkers) and sinks (Listeners). Looking back at pre-TSN
+when this was called AVB, these names make a bit more sense.
+
+Common for both types, they need to be PTPv2 capable, i.e. you need to
+timestamp gPTP frames upon ingress/egress to improve the accuracy of
+PTP.
+
+2.2.1 Talkers
+
+A Talker must be single ES in the AVB Domain.
+
+Hardware requirements:
+- Multiple Tx-queues
+- Credit based shaper on at least one of the queues for pacing the
+ frames onto the network
+- VLAN capable
+
+2.2.2 Listener
+
+A Listener does not have the same requirements as a Talker as it cannot
+control the pace of the incoming frames anyway. It is beneficial if the
+NIC understands VLANs and has a few Rx-queues so that you can steer all
+TSN-frames to a dedicated queue, but this is not a hard requirement.
+
+If the Listener receives audio, having an adjustable PL/L is a clear
+benefit to avoid resampling.
+
+2.3 Bridges
+
+A Bridge is what TSN calls switches that are TSN-capable. They must be
+able to prioritize TSN-streams, have the credit-based shaper available
+for that class, support SRP, support gPTP and so on. The requirements is
+laid down in "Forwardin and Queueing of Time Sensitive Streams" (IEEE
+802.1Q-2014 sec. 34).
+
+2.4 Relevant standards
+
+* IEEE 802.1BA-2011 Audio Video Bridging (AVB) Systems
+
+* IEEE 802.1Q-2014 sec 34 and 35
+
+ What is referred to as:
+ IEEE 802.1Qav (Forwarding and Queueing for Time-sensitive Streams)
+ IEEE 802.1Qat (Stream Registration protocol)
+
+* IEEE 802.1AS gPTP
+
+ A PTPv2 profile (from IEEE 1588) tailored for this domain. Notable
+ changes include the requirement that all nodes in the network must be
+ gPTP capable (i.e. no traversing non-PTP entities), and it allows
+ traffic over a wider range of medium that what "pure" PTPv2 allows.
+
+* IEEE 1722 AVTP Layer 2 Transport Protocol for Time-Sensitive
+ Applications in Bridged Local Area Networks
+ Further improvements in IEEE 1722.d16-2015.
+
+* IEEE 1722.1 Device Discovery, Connection Management and Control for 1722
+
+ What allows AVB (TSN) devices to handle discovery, enumeration and
+ control, basically let you connect 2 devices from a 3rd
+
+ In this (in the scope of the Linux kernel TSN driver) must be done
+ purely from userspace as we do not want the kernel to suddenly attach
+ to a remote system without the user's knowledge. This is further
+ reflected in how the attributes for the link is managed via ConfigFS.
+
+
+3. Overview and/or design of the TSN-driver
+===========================================
+
+The driver handles the shifting of data for TSN-streams. Anything else
+is left for userspace to handle. This includes stream reservation (using
+some sort of MSRP client), negotiating multicast addresses, finding the
+value of the different attributes and connect application(s) to the
+exposed devices (currently we only have an ALSA-device via the AVB ALSA
+Shim).
+
+Note: the kernel-driver for TSN is tsn (lowercase). When we refer to the
+_standard_ TSN, we use uppercase. Hopefully this won't be too confusing.
+
+ /--------------------\
+ | |
+ | Media application |
+ | |
+ \--------------------/
+ | |
+ +----------+ +----+
+ | |
+ | |
+ +------------+ |
+ | ALSA | |
+ +------------+ |
+ | |
+ | |
+ +------------+ +--------------+
+ | avb_alsa | | tsn_configfs |
+ | (tsn-shim) | +--------------+
+ +------------+ |
+ | |
+ | |
+ +------+ |
+ | |
+ | |
+ +------------+ |
+ | tsn_core |<--------+
+ +------------+
+ |
+ |
+ +------------+
+ | tsn_net |
+ +------------+
+ |
+ |
+ +------------+
+ | network |
+ | subsystem |
+ +------------+
+ |
+ |
+ ...
+
+
+3.1 Terms and concepts
+
+tsn uses the concept of streams and shims.
+
+- A shim is a thin wrapper that binds TSN to another subsystem (or
+ directly to userspace). avb_alsa is an example of such a shim.
+
+- A stream is the only data TSN cares about. What the data inside the
+ stream represents, is left for the associated shim to handle. TSN will
+ verify the headers up to the protocol specific header and then pass it
+ along to the shim.
+
+Note: currently, only the data-unit part is implemented, the control
+part, in which 1722.1 (discovery and enumeration) is part, is not
+handled.
+
+3.2 Userspace requirements
+
+(msrp-client, "tsnctl"-tool
+
+4. ConfigFS overview
+=====================================
+
+4.1 Global attributes
+
+4.1.1 available_shims
+
+Read-only, lists all shims that has been loaded. When no shims has been
+loaded, 'none' is the only available.
+
+e.g.
+ /config/tsn# cat available_shims
+ none
+ alsa
+
+
+4.1.2 stream_ids
+
+Read-only, list of all StreamIDs used by the links. This is for _all_
+links on all NICs. Even if 2 NICs are on separate networks and therefore
+in theory have identical stream_id, we do a global approach.
+
+
+4.2 Per NIC attributes
+
+4.2.1 pcp_a
+
+Priority Code Point, class A. This must be the same for all streams in
+a AVB-domain, and a NIC can only partake in one.
+
+4.2.2 pcp_b
+
+Priority Code Point, class B
+
+4.2.3 list of NICs
+
+Each TSN-capable NIC is listed as a directory, inside each NIC it is
+possible to instantiate a new link using mkdir.
+
+4.3 Per stream attributes
+
+The value of the per-stream attributes need to be coordinated with the
+network. Many of these can/must be found using MSRP during link
+negotiation. The TSN driver expects userspace to do all the reservation
+so that when the link is set to active, the network will accept the
+outgoing frames.
+
+4.3.1 buffer_size
+
+Size (in kB) of the buffer the shim will use to store data before
+packing it into frames and sending (or storing incoming data in).
+
+4.3.2 class
+
+TSN splits traffic in 2 class, class A and B, 'A' or 'B' will signal
+which class this link belongs to.
+
+4.3.3 enabled
+
+'on' or 'off', indicating that the link is currently not pushing data or
+that it is fully configured and is pushing data over the network.
+
+Only valid options are 'on' and 'off' (lowercase only).
+
+4.3.4 end_station
+
+'Talker' or 'Listener'. A link cannot be both. It either sends (Talker)
+or receives (Listener) data.
+
+4.3.5 local_mac
+
+MAC address that receives data for this link. Currently this is the MAC
+for the NIC, in theory it could be a multicast address (not supported
+right now).
+
+4.3.6 max_payload_size
+
+Upper bound on how much data will be pushed in each frame. A shim can
+send _less_ than this, but never more. This is due to bandwidth
+reservation constraints.
+
+4.3.7 remote_mac
+
+Destination for L2-traffic.
+
+4.3.8 shim
+
+Shim registered to this link. 'None' means no shim, and the link cannot
+be set to active. Valid shims are listed in the global
+'available_shims' (see 4.1.1).
+
+4.3.9 shim_header_size
+
+A shim will most likely need some header to go along with the data. This
+indicates to tsn how large the header is so that frame-size constraints
+can be respected.
+
+4.3.10 stream_id
+
+StreamID for the stream this link runs. Must be globally unique on the
+host, see 4.1.2.
+
+4.3.11 vlan_id
+
+TSN does not really care about the VLAN ID, but the network may care a
+whole lot. This sets the VLAN ID to use for the traffic belonging to
+this link.
+
+
+5. Creating a new TSN Link from userspace
+=====================================
+
+A link is created in multiple steps.
+
+1. Create space for the link:
+ mkdir /config/tsn/eth0/link
+
+2. Sett values for the attributes for the link
+
+
+6. Creating a new shim
+======================
+
+shim_ops
+[coming]
+
+
+7. Other resources:
+===================
+
+https://en.wikipedia.org/wiki/Audio_Video_Bridging
--
2.7.4
From: Henrik Austad <[email protected]>
Not sure how relevant this is other than making a point about
maintaining it.
Signed-off-by: Henrik Austad <[email protected]>
---
MAINTAINERS | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 63cefa6..7c5afd2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12295,6 +12295,20 @@ T: git git://linuxtv.org/anttip/media_tree.git
S: Maintained
F: drivers/media/tuners/tua9001*
+TSN CORE DRIVER
+M: Henrik Austad <[email protected]>
+L: [email protected]
+S: Supported
+F: drivers/net/tsn/
+F: include/linux/tsn.h
+F: include/trace/events/tsn.h
+
+TSN_AVB_DRIVER
+M: Henrik Austad <[email protected]>
+L: [email protected] (moderated for non-subscribers)
+S: Supported
+F: drivers/media/avb/
+
TULIP NETWORK DRIVERS
L: [email protected]
L: [email protected]
--
2.7.4
From: Henrik Austad <[email protected]>
This defines the general TSN headers for network packets, the
shim-interface and the central 'tsn_list' structure.
Cc: "David S. Miller" <[email protected]>
Signed-off-by: Henrik Austad <[email protected]>
---
include/linux/tsn.h | 952 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 952 insertions(+)
create mode 100644 include/linux/tsn.h
diff --git a/include/linux/tsn.h b/include/linux/tsn.h
new file mode 100644
index 0000000..9123b25
--- /dev/null
+++ b/include/linux/tsn.h
@@ -0,0 +1,952 @@
+/* TSN - Time Sensitive Networking
+ *
+ * Copyright (C) 2016- Henrik Austad <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef _TSN_H
+#define _TSN_H
+#include <linux/list.h>
+#include <linux/kthread.h>
+#include <linux/configfs.h>
+#include <linux/hrtimer.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/spinlock.h>
+
+/*
+ * List of current subtype fields in the common header of AVTPDU
+ *
+ * Note: AVTPDU is a remnant of the standards from when it was AVB.
+ *
+ * The list has been updated with the recent values from IEEE 1722, draft 16.
+ */
+enum avtp_subtype {
+ TSN_61883_IIDC = 0, /* IEC 61883/IIDC Format */
+ TSN_MMA_STREAM, /* MMA Streams */
+ TSN_AAF, /* AVTP Audio Format */
+ TSN_CVF, /* Compressed Video Format */
+ TSN_CRF, /* Clock Reference Format */
+ TSN_TSCF, /* Time-Synchronous Control Format */
+ TSN_SVF, /* SDI Video Format */
+ TSN_RVF, /* Raw Video Format */
+ /* 0x08 - 0x6D reserved */
+ TSN_AEF_CONTINOUS = 0x6e, /* AES Encrypted Format Continous */
+ TSN_VSF_STREAM, /* Vendor Specific Format Stream */
+ /* 0x70 - 0x7e reserved */
+ TSN_EF_STREAM = 0x7f, /* Experimental Format Stream */
+ /* 0x80 - 0x81 reserved */
+ TSN_NTSCF = 0x82, /* Non Time-Synchronous Control Format */
+ /* 0x83 - 0xed reserved */
+ TSN_ESCF = 0xec, /* ECC Signed Control Format */
+ TSN_EECF, /* ECC Encrypted Control Format */
+ TSN_AEF_DISCRETE, /* AES Encrypted Format Discrete */
+ /* 0xef - 0xf9 reserved */
+ TSN_ADP = 0xfa, /* AVDECC Discovery Protocol */
+ TSN_AECP, /* AVDECC Enumeration and Control Protocol */
+ TSN_ACMP, /* AVDECC Connection Management Protocol */
+ /* 0xfd reserved */
+ TSN_MAAP = 0xfe, /* MAAP Protocol */
+ TSN_EF_CONTROL, /* Experimental Format Control */
+};
+
+/* Link-states to help error-recovery detected from irq context.
+ */
+enum link_states {
+ LINK_OFF = 0,
+ LINK_RUNNING,
+ LINK_ERROR,
+};
+
+
+/* NOTE NOTE NOTE !!
+ * The headers below use bitfields extensively and verifications
+ * are needed when using little-endian vs big-endian systems.
+ */
+
+/* Common part of avtph header
+ *
+ * AVB Transport Protocol Common Header
+ *
+ * Defined in 1722-2011 Sec. 5.2
+ */
+struct avtp_ch {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ /* use avtp_subtype enum.
+ */
+ u8 subtype:7;
+
+ /* Controlframe: 1
+ * Dataframe : 0
+ */
+ u8 cd:1;
+
+ /* Type specific data, part 1 */
+ u8 tsd_1:4;
+
+ /* In current version of AVB, only 0 is valid, all other values
+ * are reserved for future versions.
+ */
+ u8 version:3;
+
+ /* Valid StreamID in frame
+ *
+ * ControlData not related to a specific stream should clear
+ * this (and have stream_id = 0), _all_ other values should set
+ * this to 1.
+ */
+ u8 sv:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 cd:1;
+ u8 subtype:7;
+ u8 sv:1;
+ u8 version:3;
+ u8 tsd_1:4;
+#else
+#error "Unknown Endianness, cannot determine bitfield ordering"
+#endif
+ /* Type specific data (adjacent to tsd_1, but split due to bitfield) */
+ u16 tsd_2;
+ u64 stream_id;
+
+ /*
+ * payload by subtype
+ */
+ u8 pbs[0];
+} __packed;
+
+/* AVTPDU Common Control header format
+ * IEEE 1722#5.3
+ */
+struct avtpc_header {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 subtype:7;
+ u8 cd:1;
+ u8 control_data:4;
+ u8 version:3;
+ u8 sv:1;
+ u16 control_data_length:11;
+ u16 status:5;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 cd:1;
+ u8 subtype:7;
+ u8 sv:1;
+ u8 version:3;
+ u8 control_data:4;
+ u16 status:5;
+ u16 control_data_length:11;
+#else
+#error "Unknown Endianness, cannot determine bitfield ordering"
+#endif
+ u64 stream_id;
+} __packed;
+
+/* AVTP common stream data AVTPDU header format
+ * IEEE 1722#5.4
+ */
+struct avtpdu_header {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 subtype:7;
+ u8 cd:1;
+
+ /* avtp_timestamp valid */
+ u8 tv: 1;
+
+ /* gateway_info valid */
+ u8 gv:1;
+
+ /* reserved */
+ u8 r:1;
+
+ /*
+ * Media clock Restart toggle
+ */
+ u8 mr:1;
+
+ u8 version:3;
+
+ /* StreamID valid */
+ u8 sv:1;
+ u8 seqnr;
+
+ /* Timestamp uncertain */
+ u8 tu:1;
+ u8 r2:7;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 cd:1;
+ u8 subtype:7;
+
+ u8 sv:1;
+ u8 version:3;
+ u8 mr:1;
+ u8 r:1;
+ u8 gv:1;
+ u8 tv: 1;
+
+ u8 seqnr;
+ u8 r2:7;
+ u8 tu:1;
+#else
+#error "Unknown Endianness, cannot determine bitfield ordering"
+#endif
+
+ u64 stream_id;
+
+ u32 avtp_timestamp;
+ u32 gateway_info;
+
+ /* Stream Data Length */
+ u16 sd_len;
+
+ /* Protocol specific header, derived from avtp_subtype */
+ u16 psh;
+
+ /* Stream Payload Data 0 to n octets
+ * n so that total size < MTU
+ */
+ u8 data[0];
+} __packed;
+
+
+/**
+ * struct tsn_list - The top level container of TSN
+ *
+ * This is what tsn_configfs refers to as 'tier-0'
+ *
+ * @head List of TSN cards
+ * @lock lock protecting global entries
+ * @tsn_subsys Ref to ConfigFS subsystem
+ *
+ * @running: hrtimer is running driving data out
+ * @tsn_timer: hrtimer container
+ * @num_avail Number of available TSN NICs exposed through ConfigFS
+ */
+struct tsn_list {
+ struct list_head head;
+ spinlock_t lock;
+ struct configfs_subsystem tsn_subsys;
+
+ /*
+ * TSN-timer is running. Not to be confused with the per-link
+ * disabled flag which indicates if a remote client, like aplay,
+ * is pushing data to it.
+ */
+ atomic_t running;
+ struct hrtimer tsn_timer;
+ unsigned int period_ns;
+
+ struct task_struct *tsn_thread;
+ int should_run;
+
+ size_t num_avail;
+};
+
+static inline void tsn_list_lock(struct tsn_list *list)
+{
+ spin_lock(&list->lock);
+}
+static inline void tsn_list_unlock(struct tsn_list *list)
+{
+ spin_unlock(&list->lock);
+}
+
+/**
+ * struct tsn_nic
+ *
+ * Individual TSN-capable NICs, or 'tier-1' struct
+ *
+ * @list linked list of all TSN NICs
+ * @group configfs group
+ * @dev corresponding net_device
+ * @dma_size : size of the DMA buffer
+ * @dma_handle: housekeeping DMA-stuff
+ * @dma_mem : pointer to memory region we're using for DMAing to the NIC
+ * @name Name of NIC (same as name in dev), TO BE REMOVED
+ * @txq Size of Tx-queue. TO BE REMOVED
+ * @rx_registered flag indicating if a handler is registered for the nic
+ * @capable: if the NIC is capable for proper TSN traffic or if it must
+ * be emulated in software.
+ *
+ */
+struct tsn_nic {
+ struct list_head list;
+ struct config_group group;
+ struct net_device *dev;
+ struct tsn_list *tsn_list;
+
+ size_t dma_size;
+ dma_addr_t dma_handle;
+ void *dma_mem;
+
+ char *name;
+ int txq;
+ u8 rx_registered:1;
+ u8 capable:1;
+
+ /*
+ * Any AVTP data stream must set the 802.1Q vlan id and priority
+ * Code point. This should be obtained from MSRP, default values
+ * are:
+ *
+ * pcp: Class A: 3
+ * Class B: 2
+ *
+ * See IEEE 802.1Q-2011, Sec 35.2.2.9.3 and table 6-6 in 6.6.2
+ * for details.
+ */
+ u8 pcp_a:3;
+ u8 pcp_b:3;
+};
+
+struct tsn_shim_ops;
+/**
+ * tsn_link - Structure describing a single TSN link
+ *
+ */
+struct tsn_link {
+ /* Locks for protecting the link
+ *
+ * Due to how we do Rx and Tx, we need different types of locks
+ * in these settings. A link _cannot_ be both, so even though
+ * this way of doing it is ugly, it should be safe.
+ *
+ * Reader: must disable interrupt as we take the lock in rx-handler (Network bh)
+ * Talker: must not disable interrupt
+ */
+ spinlock_t tlock;
+ raw_spinlock_t llock;
+ unsigned long lflags;
+
+ struct config_group group;
+ struct tsn_nic *nic;
+ struct hlist_node node;
+
+ /* The link itself is active, and the tsn_core will treat it as
+ * an active participant and feed data from it to the
+ * network. This places some restrictions on what attributes
+ * (most actually) that can be changed.
+ *
+ */
+ atomic_t link_state;
+
+ /* keep track of how many frames we have sent (for debugging) */
+ u64 frames_sent;
+
+ /* timestamp for last frame going in/out over the network, delta
+ * and avg delta
+ */
+ u64 ts_net_ns;
+ u64 ts_delta_ns;
+
+ /* simple, exponential smoothing of the time between received
+ * samples. This is useful for shims that need to calculate an
+ * offset into the buffer of received data.
+ * exp_avg = alpha * ts_delta_ns + (1-alpha)exp_avg_{-1}
+ * avg_delta_ns = ts_delta_ns * alpha_scale + avg_delta_ns * (1 - alpha_scale)
+ */
+ u64 ts_exp_avg;
+
+ /* alpha_scale is currently in the range 0 - (2^14 - 1) because
+ * 16384 different values is "probably enough" for a smoothed
+ * avg.
+ */
+ u16 ts_exp_alpha;
+
+ /* Pointer to media-specific data.
+ * e.g. struct avb_chip
+ */
+ void *media_chip;
+
+ u64 stream_id;
+
+ /*
+ * The max required size for a _single_ TSN frame.
+ *
+ * To be used instead of channels and sample_freq.
+ */
+ u16 max_payload_size;
+ u16 shim_header_size;
+
+ /*
+ * Size of buffer (in bytes) to use when handling data to/from
+ * NIC.
+ *
+ * Smaller size will result in client being called more often
+ * but also provides lower latencies.
+ */
+ size_t buffer_size;
+ size_t used_buffer_size;
+
+ /* used to keep skb when we overproduce so that we can do a
+ * somewhat sane backoff.
+ */
+ struct sk_buff *old_skb;
+
+ /*
+ * Used when frames are constructed and shipped to the network
+ * layer. If this is true, 0-frames will be sent insted of data
+ * from the buffer.
+ */
+ atomic_t buffer_active;
+
+ /*
+ * ringbuffer for incoming or outging traffic
+ * +-----------------------------------+
+ * | ########## |
+ * +-----------------------------------+
+ * ^ ^ ^ ^
+ * buffer tail head end
+ *
+ * Buffer: start of memory area
+ * tail: first byte of data in buffer
+ * head: first unused slot in which to store new data
+ *
+ * head,tail is used to represent the position of 'live data' in
+ * the buffer.
+ */
+ void *buffer;
+ void *head;
+ void *tail;
+ void *end;
+
+ /* Number of bytes to run refill/drain callbacks */
+ size_t low_water_mark;
+ size_t high_water_mark;
+
+
+ /*
+ * callback ops.
+ */
+ struct tsn_shim_ops *ops;
+
+ /*
+ * EndStation Type
+ *
+ * Either Talker or Listener
+ *
+ * 1: We are *Talker*, i.e. producing data to send
+ * 0: We are *Listener*, i.e. we receive data from another ES.
+ *
+ * This is for a single link, so even though an end-station can
+ * be both Talker *and* Listener, a link can only be one.
+ */
+ u8 estype_talker;
+
+ /*
+ * Link will use buffer managed by the shim. For this to work,
+ * the shim must:
+ *
+ * - call tsn_use_external_buffer(link, size);
+ * - provide tsn_shim_buffer_swap(link) in tsn_shim_ops
+ */
+ u8 external_buffer;
+
+ u8 last_seqnr;
+
+ /*
+ * Class can be of different classes, currently A or B
+ *
+ * ClassA: every 125us
+ * ClassB: every 250us
+ *
+ * This will also affect how large each frame will be and will
+ * also grab the PCP from the NIC-struct
+ */
+ enum sr_class class;
+
+ u16 vlan_id;
+
+ u8 remote_mac[6];
+};
+
+static inline void tsn_lock(struct tsn_link *link)
+{
+ if (link->estype_talker)
+ spin_lock(&link->tlock);
+ else
+ raw_spin_lock_irqsave(&link->llock, link->lflags);
+}
+
+static inline void tsn_unlock(struct tsn_link *link)
+{
+ if (link->estype_talker)
+ spin_unlock(&link->tlock);
+ else
+ raw_spin_unlock_irqrestore(&link->llock, link->lflags);
+}
+
+void tsn_lock_init(struct tsn_link *link);
+
+/**
+ * tsn_link_on - make link active
+ *
+ * This cause most of the attributes to be treated read-only since we
+ * will have to re-negotiate with the network if most of these
+ * parameters change.
+ *
+ * Note: this means that the link will be handled by the rx-handler or
+ * the timer callback, but until the link_buffer is set active (via
+ * tsn_lb_on()), actual data is not moved.
+ *
+ * @link: link being set to active
+ */
+static inline void tsn_link_on(struct tsn_link *link)
+{
+ if (link)
+ atomic_set(&link->link_state, LINK_RUNNING);
+}
+
+/**
+ * tsn_link_off - make link inactive
+ *
+ * The link will now be ignored by timer callback or the
+ * rx-handler. Attributes can be mostly freely changed (we assume that
+ * userspace sets values that are negotiated properly).
+ *
+ * @link: link to deactivate
+ */
+static inline void tsn_link_off(struct tsn_link *link)
+{
+ if (link)
+ atomic_set(&link->link_state, LINK_OFF);
+}
+
+static inline int tsn_link_is_off(struct tsn_link *link)
+{
+ if (link)
+ return atomic_read(&link->link_state) == LINK_OFF;
+ return 0;
+}
+/**
+ * tsn_link_err - mark link as having an error.
+ *
+ * Link will be ignored by timer callback after being marked as in error
+ * and will be torn down upon next non-irq handling of link. After being
+ * torn down, it will be marked as 'off'.
+ *
+ *@link: link in error
+ */
+static inline void tsn_link_err(struct tsn_link *link)
+{
+ if (link)
+ atomic_set(&link->link_state, LINK_ERROR);
+}
+static inline int tsn_link_is_err(struct tsn_link *link)
+{
+ if (link)
+ return atomic_read(&link->link_state) == LINK_ERROR;
+ return 0;
+}
+/**
+ * tsn_link_is_on - query link to see if it is active
+ *
+ * Mostly used by tsn_configfs to respect the "read-only" once link is
+ * configured and made active.
+ *
+ * @link active link
+ * @returns 1 if active/on, 0 otherwise
+ */
+static inline int tsn_link_is_on(struct tsn_link *link)
+{
+ if (link)
+ return atomic_read(&link->link_state) == LINK_RUNNING;
+ return 0;
+}
+
+/**
+ * tsn_set_buffer_size - adjust buffersize to match a shim
+ *
+ * This will not allocate (or deallcoate) memory, just adjust how much
+ * of the buffer allocated in tsn_prepare_link is being used. tsn_
+ * expects tsn_clear_buffer_size() to be invoked when stream is closed.
+ */
+int tsn_set_buffer_size(struct tsn_link *link, size_t bsize);
+int tsn_clear_buffer_size(struct tsn_link *link);
+
+/**
+ * tsn_buffer_write write data into the buffer from shim
+ *
+ * This is called from the shim-driver when more data is available and
+ * data needs to be pushed out to the network.
+ *
+ * NOTE: This is used when TSN handles the databuffer. This will not be
+ * needed for "shim-hosted" buffers.
+ *
+ * _If_ this function is called when the link is inactive, it will
+ * _enable_ the link (i.e. link will mark the buffer as 'active'). Do
+ * not copy data into the buffer unless you are ready to start sending
+ * frames!
+ *
+ * @link active link
+ * @src the buffer to copy data from
+ * @bytes bytes to copy
+ * @return bytes copied from link->buffer or negative error
+ */
+int tsn_buffer_write(struct tsn_link *link, void *src, size_t bytes);
+
+
+/**
+ * tsn_buffer_read - read data from link->buffer and give to shim
+ *
+ * When we act as a listener, this is what the shim (should|will) call
+ * to grab data. It typically grabs much more data than the _net
+ * equivalent. It also do not trigger a refill-event the same way
+ * buffer_read_net does.
+ *
+ * @param link current link that holds the buffer
+ * @param buffer the buffer to copy into, must be at least of size bytes
+ * @param bytes number of bytes.
+ *
+ * Note that this routine does NOT CARE about channels, samplesize etc,
+ * it is a _pure_ copy that handles ringbuffer wraps etc.
+ *
+ * This function have side-effects as it will update internal tsn_link
+ * values.
+ *
+ * @return Bytes copied into link->buffer, negative value upon error.
+ */
+int tsn_buffer_read(struct tsn_link *link, void *buffer, size_t bytes);
+
+/**
+ * tsn_lb_enable - TSN Link Buffer Enable
+ *
+ * Mark the link as "buffer-enabled" which will let the core start
+ * shifting data in/out of the buffer instead of ignoring incoming
+ * frames or sending "nullframes".
+ *
+ * This is for the network-end of the tsn-buffer, i.e.
+ * - when enabled frames *from* the network will be inserted into the buffer,
+ * - or frames going *out* will include data from the buffer instead of sending
+ * null-frames.
+ *
+ * When disabled, data will be zero'd, e.g Tx will send NULL-frames and
+ * Rx will silently drop the frames.
+ *
+ * @link: active link
+ */
+static inline void tsn_lb_enable(struct tsn_link *link)
+{
+ if (link)
+ atomic_set(&link->buffer_active, 1);
+}
+
+/**
+ * tsn_lb_disable - stop using the buffer for the net-side of TSN
+ *
+ * When we close a stream, we do not necessarily tear down the link, and
+ * we need to handle the data in some way.
+ */
+static inline void tsn_lb_disable(struct tsn_link *link)
+{
+ if (link)
+ atomic_set(&link->buffer_active, 0);
+}
+
+/**
+ * tsn_lb() - query if we have disabled pushing of data to/from link-buffer
+ *
+ * @param struct tsn_link *link - active link
+ * @returns 1 if link is enabled
+ */
+static inline int tsn_lb(struct tsn_link *link)
+{
+ if (link)
+ return atomic_read(&link->buffer_active);
+
+ /* if link is NULL; buffer not active */
+ return 0;
+}
+
+/**
+ * tsn_update_net_time - update timestamp for latest activity on the net-side
+ *
+ * This function expects link to be locked, i.e. no-one else is updating
+ * the fields. Typically called from tsn_rx_handler (which grabs tsn_link()).
+ *
+ * @param link active link
+ * @param tim_ns timestamp
+ * @param increment the number if times buffer has been updated
+ *
+ * @returns 0 on success, negative on error.
+ */
+int tsn_update_net_time(struct tsn_link *link, u64 time_ns, int increment);
+
+/**
+ * Shim ops - what tsn_core use when calling back into the shim. All ops
+ * must be reentrant.
+ */
+#define SHIM_NAME_SIZE 32
+struct tsn_shim_ops {
+
+ /* internal linked list used by tsn_core to keep track of all
+ * shims.
+ */
+ struct list_head head;
+
+ /**
+ * name - a unique name identifying this shim
+ *
+ * This is what userspace use to indicate to core what SHIM a
+ * particular link will use. If the name is already present,
+ * core will reject this name.
+ */
+ char shim_name[SHIM_NAME_SIZE];
+
+ /**
+ * probe - callback when a new link of this type is instantiated.
+ *
+ * When a new link is brought online, this is called once the
+ * essential parts of tsn_core has finiesh. Once probe_cb has
+ * finisehd, the shim _must_ be ready to accept data to/from
+ * tsn_core. On the other hand, due to the final steps of setup,
+ * it cannot expect to be called into action immediately after
+ * probe has finished.
+ *
+ * In other words, shim must be ready, but core doesn't have to
+ *
+ * @param : a particular link to pass along to the probe-function.
+ */
+ int (*probe)(struct tsn_link *link);
+
+ /**
+ * buffer_swap - set a new buffer for the link. [OPTIONAL]
+ *
+ * Used when external buffering is enabled.
+ *
+ * When called, a new buffer must be returned WITHOUT blocking
+ * as this will be called from interrupt context.
+ *
+ * The buffer returned from the shim must be at least the size
+ * of used_buffer_size.
+ *
+ * @param current link
+ * @param old_buffer the buffer that are no longer needed
+ * @param used number of bytes in buffer that has been filled with data.
+ * @return new buffer to use
+ */
+ void * (*buffer_swap)(struct tsn_link *link, void *old_buffer,
+ size_t used);
+
+ /**
+ * buffer_refill - signal shim that more data is required
+ * @link Active link
+ *
+ * This function should not do anything that can preempt the
+ * task (kmalloc, sleeping lock) or invoke actions that can take
+ * a long time to complete.
+ *
+ * This will be called from tsn_buffer_read_net() when available
+ * data in the buffer drops below low_water_mark. It will be
+ * called with the link-lock *held*
+ */
+ size_t (*buffer_refill)(struct tsn_link *link);
+
+ /**
+ * buffer_drain - shim need to copy data from buffer
+ *
+ * This will be called from tsn_buffer_write_net() when data in
+ * the buffer exceeds high_water_mark.
+ *
+ * The expected behavior is for the shim to then fill data into
+ * the buffer via tsn_buffer_write()
+ */
+ size_t (*buffer_drain)(struct tsn_link *link);
+
+ /**
+ * media_close - shut down media controller properly
+ *
+ * when the link is closed/removed for some reason
+ * external to the media controller (ALSA soundcard, v4l2 driver
+ * etc), we call this to clean up.
+ *
+ * Normal operation is stopped before media_close is called, but
+ * all references should be valid. TSN core expects media_close
+ * to handle any local cleanup, once returned, any references in
+ * stale tsn_links cannot be trusted.
+ *
+ * @link: current link where data is stored
+ * @returns: 0 upon success, negative on error.
+ */
+ int (*media_close)(struct tsn_link *link);
+
+ /**
+ * hdr_size - ask shim how large the header is
+ *
+ * Needed when reserving space in skb for transmitting data.
+ *
+ * @link: current link where data is stored
+ * @return: size of header for this shim
+ */
+ size_t (*hdr_size)(struct tsn_link *link);
+
+ /**
+ * copy_size - ask client how much from the buffer to include in
+ * the next frame.
+ *
+ * This is for *outgoing* frames, incoming frames
+ * have 'sd_len' set in the header.
+ *
+ * Note: copy_size should not return a size larger
+ * than link->max_payload_size
+ */
+ size_t (*copy_size)(struct tsn_link *link);
+
+ /**
+ * validate_header - let the shim validate subtype-header
+ *
+ * Both psh and data may (or may not) contain headers that need
+ * validating. This is the responsibility of the shim to
+ * validate, and ops->valdiate_header() will be called before
+ * any data is copied from the incoming frame and into the
+ * buffer.
+ *
+ * Important: tsn_core expects validate_header to _not_ alter
+ * the contents of the frame, and ideally, validate_header could
+ * be called multiple times and give the same result.
+ *
+ * @param: active link owning the new data
+ * @param: start of data-unit header
+ *
+ * This function will be called from interrupt-context and MUST
+ * NOT take any locks.
+ */
+ int (*validate_header)(struct tsn_link *link,
+ struct avtpdu_header *header);
+
+ /**
+ * assemble_header - add shim-specific headers
+ *
+ * This adds the headers required by the current shim after the
+ * generic 1722-header.
+ *
+ * @param: active link
+ * @param: start of data-unit header
+ * @param: size of data to send in this frame
+ * @return void
+ */
+ void (*assemble_header)(struct tsn_link *link,
+ struct avtpdu_header *header, size_t bytes);
+
+ /**
+ * get_payload_data - get a pointer to where the data is stored
+ *
+ * core will use the pointer (or drop it if NULL is returned)
+ * and copy header->sd_len bytes of *consecutive* data from the
+ * target memory and into the buffer memory.
+ *
+ * This is called with relevant locks held, from interrupt context.
+ *
+ * @param link active link
+ * @param header header of frame, which contains data
+ * @returns pointer to memory to copy from
+ */
+ void * (*get_payload_data)(struct tsn_link *link,
+ struct avtpdu_header *header);
+};
+/**
+ * tsn_shim_register_ops - register shim-callbacks for a given shim
+ *
+ * @param shim_ops - callbacks. The ops-struct should be kept intact for
+ * as long as the driver is running.
+ *
+ *
+ */
+int tsn_shim_register_ops(struct tsn_shim_ops *shim_ops);
+
+/**
+ * tsn_shim_deregister_ops - remove callback for module
+ *
+ * Completely remove shim_ops. This will close any links currently using
+ * this shim. Note: the links will be closed, but _not_ removed.
+ *
+ * @param shim_ops ops associated with this shim
+ */
+void tsn_shim_deregister_ops(struct tsn_shim_ops *shim_ops);
+
+/**
+ * tsn_shim_get_active : return the name of the currently loaded shim
+ *
+ * @param current link
+ * @return name of shim (matches an entry from exported triggers)
+ */
+char *tsn_shim_get_active(struct tsn_link *link);
+
+/**
+ * tsn_shim_find_by_name find shim_ops by name
+ *
+ * @param name of shim
+ * @return shim or NULL if not found/error.
+ */
+struct tsn_shim_ops *tsn_shim_find_by_name(const char *name);
+
+/**
+ * tsn_shim_export_probe_triggers - export a list of registered shims
+ *
+ * @param page to write content into
+ * @returns length of data written to page
+ */
+ssize_t tsn_shim_export_probe_triggers(char *page);
+
+/**
+ * tsn_get_framesize - get the size of the next TSN frame to send
+ *
+ * This will call into the shim to get the next chunk of data to
+ * read. Some sanitychecking is performed, i.e.
+ *
+ * 0 <= size <= max_payload_size
+ *
+ * @param struct tsn_link *link active link
+ * @returns size of frame in bytes or negative on error.
+ */
+static inline size_t tsn_shim_get_framesize(struct tsn_link *link)
+{
+ size_t ret;
+
+ ret = link->ops->copy_size(link);
+ if (ret <= link->max_payload_size)
+ return ret;
+ return link->max_payload_size;
+}
+
+/**
+ * tsn_get_hdr_size - get the size of the shim-specific header size
+ *
+ * The shim will add it's own header to the frame.
+ */
+static inline size_t tsn_shim_get_hdr_size(struct tsn_link *link)
+{
+ size_t ret;
+
+ if (!link || !link->ops->hdr_size)
+ return -EINVAL;
+ ret = link->ops->hdr_size(link);
+ if (ret > link->max_payload_size)
+ return -EINVAL;
+ return ret;
+}
+
+static inline u8 sr_class_to_pcp(struct tsn_nic *nic, enum sr_class class)
+{
+ if (!nic)
+ return 0;
+ switch (class) {
+ case SR_CLASS_A:
+ return nic->pcp_a;
+ case SR_CLASS_B:
+ return nic->pcp_b;
+ /* room for future class C & D */
+ default:
+ pr_err("Unknown class in mapping %d\n", class);
+ }
+ return 0;
+}
+
+#endif /* _TSN_H */
--
2.7.4
From: Henrik Austad <[email protected]>
TSN provides a mechanism to create reliable, jitter-free, low latency
guaranteed bandwidth links over a local network. It does this by
reserving a path through the network. Support for TSN must be found in
both the NIC as well as in the network itself.
This adds required hooks into netdev_ops so that the core TSN driver can
use this when configuring a new NIC or setting up a new link. It also
provides hook for removing a link and reducing the idle_slope parameter on
the NIC.
(We need to set the PCP values when we first configure the link. This
value should not change as long as we have valid streams running, and in
most cases, the PCP for the domain will not change.)
Cc: "David S. Miller" <[email protected]>
Signed-off-by: Henrik Austad <[email protected]>
---
include/linux/netdevice.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
net/Kconfig | 1 +
net/tsn/Kconfig | 32 ++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+)
create mode 100644 net/tsn/Kconfig
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index e16a2a9..0d758aa 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -112,6 +112,15 @@ enum netdev_tx {
};
typedef enum netdev_tx netdev_tx_t;
+#if IS_ENABLED(CONFIG_TSN)
+enum sr_class {
+ SR_CLASS_A = 1,
+ SR_CLASS_B = 2,
+ SR_CLASS_LAST,
+ SR_CLASS_ERR,
+};
+#endif
+
/*
* Current order: NETDEV_TX_MASK > NET_XMIT_MASK >= 0 is significant;
* hard_start_xmit() return < NET_XMIT_MASK means skb was consumed.
@@ -944,6 +953,31 @@ struct netdev_xdp {
*
* void (*ndo_poll_controller)(struct net_device *dev);
*
+ * TSN functions (if CONFIG_TSN)
+ *
+ * int (*ndo_tsn_capable)(struct net_device *dev);
+ * If a particular device is capable of sustaining TSN traffic
+ * provided current configuration
+ *
+ * int (*ndo_tsn_link_configure)(struct net_device *dev,
+ * enum sr_class class,
+ * u16 framesize,
+ * u16 vid,
+ * u8 add_link,
+ * u8 pcp_hi,
+ * u8 pcp_lo)
+);
+ * Configure a NIC to handle TSN-streams
+ * - Update the bandwidth for the particular stream-class.
+ * - The framesize is the size of the _entire_ frame (not just the payload)
+ * since the full size is required to allocate bandwidth through
+ * the credit based shaper in the NIC
+ * - the vlan_id is the configured vlan for TSN in this session.
+ * - add_link: if the link should be added or subtracted from the current
+ * budget.
+ * - u8 pcp_hi: 802.1Q priority value for high-class traffic (class A)
+ * - u8 pcp_lo: 802.1Q priority value for low-class traffic (class B)
+ *
* SR-IOV management functions.
* int (*ndo_set_vf_mac)(struct net_device *dev, int vf, u8* mac);
* int (*ndo_set_vf_vlan)(struct net_device *dev, int vf, u16 vlan,
@@ -1185,6 +1219,16 @@ struct net_device_ops {
#ifdef CONFIG_NET_RX_BUSY_POLL
int (*ndo_busy_poll)(struct napi_struct *dev);
#endif
+
+#if IS_ENABLED(CONFIG_TSN)
+ int (*ndo_tsn_capable)(struct net_device *dev);
+ int (*ndo_tsn_link_configure)(struct net_device *dev,
+ enum sr_class class,
+ u16 framesize,
+ u16 vid, u8 add_link,
+ u8 pcp_hi, u8 pcp_lo);
+#endif /* CONFIG_TSN */
+
int (*ndo_set_vf_mac)(struct net_device *dev,
int queue, u8 *mac);
int (*ndo_set_vf_vlan)(struct net_device *dev,
diff --git a/net/Kconfig b/net/Kconfig
index 7b6cd34..19b8f9a 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -215,6 +215,7 @@ source "net/802/Kconfig"
source "net/bridge/Kconfig"
source "net/dsa/Kconfig"
source "net/8021q/Kconfig"
+source "net/tsn/Kconfig"
source "net/decnet/Kconfig"
source "net/llc/Kconfig"
source "net/ipx/Kconfig"
diff --git a/net/tsn/Kconfig b/net/tsn/Kconfig
new file mode 100644
index 0000000..1fc3c1d
--- /dev/null
+++ b/net/tsn/Kconfig
@@ -0,0 +1,32 @@
+#
+# Configuration for 802.1 Time Sensitive Networking (TSN)
+#
+
+config TSN
+ tristate "802.1 TSN Support"
+ depends on VLAN_8021Q && PTP_1588_CLOCK && CONFIGFS_FS
+ ---help---
+ Select this if you want to enable TSN on capable interfaces.
+
+ TSN allows you to set up deterministic links on your LAN (only
+ L2 is currently supported). Once loaded, the driver will probe
+ all available interfaces if they are capable of supporting TSN
+ links.
+
+ Once loaded, a directory in configfs called tsn/ will expose
+ the capable NICs and allow userspace to create
+ links. Userspace must provide us with a StreamID as well as
+ reserving bandwidth through the network and once this is done,
+ a new link can be created by issuing a mkdir() in configfs and
+ updating the attributes for the new link.
+
+ TSN itself does not produce nor consume data, it is dependent
+ upon 'shims' doing this, which can be virtually anything. ALSA
+ is a good candidate.
+
+ For more information, refer to the TSN-documentation in the
+ kernel documentation repository.
+
+ The resulting module will be called 'tsn'
+
+ If unsure, say N.
--
2.7.4
On Fri, 2016-12-16 at 18:59 +0100, [email protected] wrote:
> From: Henrik Austad <[email protected]>
>
>
> The driver is directed via ConfigFS as we need userspace to handle
> stream-reservation (MSRP), discovery and enumeration (IEEE 1722.1) and
> whatever other management is needed. This also includes running an
> appropriate PTP daemon (TSN favors gPTP).
I suggest using a generic netlink interface to communicate with the
driver to set up and/or configure your drivers.
I think configfs is frowned upon for network drivers. YMMV.
- Greg
>
> Once we have all the required attributes, we can create link using
> mkdir, and use write() to set the attributes. Once ready, specify the
> 'shim' (basically a thin wrapper between TSN and another subsystem) and
> we start pushing out frames.
>
> The network part: it ties directly into the Rx-handler for receive and
> writes skb's using dev_queue_xmit(). This could probably be improved.
>
> 2 new fields in netdev_ops have been introduced, and the Intel
> igb-driver has been updated (as this an AVB-capable NIC which is
> available as a PCI-e card).
>
> What remains (tl;dr: a lot) a.k.a "Known problems" or "working on it!"
> - tie to (g)PTP properly, currently using ktime_get() for presentation
> time
> - get time from shim into TSN
> - let shim create/manage buffer
> - redo parts of the link-stuff using RCUs, the current setup is a bit
> clumsy.
> - The igb-driver does not work properly when compiled with IGB_TSN, some
> details in setting the register values needs to be figured out. I am
> working on this, but as it stands, the best bet is to load tsn using
> in_debug=1 to bypass the capability-check. I have had e1000 and sky2
> running for several days without crashing, igb crashes and burns
> violently.
> - The ALSA driver does not handle multiple devices very well and is a
> work in progress.
>
> * v2: changes since v1
>
> Changes since v1
> - updated to latest upstream kernel (v4.8)
> - set dedicated enabled-attribute and let shim be stored in own (support
> future plan for enabling per-shim attributes)
> - fixed endianess issue in bitfields used in tsn-structs
> - Updated some of the trace-events to use trace_class
> - Fix various silly typos
> - Handle disabling of link from hrtimer a bit more gracefully (that
> actually works-ish).
> - use old skb and size of skb when that is set (Reporte by Nikita)
> - Move PCP-codes to NIC and not in the link itself
> - Allow TSN-capable card to be loaded even when in debug-mode (and do
> not enforce TSN behaviour)
> - Start hooking into ALSA's get_time_info hooks (very much incomplete)
> - use threads for sending frames, wake from hrtimer-callback.
> This also queues up awaiting timers if we fail to complete the
> transmit before another timer arrives, it will immediately execute
> another iteration, so no events should be lost. That being said,
> should this happen, it is a clear bug as we really should complete
> well before the next interval.
> - Cleanup link-locking and differentiate between Talker and Listener (as
> Listener grab link-lock from IRQ context)
> - Change list-lock to spinlock as we may need to take a link-lock whilst
> holding the master list-lock.
> - Do a more careful dance holding the spinlocks to regions only doing
> actual update.
>
> Network driver (I210 only)
> - bring up all Tx-/Rx-queues when igb is in TSN-mode regardless of how
> many CPUs the system has for I210
> - Correctly calculate the idle_slope in I210's configure hook
> - Update igb-driver with queue-select and return correct queue when
> sending TSN-frames
> - add IGB_FLAG_QAV_PRIO flag to igb_adapter (to handle proper config of
> tx-ring when adapter is brought up.
> - add TXDCTL logic (part of preparatory work for TSN) to igb-driver
> - Improve SR(A|B) accountingo
>
> ALSA Shim
> - Allow userspace to grab much smaller chunks of data (down to a single
> Class A frame for S16_LE 2ch 48kHz).
> - Create the card with index/id pattern to avoid collision with other
> cards.
> * v1
>
> Before reading on - this is not even beta, but I'd really appreciate if
> people would comment on the overall architecture and perhaps provide
> some pointers to where I should improve/fix/update
> - thanks!
>
> This is a very early RFC for a TSN-driver in the kernel. It has been
> floating around in my repo for a while and I would appreciate some
> feedback on the overall design to avoid doing some major blunders.
>
> There are at least one AVB-driver (the AV-part of TSN) in the kernel
> already. This driver aims to solve a wider scope as TSN can do much more
> than just audio. A very basic ALSA-driver is added to the end that
> allows you to play music between 2 machines using aplay in one end and
> arecord | aplay on the other (some fiddling required) We have plans for
> doing the same for v4l2 eventually (but there are other fishes to fry
> first). The same goes for a TSN_SOCK type approach as well.
>
> Henrik Austad (9):
> igb: add missing fields to TXDCTL-register
> TSN: add documentation
> TSN: Add the standard formerly known as AVB to the kernel
> Adding TSN-driver to Intel I210 controller
> Add TSN header for the driver
> Add TSN machinery to drive the traffic from a shim over the network
> Add TSN event-tracing
> AVB ALSA - Add ALSA shim for TSN
> MAINTAINERS: add TSN/AVB-entries
>
> Documentation/TSN/tsn.txt | 345 ++++++++
> MAINTAINERS | 14 +
> drivers/media/Kconfig | 15 +
> drivers/media/Makefile | 2 +-
> drivers/media/avb/Makefile | 5 +
> drivers/media/avb/avb_alsa.c | 793 +++++++++++++++++
> drivers/media/avb/tsn_iec61883.h | 152 ++++
> drivers/net/ethernet/intel/Kconfig | 18 +
> drivers/net/ethernet/intel/igb/Makefile | 2 +-
> drivers/net/ethernet/intel/igb/e1000_82575.h | 4 +
> drivers/net/ethernet/intel/igb/igb.h | 26 +
> drivers/net/ethernet/intel/igb/igb_main.c | 39 +-
> drivers/net/ethernet/intel/igb/igb_tsn.c | 468 ++++++++++
> include/linux/netdevice.h | 44 +
> include/linux/tsn.h | 952 +++++++++++++++++++++
> include/trace/events/tsn.h | 333 ++++++++
> net/Kconfig | 1 +
> net/Makefile | 1 +
> net/tsn/Kconfig | 32 +
> net/tsn/Makefile | 6 +
> net/tsn/tsn_configfs.c | 673 +++++++++++++++
> net/tsn/tsn_core.c | 1189 ++++++++++++++++++++++++++
> net/tsn/tsn_header.c | 162 ++++
> net/tsn/tsn_internal.h | 397 +++++++++
> net/tsn/tsn_net.c | 392 +++++++++
> 25 files changed, 6061 insertions(+), 4 deletions(-)
> create mode 100644 Documentation/TSN/tsn.txt
> create mode 100644 drivers/media/avb/Makefile
> create mode 100644 drivers/media/avb/avb_alsa.c
> create mode 100644 drivers/media/avb/tsn_iec61883.h
> create mode 100644 drivers/net/ethernet/intel/igb/igb_tsn.c
> create mode 100644 include/linux/tsn.h
> create mode 100644 include/trace/events/tsn.h
> create mode 100644 net/tsn/Kconfig
> create mode 100644 net/tsn/Makefile
> create mode 100644 net/tsn/tsn_configfs.c
> create mode 100644 net/tsn/tsn_core.c
> create mode 100644 net/tsn/tsn_header.c
> create mode 100644 net/tsn/tsn_internal.h
> create mode 100644 net/tsn/tsn_net.c
>
> --
> 2.7.4
From: Greg <[email protected]>
Date: Fri, 16 Dec 2016 10:12:44 -0800
> On Fri, 2016-12-16 at 18:59 +0100, [email protected] wrote:
>> From: Henrik Austad <[email protected]>
>>
>>
>> The driver is directed via ConfigFS as we need userspace to handle
>> stream-reservation (MSRP), discovery and enumeration (IEEE 1722.1) and
>> whatever other management is needed. This also includes running an
>> appropriate PTP daemon (TSN favors gPTP).
>
> I suggest using a generic netlink interface to communicate with the
> driver to set up and/or configure your drivers.
>
> I think configfs is frowned upon for network drivers. YMMV.
Agreed.
On Fri, Dec 16, 2016 at 01:20:57PM -0500, David Miller wrote:
> From: Greg <[email protected]>
> Date: Fri, 16 Dec 2016 10:12:44 -0800
>
> > On Fri, 2016-12-16 at 18:59 +0100, [email protected] wrote:
> >> From: Henrik Austad <[email protected]>
> >>
> >>
> >> The driver is directed via ConfigFS as we need userspace to handle
> >> stream-reservation (MSRP), discovery and enumeration (IEEE 1722.1) and
> >> whatever other management is needed. This also includes running an
> >> appropriate PTP daemon (TSN favors gPTP).
> >
> > I suggest using a generic netlink interface to communicate with the
> > driver to set up and/or configure your drivers.
> >
> > I think configfs is frowned upon for network drivers. YMMV.
>
> Agreed.
Ok - thanks!
I will have look at netlink and see if I can wrap my head around it and if
I can apply it to how to bring the media-devices up once the TSN-link has
been configured.
Thanks! :)
--
Henrik Austad
On Fri, Dec 16, 2016 at 06:59:04PM +0100, [email protected] wrote:
> The driver is directed via ConfigFS as we need userspace to handle
> stream-reservation (MSRP), discovery and enumeration (IEEE 1722.1) and
> whatever other management is needed.
I complained about configfs before, but you didn't listen.
> 2 new fields in netdev_ops have been introduced, and the Intel
> igb-driver has been updated (as this an AVB-capable NIC which is
> available as a PCI-e card).
The igb hacks show that you are on the wrong track. We can and should
be able to support TSN without resorting to driver specific hacks and
module parameters.
> Before reading on - this is not even beta, but I'd really appreciate if
> people would comment on the overall architecture and perhaps provide
> some pointers to where I should improve/fix/update
As I said before about V1, this architecture stinks. You appear to
have continued hacking along and posted the same design again. Did
you even address any of the points I raised back then?
You are trying to put tons of code into the kernel that really belongs
in user space, and at the same time, you omit critical functions that
only the kernel can provide.
> There are at least one AVB-driver (the AV-part of TSN) in the kernel
> already.
And which driver is that?
Thanks,
Richard
On Fri, Dec 16, 2016 at 06:59:09PM +0100, [email protected] wrote:
> +/*
> + * List of current subtype fields in the common header of AVTPDU
> + *
> + * Note: AVTPDU is a remnant of the standards from when it was AVB.
> + *
> + * The list has been updated with the recent values from IEEE 1722, draft 16.
> + */
> +enum avtp_subtype {
> + TSN_61883_IIDC = 0, /* IEC 61883/IIDC Format */
> + TSN_MMA_STREAM, /* MMA Streams */
> + TSN_AAF, /* AVTP Audio Format */
> + TSN_CVF, /* Compressed Video Format */
> + TSN_CRF, /* Clock Reference Format */
> + TSN_TSCF, /* Time-Synchronous Control Format */
> + TSN_SVF, /* SDI Video Format */
> + TSN_RVF, /* Raw Video Format */
> + /* 0x08 - 0x6D reserved */
> + TSN_AEF_CONTINOUS = 0x6e, /* AES Encrypted Format Continous */
> + TSN_VSF_STREAM, /* Vendor Specific Format Stream */
> + /* 0x70 - 0x7e reserved */
> + TSN_EF_STREAM = 0x7f, /* Experimental Format Stream */
> + /* 0x80 - 0x81 reserved */
> + TSN_NTSCF = 0x82, /* Non Time-Synchronous Control Format */
> + /* 0x83 - 0xed reserved */
> + TSN_ESCF = 0xec, /* ECC Signed Control Format */
> + TSN_EECF, /* ECC Encrypted Control Format */
> + TSN_AEF_DISCRETE, /* AES Encrypted Format Discrete */
> + /* 0xef - 0xf9 reserved */
> + TSN_ADP = 0xfa, /* AVDECC Discovery Protocol */
> + TSN_AECP, /* AVDECC Enumeration and Control Protocol */
> + TSN_ACMP, /* AVDECC Connection Management Protocol */
> + /* 0xfd reserved */
> + TSN_MAAP = 0xfe, /* MAAP Protocol */
> + TSN_EF_CONTROL, /* Experimental Format Control */
> +};
The kernel shouldn't be in the business of assembling media packets.
Thanks,
Richard
On Fri, Dec 16, 2016 at 11:09:38PM +0100, Richard Cochran wrote:
> On Fri, Dec 16, 2016 at 06:59:09PM +0100, [email protected] wrote:
> > +/*
> > + * List of current subtype fields in the common header of AVTPDU
> > + *
> > + * Note: AVTPDU is a remnant of the standards from when it was AVB.
> > + *
> > + * The list has been updated with the recent values from IEEE 1722, draft 16.
> > + */
> > +enum avtp_subtype {
> > + TSN_61883_IIDC = 0, /* IEC 61883/IIDC Format */
> > + TSN_MMA_STREAM, /* MMA Streams */
> > + TSN_AAF, /* AVTP Audio Format */
> > + TSN_CVF, /* Compressed Video Format */
> > + TSN_CRF, /* Clock Reference Format */
> > + TSN_TSCF, /* Time-Synchronous Control Format */
> > + TSN_SVF, /* SDI Video Format */
> > + TSN_RVF, /* Raw Video Format */
> > + /* 0x08 - 0x6D reserved */
> > + TSN_AEF_CONTINOUS = 0x6e, /* AES Encrypted Format Continous */
> > + TSN_VSF_STREAM, /* Vendor Specific Format Stream */
> > + /* 0x70 - 0x7e reserved */
> > + TSN_EF_STREAM = 0x7f, /* Experimental Format Stream */
> > + /* 0x80 - 0x81 reserved */
> > + TSN_NTSCF = 0x82, /* Non Time-Synchronous Control Format */
> > + /* 0x83 - 0xed reserved */
> > + TSN_ESCF = 0xec, /* ECC Signed Control Format */
> > + TSN_EECF, /* ECC Encrypted Control Format */
> > + TSN_AEF_DISCRETE, /* AES Encrypted Format Discrete */
> > + /* 0xef - 0xf9 reserved */
> > + TSN_ADP = 0xfa, /* AVDECC Discovery Protocol */
> > + TSN_AECP, /* AVDECC Enumeration and Control Protocol */
> > + TSN_ACMP, /* AVDECC Connection Management Protocol */
> > + /* 0xfd reserved */
> > + TSN_MAAP = 0xfe, /* MAAP Protocol */
> > + TSN_EF_CONTROL, /* Experimental Format Control */
> > +};
>
> The kernel shouldn't be in the business of assembling media packets.
No, but assembling the packets and shipping frames to a destination is not
neccessarily the same thing.
A nice workflow would be to signal to the shim that "I'm sending a
compressed video format" and then the shim/tsn_core will ship out the
frames over the network - and then you need to set TSN_CVF as subtype in
each header.
That does not that mean you should do H.264 encode/decode *in* the kernel
Perhaps this is better placed in include/uapi/tsn.h so that userspace and
kernel share the same header?
--
Henrik Austad
Hi Richard,
On Fri, Dec 16, 2016 at 11:05:30PM +0100, Richard Cochran wrote:
> On Fri, Dec 16, 2016 at 06:59:04PM +0100, [email protected] wrote:
> > The driver is directed via ConfigFS as we need userspace to handle
> > stream-reservation (MSRP), discovery and enumeration (IEEE 1722.1) and
> > whatever other management is needed.
>
> I complained about configfs before, but you didn't listen.
Yes you did, I remember quite well, and no, I didn't listen :)
At the time, there were other issues that I had to address. The
configfs-part is fairly isolated. As I tried to explain the last round,
the *reason* I've used ConfigFS thus far, is because it makes it pretty
easy from userspace to signal the driver to create a new alsa-device.
And the reason I haven't changed configfs, is because so far, that part has
worked fairly well and have made testing quite easy. At this stage, *this*
is what is helpful, not a perfect interface. This does not mean that
configfs is set in stone.
To clearify:
I'm sending out a new set now because, what I have works _fairly_ well for
testing and a way to see what you can do with AVB. Using spotify to play
music on random machines is quite entertaining.
It is by no means -done-, nor do I consider it done. I have been tight on
time, and instead of sitting in an office polishing on some code, I thought
it better to send out a new (and not done) set of patches so that others
could see it still being worked on. If this turned out to be noise-only, I
appologize!
> > 2 new fields in netdev_ops have been introduced, and the Intel
> > igb-driver has been updated (as this an AVB-capable NIC which is
> > available as a PCI-e card).
>
> The igb hacks show that you are on the wrong track. We can and should
> be able to support TSN without resorting to driver specific hacks and
> module parameters.
I was not able to find a sane way to change the mode of the NIC, some of
the settings required to enable Qav-mode must be done when bringing the
NIC up, so I needed hooks in _probe().
ANother elemnt needed is a way for tsn_core to ascertain if a NIC is
capable of TSN or not (this would be ndo_tsn_capable)
Then finally, you need to update values in a per-tx-queue manner when a new
stream is ready (hence ndo_tsn_link_configure).
What you mean by 'driver specific hacks' is not obvious though, TSN
requires a set of fairly standardized parameters (priority code points,
size of frames to send in a new stream and so on), adding this to the
hw-registers in the NIC is an operation that will be common for all
TSN-capable NICs.
> > Before reading on - this is not even beta, but I'd really appreciate if
> > people would comment on the overall architecture and perhaps provide
> > some pointers to where I should improve/fix/update
>
> As I said before about V1, this architecture stinks.
I like feedback when it's short, sweet and to the point
2 out of 3 ain't that bad ;)
> You appear to have continued hacking along and posted the same design
> again. Did you even address any of the points I raised back then?
So you did raise a lot of good points the last round, and no, I have not
had the time to address them properly. That does not mean I do not *want*
to (apart from configfs actually having worked quite nicely thus far and
'shim' being a name I like ;)
From the last round of discussion:
> 1. A proper userland stack for AVDECC, MAAP, FQTSS, and so on. The
> OpenAVB project does not offer much beyond simple examples.
Yes, I fully agree, as far as I know, no-one is working on this. That being
said, I have not paid much attention the userspace tooling lately. But all
of this must be handled in userspace, having avdecc in the kernel would be
an utter nightmare :)
> 2. A user space audio application that puts it all together, making
> use of the services in #1, the linuxptp gPTP service, the ALSA
> services, and the network connections. This program will have all
> the knowledge about packet formats, AV encodings, and the local HW
> capabilities. This program cannot yet be written, as we still need
> some kernel work in the audio and networking subsystems.
And therein lies the problem. It cannot yet be written, so we have to start
in *some* end. And as I repeatedly stated in June, I'm at an RFC here,
trying to spark some interest and lure other developers in :)
Also, I really do not want a media-application to care about _where_ the
frames are going. Sure, I see the issue of configuring a link, but that can
be done from _outside_ the media-application. VLC (or aplay, or totem, or
.. take your pick) should not have to worry about this.
Applications that require finer control over timestamping is easier to
adapt to AVB than all the others, I'd rather add special knobs for those
who are interested than adding a set of knobs that -all- applications must
be aware of.
Could be that we are talking about the same thing, just from different
perspectives.
> * Kernel Space
>
> 1. Providing frames with a future transmit time. For normal sockets,
> this can be in the CMESG data. For mmap'ed buffers, we will need a
> new format. (I think Arnd is working on a new layout.)
I need to revisit that discussion again I think.
> 2. Time based qdisc for transmitted frames. For MACs that support
> this (like the i210), we only have to place the frame into the
> correct queue. For normal HW, we want to be able to reserve a time
> window in which non-TSN frames are blocked. This is some work, but
> in the end it should be a generic solution that not only works
> "perfectly" with TSN HW but also provides best effort service using
> any NIC.
Yes, indeed, that would be one good solution, and quite a lot of work.
> 3. ALSA support for tunable AD/DA clocks. The rate of the Listener's
> DA clock must match that of the Talker and the other Listeners.
To nitpick a bit, all AD/DAs should match that of the gPTP grandmaster
(which in most settings would be the Talker). But yes, you need to adjust
the AD/DA. SRC is slow and painful, best to avoid.
> Either you adjust it in HW using a VCO or similar, or you do
> adaptive sample rate conversion in the application. (And that is
> another reason for *not* having a shared kernel buffer.) For the
> Talker, either you adjust the AD clock to match the PTP time, or
> you measure the frequency offset.
Yes, some hook into adjusting the clock is needed, I wonder if this is
possible via V4L2, or of the monitor-world is a completely different beast.
> 4. ALSA support for time triggered playback. The patch series
> completely ignore the critical issue of media clock recovery. The
> Listener must buffer the stream in order to play it exactly at a
> specified time. It cannot simply send the stream ASAP to the audio
> HW, because some other Listener might need longer. AFAICT, there is
> nothing in ALSA that allows you to say, sample X should be played at
> time Y.
Yes, and this requires a lot of change to ALSA (and probably something in
V4L2 as well?), so before we get to that, perhaps have a set of patches
that does this best effort and *then* work on getting time-triggered
playback into the kernel?
Another item that was brought up last round was getting timing-information
to/from ALSA, See driver/media/avb/avb_alsa.c, as a start it updates the
time for last incoming/outgoing frame so that userspace can get that
information. Probably buggy as heck :)
* Back to your email from last night*
> You are trying to put tons of code into the kernel that really belongs
> in user space, and at the same time, you omit critical functions that
> only the kernel can provide.
Some (well, to be honest, most) of the of the critical functions that my
driver omits, are omitted because they require substantial effort to
implement - and befor there's a need for this, that won't happen. So,
consider the TSN-driver such a need!
I'd love to use a qdisc that uses a time-triggered transmit, that would
drop the need for a lot of the stuff in tsn_core.c. The same goes for
time-triggered playback in media.
> > There are at least one AVB-driver (the AV-part of TSN) in the kernel
> > already.
>
> And which driver is that?
Ah, a proverbial slip of the changelog, we visited this the last iteration,
that would be the ravb-driver (which is an AVB capable NIC), but it does
not include much in the way of AVB-support *In* kernel. Sorry about that!
Since then, the iMX7 from NXP has arrived, and this also has HW-support for
TSN, but not in the kernel AFAICT.
So, the next issue I plan to tackle, is how I do buffers, the current
approach where tsn_core allocates memory is on its way out and I'll let the
shim (which means alsa/v4l2) will provide a buffer. Then I'll start looking
at qdisc.
Thanks!
--
Henrik Austad
On Sat, Dec 17, 2016 at 10:05:54AM +0100, Henrik Austad wrote:
> I'm sending out a new set now because, what I have works _fairly_ well for
> testing and a way to see what you can do with AVB. Using spotify to play
> music on random machines is quite entertaining.
You have missed the point about TSN entirely. Unless your demo has
synchronized playback (in the low microsecond range), then it really
is pointless. We can already play music over the LAN using gstreamer,
without a single kernel change. Heck, gstreamer even has its own
rudimentary synchronization, so your series is a step backwards.
> And therein lies the problem. It cannot yet be written, so we have to start
> in *some* end. And as I repeatedly stated in June, I'm at an RFC here,
> trying to spark some interest and lure other developers in :)
The best way to attract interest is to provide the critical
infrastructure missing in the kernel. Coding a media player in kernel
space is not very interesting in my view.
> Yes, and this requires a lot of change to ALSA (and probably something in
> V4L2 as well?), so before we get to that, perhaps have a set of patches
> that does this best effort and *then* work on getting time-triggered
> playback into the kernel?
No, we don't need a best effort implementation. That is gstreamer and Co.
> So, the next issue I plan to tackle, is how I do buffers, the current
> approach where tsn_core allocates memory is on its way out and I'll let the
> shim (which means alsa/v4l2) will provide a buffer. Then I'll start looking
> at qdisc.
More than once you wrote something like, "I know that's needed, but it
is just too hard ATM." Please start with qdisc and tc. That
shouldn't be too hard. If we had the AVB shaping rules with one or
two drivers supporting them, that would be one piece already done.
Thanks,
Richard