* [PATCH net-next v4 2/3] Linn packet sniffer core framework
2015-02-24 16:48 [PATCH net-next v4 0/3] Linn Ethernet Packet Sniffer driver Stathis Voukelatos
2015-02-24 16:48 ` [PATCH net-next v4 1/3] Linn Ethernet packet sniffer: device tree binding and vendor prefix Stathis Voukelatos
@ 2015-02-24 16:48 ` Stathis Voukelatos
2015-02-24 16:48 ` [PATCH net-next v4 3/3] Linn Ethernet packet sniffer driver Stathis Voukelatos
2015-02-25 15:19 ` [PATCH net-next v4 0/3] Linn Ethernet Packet Sniffer driver Richard Cochran
3 siblings, 0 replies; 15+ messages in thread
From: Stathis Voukelatos @ 2015-02-24 16:48 UTC (permalink / raw)
To: linux-kernel, netdev, devicetree; +Cc: Stathis Voukelatos
The framework registers each backend sniffer channel as a netdev,
which can be accessed from user space through a raw packet socket.
Packets received from user space are treated as a command string
configuration. Each match event from the backend driver will
generate a packet with the matching bytes plus an optional
timestamp, if configured by the command string.
Signed-off-by: Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
---
MAINTAINERS | 6 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/linn/Kconfig | 25 ++
drivers/net/ethernet/linn/Makefile | 19 ++
.../net/ethernet/linn/pkt-sniffer/core/Makefile | 19 ++
.../net/ethernet/linn/pkt-sniffer/core/snf_core.c | 344 +++++++++++++++++++++
.../net/ethernet/linn/pkt-sniffer/core/snf_core.h | 60 ++++
8 files changed, 475 insertions(+)
create mode 100644 drivers/net/ethernet/linn/Kconfig
create mode 100644 drivers/net/ethernet/linn/Makefile
create mode 100644 drivers/net/ethernet/linn/pkt-sniffer/core/Makefile
create mode 100644 drivers/net/ethernet/linn/pkt-sniffer/core/snf_core.c
create mode 100644 drivers/net/ethernet/linn/pkt-sniffer/core/snf_core.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 0597c5b..6186c0c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5828,6 +5828,12 @@ M: Sasha Levin <sasha.levin@oracle.com>
S: Maintained
F: tools/lib/lockdep/
+LINN PACKET SNIFFER DRIVER
+M: Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+S: Maintained
+F: drivers/net/ethernet/linn/
+F: Documentation/devicetree/bindings/net/linn-ether-packet-sniffer.txt
+
LINUX FOR IBM pSERIES (RS/6000)
M: Paul Mackerras <paulus@au.ibm.com>
W: http://www.ibm.com/linux/ltc/projects/ppc
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index eadcb05..ee4b3ed 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -103,6 +103,7 @@ config LANTIQ_ETOP
---help---
Support for the MII0 inside the Lantiq SoC
+source "drivers/net/ethernet/linn/Kconfig"
source "drivers/net/ethernet/marvell/Kconfig"
source "drivers/net/ethernet/mellanox/Kconfig"
source "drivers/net/ethernet/micrel/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index 1367afc..f8071d3 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_NET_VENDOR_HP) += hp/
obj-$(CONFIG_NET_VENDOR_IBM) += ibm/
obj-$(CONFIG_NET_VENDOR_INTEL) += intel/
obj-$(CONFIG_NET_VENDOR_I825XX) += i825xx/
+obj-$(CONFIG_NET_VENDOR_LINN) += linn/
obj-$(CONFIG_NET_VENDOR_XSCALE) += xscale/
obj-$(CONFIG_IP1000) += icplus/
obj-$(CONFIG_JME) += jme.o
diff --git a/drivers/net/ethernet/linn/Kconfig b/drivers/net/ethernet/linn/Kconfig
new file mode 100644
index 0000000..6654f4e
--- /dev/null
+++ b/drivers/net/ethernet/linn/Kconfig
@@ -0,0 +1,25 @@
+#
+# Linn device configuration
+#
+
+config NET_VENDOR_LINN
+ bool "Linn devices"
+ ---help---
+ Say Y to add support for Linn Products devices.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about Linn devices. If you say Y, you will be asked for
+ your specific device in the following questions.
+
+if NET_VENDOR_LINN
+
+menuconfig PKT_SNIFFER
+ tristate "Packet sniffer support"
+ ---help---
+ Say Y to add support for the packet sniffer driver framework.
+
+ The core driver can also be built as a module. If so, the module
+ will be called snf_core.
+
+endif # NET_VENDOR_LINN
diff --git a/drivers/net/ethernet/linn/Makefile b/drivers/net/ethernet/linn/Makefile
new file mode 100644
index 0000000..f3338f3
--- /dev/null
+++ b/drivers/net/ethernet/linn/Makefile
@@ -0,0 +1,19 @@
+###############################################################################
+# Makefile for the Linn Products device drivers
+#
+# Copyright (C) 2015 Linn Products Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2
+# as published by the Free Software Foundation.
+#
+# 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.
+#
+# Written by:
+# Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+###############################################################################
+
+obj-$(CONFIG_PKT_SNIFFER) += pkt-sniffer/core/
diff --git a/drivers/net/ethernet/linn/pkt-sniffer/core/Makefile b/drivers/net/ethernet/linn/pkt-sniffer/core/Makefile
new file mode 100644
index 0000000..4dc8f11
--- /dev/null
+++ b/drivers/net/ethernet/linn/pkt-sniffer/core/Makefile
@@ -0,0 +1,19 @@
+###############################################################################
+# Makefile for the Linn packet sniffer framework driver
+#
+# Copyright (C) 2015 Linn Products Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2
+# as published by the Free Software Foundation.
+#
+# 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.
+#
+# Written by:
+# Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+###############################################################################
+
+obj-$(CONFIG_PKT_SNIFFER) += snf_core.o
diff --git a/drivers/net/ethernet/linn/pkt-sniffer/core/snf_core.c b/drivers/net/ethernet/linn/pkt-sniffer/core/snf_core.c
new file mode 100644
index 0000000..9490943
--- /dev/null
+++ b/drivers/net/ethernet/linn/pkt-sniffer/core/snf_core.c
@@ -0,0 +1,344 @@
+/*
+ * The Linn packet sniffer allows incoming or outgoing data packets
+ * to be parsed, matched against a user-defined data pattern and
+ * timestamped.
+ *
+ * Packet filtering is driven by a user-supplied command string
+ * which consists of a series of interleaved command and data bytes.
+ * ie. the command string has the following format:
+ * --------------------------------
+ * | CMD | DATA | CMD | DATA | ....
+ * --------------------------------
+ * The supported commands and their function is documented in the
+ * corresponding backend driver.
+ * Data returned to the user for each matched packet include selected
+ * data bytes from the packet and optionally a timestamp.
+ *
+ * This module is the packet sniffer core driver that handles
+ * the interface between the H/W backend modules and user space:
+ *
+ * - Each backend sniffer channel is registered as a netdev, eg. Ethernet
+ * TX and RX are two different channels.
+ *
+ * - Access from user space is through AF_PACKET sockets bound to
+ * the netdev
+ *
+ * - User space supplies the command string by writing a packet to a socket.
+ * The packet will contain interleaved commands and data values as
+ * shown above.
+ *
+ * - Data from each packet match event are also returned through an AF_PACKET
+ * socket. Timestamps use the existing kernel timestamping framework,
+ * ie. they are returned to the user through a socket control message
+ * (ancillary data).
+ *
+ * Example usage:
+ * - Open a socket
+ * fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ * - Bind it to the netdev
+ * strcpy(ifr.ifr_name, "snfethrx0");
+ * ioctl(fd, SIOCGIFINDEX, &ifr);
+ * ll.sll_family = AF_PACKET;
+ * ll.sll_ifindex = ifr.ifr_ifindex;
+ * bind(fd, (struct sockaddr *) &ll, sizeof(ll));
+ * - Enable timestamping at the driver and socket level
+ * ioctl(fd, SIOCSHWTSTAMP, &ifr);
+ * opt = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;
+ * setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &opt, sizeof(opt));
+ * - Configure a command string
+ * char buf[4] = {0x03, 0xAB, 0x4, 0x00};
+ * write(fd, buf, 4);
+ * - Wait for a packet match
+ * select(fd+1, &rfds, NULL, NULL, NULL);
+ * - Read data
+ * recvmsg(fd, &msg, 0);
+ * - Timestamp will be available through a control message
+ * cmsg = CMSG_FIRSTHDR(&msg);
+ * if (cmsg->cmsg_type == SO_TIMESTAMPING)
+ * struct timespec *scmt = CMSG_DATA(cmsg);
+ *
+ * Copyright (C) 2015 Linn Products Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Written by:
+ * Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/net_tstamp.h>
+#include <linux/if_arp.h>
+#include <linux/spinlock.h>
+#include "snf_core.h"
+
+struct snf_ndev_state {
+ struct snf_chan *chan;
+ bool rx_tstamp_enabled;
+ spinlock_t lock;
+};
+
+static int hw_timestamp_set(struct net_device *dev, struct ifreq *ifr)
+{
+ struct snf_ndev_state *priv = netdev_priv(dev);
+ struct hwtstamp_config tconf;
+
+ if (copy_from_user(&tconf, ifr->ifr_data, sizeof(tconf)))
+ return -EFAULT;
+
+ /* No TX timestamping supported.
+ * This interface only receives packets from the sniffer backend
+ */
+ if (tconf.tx_type != HWTSTAMP_TX_OFF)
+ return -ERANGE;
+
+ if (tconf.rx_filter != HWTSTAMP_FILTER_NONE) {
+ /* If timestamping is not enabled in the command string then
+ * we cannot return any RX timestamps
+ */
+ if (!priv->chan->ts_enabled(priv->chan))
+ return -ERANGE;
+ priv->rx_tstamp_enabled = true;
+ tconf.rx_filter = HWTSTAMP_FILTER_ALL;
+ } else {
+ priv->rx_tstamp_enabled = false;
+ }
+
+ return copy_to_user(ifr->ifr_data, &tconf, sizeof(tconf)) ? -EFAULT : 0;
+}
+
+static int hw_timestamp_get(struct net_device *dev, struct ifreq *ifr)
+{
+ struct snf_ndev_state *priv = netdev_priv(dev);
+ struct hwtstamp_config tconf;
+
+ memset(&tconf, 0, sizeof(tconf));
+ tconf.tx_type = HWTSTAMP_TX_OFF;
+ /* We also need to check here that the current command string
+ * will cause timestamps to be generated
+ */
+ tconf.rx_filter = (priv->rx_tstamp_enabled &&
+ priv->chan->ts_enabled(priv->chan)) ?
+ HWTSTAMP_FILTER_ALL : HWTSTAMP_FILTER_NONE;
+
+ return copy_to_user(ifr->ifr_data, &tconf, sizeof(tconf)) ? -EFAULT : 0;
+}
+
+static int snf_init(struct net_device *dev)
+{
+ struct snf_ndev_state *priv = netdev_priv(dev);
+
+ /* Two bytes per command string entry */
+ dev->mtu = priv->chan->max_ptn_entries(priv->chan) * 2;
+ return 0;
+}
+
+static int snf_open(struct net_device *dev)
+{
+ struct snf_ndev_state *priv = netdev_priv(dev);
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ ret = priv->chan->start(priv->chan);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return ret;
+}
+
+static int snf_stop(struct net_device *dev)
+{
+ struct snf_ndev_state *priv = netdev_priv(dev);
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ ret = priv->chan->stop(priv->chan);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return ret;
+}
+
+static int snf_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ switch (cmd) {
+ case SIOCSHWTSTAMP:
+ return hw_timestamp_set(dev, ifr);
+
+ case SIOCGHWTSTAMP:
+ return hw_timestamp_get(dev, ifr);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int snf_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct snf_ndev_state *priv = netdev_priv(dev);
+ struct snf_chan *sch = priv->chan;
+ unsigned long flags;
+ int ret;
+
+ /* When a packet is sent to the netdev it is assumed to
+ * contain a new command string. So apply it.
+ */
+ spin_lock_irqsave(&priv->lock, flags);
+ sch->stop(sch);
+ ret = sch->set_pattern(sch, skb->data, skb->len / 2);
+ sch->start(sch);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (ret < 0) {
+ dev->stats.tx_dropped++;
+ } else {
+ dev->stats.tx_packets++;
+ dev->stats.tx_bytes += skb->len;
+ }
+
+ dev_kfree_skb(skb);
+ return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops snf_netdev_ops = {
+ .ndo_init = snf_init,
+ .ndo_open = snf_open,
+ .ndo_stop = snf_stop,
+ .ndo_start_xmit = snf_start_xmit,
+ .ndo_do_ioctl = snf_ioctl
+};
+
+static void snf_setup(struct net_device *dev)
+{
+ dev->netdev_ops = &snf_netdev_ops;
+ dev->tx_queue_len = 1;
+ dev->flags = IFF_NOARP;
+ dev->type = ARPHRD_NONE;
+}
+
+/* Initialise netdev for a sniffer channel */
+int snf_channel_add(struct snf_chan *sch, const char *name)
+{
+ int ret;
+ struct net_device *ndev;
+ struct snf_ndev_state *priv;
+
+ ndev = alloc_netdev(sizeof(*priv), name, NET_NAME_UNKNOWN, snf_setup);
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ priv->chan = sch;
+ priv->rx_tstamp_enabled = false;
+ spin_lock_init(&priv->lock);
+
+ ret = register_netdev(ndev);
+ if (ret < 0) {
+ free_netdev(ndev);
+ return ret;
+ }
+
+ sch->cstate = ndev;
+ return 0;
+}
+EXPORT_SYMBOL(snf_channel_add);
+
+/* Release netdev for a sniffer channel and free resources */
+int snf_channel_remove(struct snf_chan *sch)
+{
+ struct net_device *ndev = (struct net_device *)sch->cstate;
+
+ unregister_netdev(ndev);
+ free_netdev(ndev);
+ return 0;
+}
+EXPORT_SYMBOL(snf_channel_remove);
+
+/* Send a packet to user space for a sniffer match event */
+int snf_channel_notify_match(struct snf_chan *sch, struct snf_match_evt *mt)
+{
+ struct net_device *ndev = (struct net_device *)sch->cstate;
+ struct snf_ndev_state *priv = netdev_priv(ndev);
+ struct sk_buff *skb;
+ struct skb_shared_hwtstamps *skts;
+
+ skb = netdev_alloc_skb(ndev, mt->len);
+ if (!skb) {
+ ndev->stats.rx_dropped++;
+ return -ENOMEM;
+ }
+
+ skb_put(skb, mt->len);
+ skb_copy_to_linear_data(skb, mt->data, mt->len);
+ if (mt->ts_valid && priv->rx_tstamp_enabled) {
+ skts = skb_hwtstamps(skb);
+ skts->hwtstamp = ns_to_ktime(mt->ts);
+ }
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += mt->len;
+ netif_rx(skb);
+ return 0;
+}
+EXPORT_SYMBOL(snf_channel_notify_match);
+
+/* Suspend the interface */
+int snf_channel_suspend(struct snf_chan *sch)
+{
+ struct net_device *ndev = (struct net_device *)sch->cstate;
+ struct snf_ndev_state *priv = netdev_priv(ndev);
+ unsigned long flags;
+ int ret;
+
+ if (!netif_running(ndev))
+ return 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ ret = sch->stop(sch);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ netif_device_detach(ndev);
+ return ret;
+}
+EXPORT_SYMBOL(snf_channel_suspend);
+
+/* Resume the interface */
+int snf_channel_resume(struct snf_chan *sch)
+{
+ struct net_device *ndev = (struct net_device *)sch->cstate;
+ struct snf_ndev_state *priv = netdev_priv(ndev);
+ unsigned long flags;
+ int ret;
+
+ if (!netif_running(ndev))
+ return 0;
+
+ netif_device_attach(ndev);
+ spin_lock_irqsave(&priv->lock, flags);
+ ret = sch->start(sch);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL(snf_channel_resume);
+
+static int __init snf_core_init(void)
+{
+ return 0;
+}
+
+static void __exit snf_core_cleanup(void)
+{
+}
+
+module_init(snf_core_init);
+module_exit(snf_core_cleanup);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Core packet sniffer driver");
+MODULE_AUTHOR("Linn Products Ltd");
+
diff --git a/drivers/net/ethernet/linn/pkt-sniffer/core/snf_core.h b/drivers/net/ethernet/linn/pkt-sniffer/core/snf_core.h
new file mode 100644
index 0000000..73a30ff
--- /dev/null
+++ b/drivers/net/ethernet/linn/pkt-sniffer/core/snf_core.h
@@ -0,0 +1,60 @@
+/*
+ * Packet sniffer core driver
+ * - this header provides the interface to specific backend packet
+ * sniffer implementations
+ *
+ * Copyright (C) 2015 Linn Products Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Written by:
+ * Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+ */
+#ifndef __SNF_CORE_H
+#define __SNF_CORE_H
+
+#include <linux/types.h>
+
+/* This is a global maximum. Each backend will have its own limit */
+#define MAX_MATCH_BYTES 512
+
+/* Sniffer channel data structure */
+struct snf_chan {
+ int (*start)(struct snf_chan *sch);
+ int (*stop)(struct snf_chan *sch);
+ int (*set_pattern)(struct snf_chan *sch, const u8 *pattern, int count);
+ int (*max_ptn_entries)(struct snf_chan *sch);
+ int (*ts_enabled)(struct snf_chan *sch);
+ void *cstate;
+};
+
+/* Data from a sniffer match event */
+struct snf_match_evt {
+ int ts_valid; /* flag indicating if timestamp is present */
+ u64 ts; /* timestamp value */
+ u8 data[MAX_MATCH_BYTES]; /* packet data bytes matched by sniffer */
+ int len; /* number of valid bytes in the 'data' buffer */
+};
+
+/* Registers a sniffer channel */
+int snf_channel_add(struct snf_chan *sch, const char *name);
+
+/* Removes a sniffer channel */
+int snf_channel_remove(struct snf_chan *sch);
+
+/* Send a packet to user space for a sniffer match event */
+int snf_channel_notify_match(struct snf_chan *sch, struct snf_match_evt *mt);
+
+/* Suspend and resume operations */
+int snf_channel_suspend(struct snf_chan *sch);
+int snf_channel_resume(struct snf_chan *sch);
+
+#endif
+
--
1.9.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v4 3/3] Linn Ethernet packet sniffer driver
2015-02-24 16:48 [PATCH net-next v4 0/3] Linn Ethernet Packet Sniffer driver Stathis Voukelatos
2015-02-24 16:48 ` [PATCH net-next v4 1/3] Linn Ethernet packet sniffer: device tree binding and vendor prefix Stathis Voukelatos
2015-02-24 16:48 ` [PATCH net-next v4 2/3] Linn packet sniffer core framework Stathis Voukelatos
@ 2015-02-24 16:48 ` Stathis Voukelatos
2015-02-25 15:19 ` [PATCH net-next v4 0/3] Linn Ethernet Packet Sniffer driver Richard Cochran
3 siblings, 0 replies; 15+ messages in thread
From: Stathis Voukelatos @ 2015-02-24 16:48 UTC (permalink / raw)
To: linux-kernel, netdev, devicetree; +Cc: Stathis Voukelatos
Driver for the Ethernet Mii packet sniffer H/W module found in
the IMG Pistachio SoC.
Signed-off-by: Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
---
drivers/net/ethernet/linn/Kconfig | 11 +
drivers/net/ethernet/linn/Makefile | 1 +
.../linn/pkt-sniffer/backends/ether/Makefile | 20 +
.../linn/pkt-sniffer/backends/ether/channel.c | 444 +++++++++++++++++++++
.../linn/pkt-sniffer/backends/ether/channel.h | 80 ++++
.../ethernet/linn/pkt-sniffer/backends/ether/hw.h | 46 +++
.../linn/pkt-sniffer/backends/ether/platform.c | 318 +++++++++++++++
7 files changed, 920 insertions(+)
create mode 100644 drivers/net/ethernet/linn/pkt-sniffer/backends/ether/Makefile
create mode 100644 drivers/net/ethernet/linn/pkt-sniffer/backends/ether/channel.c
create mode 100644 drivers/net/ethernet/linn/pkt-sniffer/backends/ether/channel.h
create mode 100644 drivers/net/ethernet/linn/pkt-sniffer/backends/ether/hw.h
create mode 100644 drivers/net/ethernet/linn/pkt-sniffer/backends/ether/platform.c
diff --git a/drivers/net/ethernet/linn/Kconfig b/drivers/net/ethernet/linn/Kconfig
index 6654f4e..bbfd6a4 100644
--- a/drivers/net/ethernet/linn/Kconfig
+++ b/drivers/net/ethernet/linn/Kconfig
@@ -22,4 +22,15 @@ menuconfig PKT_SNIFFER
The core driver can also be built as a module. If so, the module
will be called snf_core.
+config PKT_SNIFFER_ETHER
+ tristate "Ethernet packet sniffer"
+ depends on PKT_SNIFFER
+ help
+ Say Y here if you want to use the Ethernet packet sniffer
+ module by Linn Products Ltd. It can be found in the upcoming
+ Pistachio SoC by Imagination Technologies.
+
+ The driver can also be built as a module. If so, the module
+ will be called snf_ether.
+
endif # NET_VENDOR_LINN
diff --git a/drivers/net/ethernet/linn/Makefile b/drivers/net/ethernet/linn/Makefile
index f3338f3..f51eb66 100644
--- a/drivers/net/ethernet/linn/Makefile
+++ b/drivers/net/ethernet/linn/Makefile
@@ -17,3 +17,4 @@
###############################################################################
obj-$(CONFIG_PKT_SNIFFER) += pkt-sniffer/core/
+obj-$(CONFIG_PKT_SNIFFER_ETHER) += pkt-sniffer/backends/ether/
diff --git a/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/Makefile b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/Makefile
new file mode 100644
index 0000000..1f97e51
--- /dev/null
+++ b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/Makefile
@@ -0,0 +1,20 @@
+###############################################################################
+# Makefile for the Linn ethernet packet sniffer driver
+#
+# Copyright (C) 2015 Linn Products Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2
+# as published by the Free Software Foundation.
+#
+# 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.
+#
+# Written by:
+# Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+###############################################################################
+
+obj-$(CONFIG_PKT_SNIFFER_ETHER) += snf_ether.o
+snf_ether-objs := platform.o channel.o
diff --git a/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/channel.c b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/channel.c
new file mode 100644
index 0000000..87ec790
--- /dev/null
+++ b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/channel.c
@@ -0,0 +1,444 @@
+/*
+ * Ethernet Mii packet sniffer driver
+ * - channel functions
+ *
+ * Copyright (C) 2015 Linn Products Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Written by:
+ * Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+ */
+#include <linux/io.h>
+#include <linux/hrtimer.h>
+#include <linux/clocksource.h>
+#include "../../core/snf_core.h"
+#include "hw.h"
+#include "channel.h"
+
+#define to_ether_snf_chan(dev) container_of(dev, struct ether_snf_chan, chan)
+
+#define CMD_DONTCARE 0
+#define CMD_MATCH 1
+#define CMD_COPY 2
+#define CMD_MATCHSTAMP 3
+#define CMD_COPYDONE 4
+
+/* Checks if the supplied command string is compatible with the
+ * capabilities of the H/W.
+ */
+static bool validate_pattern(
+ struct ether_snf_chan *ch,
+ const u8 *buf,
+ int count)
+{
+ int i, complete, max_copy_bytes;
+ int ts = 0;
+ int copy_before = 0;
+ int copy_after = 0;
+
+ if (count > ch->max_cmds)
+ return false;
+
+ /* Iterate through the commands in the string */
+ for (i = 0, complete = 0; (i < count) && !complete; i++) {
+ u8 cmd = buf[2 * i];
+
+ switch (cmd) {
+ case CMD_DONTCARE:
+ case CMD_MATCH:
+ break;
+
+ case CMD_MATCHSTAMP:
+ /* The timestamp needs to be word-aligned in the FIFO
+ * therefore, the number of 'copy' commands before it
+ * needs to be a multiple of 4
+ */
+ if (copy_before & 3)
+ return false;
+ /* Signal that a timestamp will be present */
+ ts = 1;
+ break;
+
+ case CMD_COPY:
+ /* Increment count of bytes that will be returned */
+ if (ts)
+ copy_after++;
+ else
+ copy_before++;
+ break;
+
+ case CMD_COPYDONE:
+ /* Increment count of bytes that will be returned
+ * This command terminates the string
+ */
+ if (ts)
+ copy_after++;
+ else
+ copy_before++;
+ /* This command completes the command string */
+ complete = 1;
+ break;
+
+ default:
+ /* Invalid command id */
+ return false;
+ }
+ }
+
+ /* Check if the string was properly terminated
+ * and contained valid number of commands
+ */
+ if (complete) {
+ max_copy_bytes = ch->fifo_blk_words * 4;
+ if (ts)
+ max_copy_bytes -= 4;
+ /* Too many copy commands will case the FIFO
+ * to wrap around
+ */
+ if ((copy_before + copy_after) > max_copy_bytes)
+ return false;
+ ch->nfb_before = copy_before;
+ ch->nfb_after = copy_after;
+ ch->evt.ts_valid = ts;
+ ch->evt.len = ch->nfb_before + ch->nfb_after;
+ return true;
+ }
+
+ /* Command string not terminated */
+ return false;
+}
+
+/* Channel methods */
+
+/* Enables the channel */
+static int esnf_start(struct snf_chan *dev)
+{
+ struct ether_snf_chan *ch = to_ether_snf_chan(dev);
+
+ if (!ch->started) {
+ ch->evt.ts = 0;
+ /* Enable interrupts */
+ iowrite32(ch->data_irq_bit | ch->full_irq_bit,
+ ch->regs + SET_INTERRUPT_ENABLE);
+ /* Enable the packet matching logic */
+ iowrite32(ENABLE_BIT, ch->reg_enable);
+
+ ch->started = 1;
+ dev_info(ch->dev, "%s: started\n", ch->name);
+ } else {
+ dev_dbg(ch->dev, "%s: already running\n", ch->name);
+ }
+
+ return 0;
+}
+
+/* Disables the channel */
+static int esnf_stop(struct snf_chan *dev)
+{
+ struct ether_snf_chan *ch = to_ether_snf_chan(dev);
+
+ if (ch->started) {
+ /* Disable interrupts */
+ iowrite32(ch->data_irq_bit | ch->full_irq_bit,
+ ch->regs + CLEAR_INTERRUPT_ENABLE);
+ /* Stop the sniffer channel */
+ iowrite32(0, ch->reg_enable);
+ /* Clear any pending interrupts */
+ iowrite32(ch->data_irq_bit | ch->full_irq_bit,
+ ch->regs + INTERRUPT_STATUS);
+
+ ch->started = 0;
+ dev_info(ch->dev, "%s: stopped\n", ch->name);
+ } else {
+ dev_dbg(ch->dev, "%s: already stopped\n", ch->name);
+ }
+
+ return 0;
+}
+
+/* Sets the command string (pattern) for the channel
+ * The bytes in the pattern buffer are in the following order:
+ */
+static int esnf_set_pattern(struct snf_chan *dev, const u8 *pattern, int count)
+{
+ struct ether_snf_chan *ch = to_ether_snf_chan(dev);
+ int i, shift = 0;
+ u32 val = 0, *ptr;
+
+ dev_info(ch->dev, "%s: set cmd pattern with %d entries\n",
+ ch->name, count);
+
+ if (ch->started) {
+ dev_err(ch->dev,
+ "%s: cannot apply cmd pattern. Channel is active\n",
+ ch->name);
+ return -EBUSY;
+ }
+
+ if (!validate_pattern(ch, pattern, count)) {
+ dev_err(ch->dev,
+ "%s: invalid cmd pattern\n",
+ ch->name);
+ return -EINVAL;
+ }
+
+ for (ptr = ch->cmd_ram, i = 0, shift = 24; i < (2 * count); i++) {
+ val |= ((u32)pattern[i]) << shift;
+ if (!shift) {
+ iowrite32(val, ptr++);
+ val = 0;
+ shift = 24;
+ } else {
+ shift -= 8;
+ }
+ }
+ if (shift)
+ iowrite32(val, ptr);
+
+ return 0;
+}
+
+/* Returns max number of commands supported by the channel */
+static int esnf_max_ptn_entries(struct snf_chan *dev)
+{
+ struct ether_snf_chan *ch = to_ether_snf_chan(dev);
+
+ return ch->max_cmds;
+}
+
+static int esnf_ts_enabled(struct snf_chan *dev)
+{
+ struct ether_snf_chan *ch = to_ether_snf_chan(dev);
+
+ return ch->evt.ts_valid;
+}
+
+/* Gray decoder */
+static u32 gray_decode(u32 gray)
+{
+ gray ^= (gray >> 16);
+ gray ^= (gray >> 8);
+ gray ^= (gray >> 4);
+ gray ^= (gray >> 2);
+ gray ^= (gray >> 1);
+ return gray;
+}
+
+/* Read a block from the data FIFO */
+static void read_fifo_data(struct ether_snf_chan *ch)
+{
+ int i;
+ u32 val, *ptr;
+ int ts = ch->evt.ts_valid;
+ int dw = ch->fifo_blk_words;
+ int bytes_before = ch->nfb_before;
+ int bytes_after = ch->nfb_after;
+
+ ptr = (u32 *)ch->evt.data;
+ for (i = 0; i < dw; i++) {
+ val = ioread32(ch->reg_fifo);
+ if (bytes_before > 0) {
+ /* Bytes before the timestamp */
+ *ptr++ = cpu_to_be32(val);
+ bytes_before -= 4;
+ } else if (ts) {
+ ch->raw_tstamp = gray_decode(val);
+ /* First timestamp since the channel was started.
+ * Initialise the timecounter
+ */
+ if (!ch->evt.ts)
+ timecounter_init(
+ &ch->tc,
+ &ch->cc,
+ ktime_to_ns(ktime_get_real()));
+ ch->evt.ts = timecounter_read(&ch->tc);
+ ts = 0;
+ } else if (bytes_after > 0) {
+ /* Bytes after the timestamp */
+ *ptr++ = cpu_to_be32(val);
+ bytes_after -= 4;
+ }
+ }
+}
+
+/* Read method for the timestamp cycle counter
+ * Returns the last received timestamp value
+ */
+static cycle_t esnf_cyclecounter_read(const struct cyclecounter *cc)
+{
+ struct ether_snf_chan *ch = container_of(cc, struct ether_snf_chan, cc);
+
+ return ch->raw_tstamp;
+}
+
+/* Taken from clocks_calc_mult_shift() in kernel/time/clocksource.c
+ * Unfortunately that function is not exported by the kernel
+ */
+static void esnf_clocks_calc_mult_shift(
+ u32 *mult,
+ u32 *shift,
+ u32 from,
+ u32 to,
+ u32 maxsec)
+{
+ u64 tmp;
+ u32 sft, sftacc = 32;
+
+ /* Calculate the shift factor which is limiting the conversion
+ * range:
+ */
+ tmp = ((u64)maxsec * from) >> 32;
+ while (tmp) {
+ tmp >>= 1;
+ sftacc--;
+ }
+
+ /* Find the conversion shift/mult pair which has the best
+ * accuracy and fits the maxsec conversion range:
+ */
+ for (sft = 32; sft > 0; sft--) {
+ tmp = (u64)to << sft;
+ tmp += from / 2;
+ do_div(tmp, from);
+ if ((tmp >> sftacc) == 0)
+ break;
+ }
+ *mult = tmp;
+ *shift = sft;
+}
+
+/* Follows the logic of __clocksource_updatefreq_scale() in
+ * in kernel/time/clocksource.c
+ */
+static void calc_mult_shift(u32 *mult, u32 *shift, cycle_t mask, u32 freq)
+{
+ u64 sec;
+
+ sec = mask - (mask >> 3);
+ do_div(sec, freq);
+ if (!sec)
+ sec = 1;
+ else if (sec > 600 && mask > UINT_MAX)
+ sec = 600;
+ esnf_clocks_calc_mult_shift(mult, shift, freq, NSEC_PER_SEC, sec);
+}
+
+/* Initialises a sniffer channel */
+int channel_init(
+ struct ether_snf_chan *ch,
+ struct platform_device *pdev,
+ void *regs,
+ int fifo_blk_words,
+ u32 tstamp_hz,
+ u32 tstamp_bits,
+ int tx)
+{
+ struct resource *res;
+ u32 *ptr;
+ int i;
+
+ ch->regs = regs;
+ ch->dev = &pdev->dev;
+ ch->data_irq_bit = tx ? TX_DATA_IRQ_BIT : RX_DATA_IRQ_BIT;
+ ch->full_irq_bit = tx ? TX_FULL_IRQ_BIT : RX_FULL_IRQ_BIT;
+ ch->reg_enable = ch->regs +
+ (tx ? TX_SNIFFER_ENABLE : RX_SNIFFER_ENABLE);
+ ch->reg_fifo = ch->regs + (tx ? TX_FIFO_DAT : RX_FIFO_DAT);
+
+ /* Retrieve and remap the address space for the command memory */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ tx ? "tx-ram" : "rx-ram");
+ if (!res)
+ return -ENOSYS;
+ ch->cmd_ram = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ch->cmd_ram))
+ return PTR_ERR(ch->cmd_ram);
+
+ /* It is 2 bytes/command, hence divide by 2 */
+ ch->max_cmds = resource_size(res) / 2;
+
+ /* Initialise the command string RAM */
+ for (i = 0, ptr = ch->cmd_ram; i < resource_size(res); i += 4)
+ iowrite32((CMD_DONTCARE << 24) | (CMD_DONTCARE << 8),
+ ptr++);
+
+ ch->fifo_blk_words = fifo_blk_words;
+ ch->started = 0;
+
+ /* Initialise the timestamp cycle counter */
+ ch->cc.read = esnf_cyclecounter_read;
+ ch->cc.mask = CLOCKSOURCE_MASK(tstamp_bits);
+ calc_mult_shift(&ch->cc.mult, &ch->cc.shift, ch->cc.mask, tstamp_hz);
+
+ /* Register the channel methods */
+ ch->chan.start = esnf_start;
+ ch->chan.stop = esnf_stop;
+ ch->chan.set_pattern = esnf_set_pattern;
+ ch->chan.max_ptn_entries = esnf_max_ptn_entries;
+ ch->chan.ts_enabled = esnf_ts_enabled;
+
+ strncpy(ch->name, tx ? "TX" : "RX", MAX_CHAN_NAME_SIZE - 1);
+
+ dev_dbg(ch->dev, "%s: channel initialized\n", ch->name);
+
+ return 0;
+}
+
+/* Registers the channel with the sniffer core module */
+int channel_register(struct ether_snf_chan *ch, const char *name)
+{
+ int ret;
+
+ ret = snf_channel_add(&ch->chan, name);
+ if (ret < 0)
+ return ret;
+
+ dev_info(ch->dev, "%s channel added\n", ch->name);
+ return 0;
+}
+
+/* Unregisters the channel */
+int channel_unregister(struct ether_snf_chan *ch)
+{
+ int ret;
+
+ ch->chan.stop(&ch->chan);
+ ret = snf_channel_remove(&ch->chan);
+ if (!ret)
+ dev_info(ch->dev, "%s channel removed\n", ch->name);
+ return ret;
+}
+
+/* Process match event data */
+void channel_data_available(struct ether_snf_chan *ch)
+{
+ int ret;
+
+ dev_dbg(ch->dev, "%s match event\n", ch->name);
+
+ read_fifo_data(ch);
+ ret = snf_channel_notify_match(&ch->chan, &ch->evt);
+ if (ret < 0)
+ dev_err(ch->dev, "%s: event notification failed\n", ch->name);
+}
+
+/* Suspend opertion */
+int channel_suspend(struct ether_snf_chan *ch)
+{
+ return snf_channel_suspend(&ch->chan);
+}
+
+/* Resume operation */
+int channel_resume(struct ether_snf_chan *ch)
+{
+ return snf_channel_resume(&ch->chan);
+}
+
diff --git a/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/channel.h b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/channel.h
new file mode 100644
index 0000000..75b942e
--- /dev/null
+++ b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/channel.h
@@ -0,0 +1,80 @@
+/*
+ * Ethernet Mii packet sniffer driver
+ * - channel interface
+ *
+ * Copyright (C) 2015 Linn Products Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Written by:
+ * Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+ */
+#ifndef _ETHER_SNIFFER_CHANNEL_H_
+#define _ETHER_SNIFFER_CHANNEL_H_
+
+#include <linux/platform_device.h>
+#include <linux/timecounter.h>
+#include "../../core/snf_core.h"
+
+#define MAX_CHAN_NAME_SIZE 5
+
+struct ether_snf_chan {
+ /* Sniffer core structure */
+ struct snf_chan chan;
+ /* Pointer to device struct */
+ struct device *dev;
+ /* Registers */
+ u8 __iomem *regs;
+ /* Command string memory */
+ u32 __iomem *cmd_ram;
+ /* Bit number for the data IRQ */
+ int data_irq_bit;
+ /* Bit number for the FIFO full IRQ */
+ int full_irq_bit;
+ /* Channel enable register */
+ u8 __iomem *reg_enable;
+ /* Data FIFO register */
+ u8 __iomem *reg_fifo;
+ /* Max number of commands in the string */
+ int max_cmds;
+ /* Max matching bytes that can fit in a FIFO block */
+ int fifo_blk_words;
+ /* Number of bytes in the FIFO before the timestamp */
+ int nfb_before;
+ /* Number of bytes in the FIFO after the timestamp */
+ int nfb_after;
+ /* Channel active flag */
+ int started;
+ /* Last raw timestamp from the FIFO */
+ u32 raw_tstamp;
+ /* Channel name (only used by debug messages) */
+ char name[MAX_CHAN_NAME_SIZE];
+ /* Struct to hold data from a packet match */
+ struct snf_match_evt evt;
+ /* Cycle and time counters for tstamp handling */
+ struct cyclecounter cc;
+ struct timecounter tc;
+};
+
+int channel_init(
+ struct ether_snf_chan *ch,
+ struct platform_device *pdev,
+ void *regs,
+ int fifo_blk_words,
+ u32 tstamp_hz,
+ u32 tstamp_bits,
+ int tx);
+int channel_register(struct ether_snf_chan *ch, const char *name);
+int channel_unregister(struct ether_snf_chan *ch);
+void channel_data_available(struct ether_snf_chan *ch);
+int channel_suspend(struct ether_snf_chan *ch);
+int channel_resume(struct ether_snf_chan *ch);
+
+#endif
diff --git a/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/hw.h b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/hw.h
new file mode 100644
index 0000000..edb1093
--- /dev/null
+++ b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/hw.h
@@ -0,0 +1,46 @@
+/*
+ * Ethernet Mii packet sniffer driver
+ * - register map
+ *
+ * Copyright (C) 2015 Linn Products Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Written by:
+ * Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+ */
+#ifndef _ETHER_SNIFFER_HW_H_
+#define _ETHER_SNIFFER_HW_H_
+
+#include <linux/bitops.h>
+
+/* Registers */
+#define INTERRUPT_ENABLE 0x00
+#define SET_INTERRUPT_ENABLE 0x04
+#define CLEAR_INTERRUPT_ENABLE 0x08
+#define INTERRUPT_STATUS 0x0c
+#define TX_FIFO_DAT 0x10
+#define RX_FIFO_DAT 0x14
+#define TX_FIFO_OCC 0x18
+#define RX_FIFO_OCC 0x1c
+#define TX_SNIFFER_ENABLE 0x20
+#define RX_SNIFFER_ENABLE 0x24
+
+/* IRQ register bits */
+#define TX_DATA_IRQ_BIT BIT(0)
+#define RX_DATA_IRQ_BIT BIT(1)
+#define TX_FULL_IRQ_BIT BIT(2)
+#define RX_FULL_IRQ_BIT BIT(3)
+
+/* Enable register bits */
+#define ENABLE_BIT BIT(0)
+
+#endif
+
diff --git a/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/platform.c b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/platform.c
new file mode 100644
index 0000000..4da039a
--- /dev/null
+++ b/drivers/net/ethernet/linn/pkt-sniffer/backends/ether/platform.c
@@ -0,0 +1,318 @@
+/*
+ * This module is the driver for the Linn Ethernet Mii packet sniffer
+ * H/W module. It allows incoming and outoing Ethernet packets to be
+ * parsed, matched against a user-defined pattern and timestamped.
+ * It sits between an Ethernet MAC and PHY and is completely passive
+ * with respect to Ethernet frames.
+ *
+ * Packet filtering is driven by a user-supplied command string
+ * which consists of a series of interleaved command and data bytes.
+ * ie. the command string has the following format:
+ * --------------------------------
+ * | CMD | DATA | CMD | DATA | ....
+ * --------------------------------
+ * The following commands are supported:
+ * 0 - Don't care
+ * 1 - Match: packet data must match command string byte
+ * 2 - Copy: packet data will be copied to FIFO
+ * 3 - Match/Stamp: if packet data matches string byte, a timestamp
+ * is copied into the FIFO
+ * 4 - Copy/Done: packet data will be copied into the FIFO.
+ * This command terminates the command string.
+ * Example:
+ * 0x00, 0x00, 0x01, 0xDE, 0x03, 0x20, 0x04, 0x00
+ * The above string will match Ethernet packets where the 2nd and 3rd
+ * bytes of the destination MAC address are 0xDE and 0x22.
+ * For each matched packet a timestamp and the 4th byte of the destination
+ * MAC will be copied to the FIFO
+ *
+ * - An IRQ is triggered for every packet match event
+ * - The modules includes a H/W block based FIFO where matched packet data
+ * and timestamp values are placed
+ * - The FIFO is accessed through a single register
+ * - An IRQ is also generated if all FIFO blocks are filled.
+ * - Timestamps are Gray encoded
+ *
+ * Copyright (C) 2015 Linn Products Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Written by:
+ * Stathis Voukelatos <stathis.voukelatos@linn.co.uk>
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include "../../core/snf_core.h"
+#include "hw.h"
+#include "channel.h"
+
+/* Names for the TX and RX channel net interfaces */
+static const char tx_channel_name[] = "snfethtx%d";
+static const char rx_channel_name[] = "snfethrx%d";
+
+struct ether_snf {
+ u8 __iomem *regs;
+ int irq;
+ struct clk *sys_clk;
+ struct clk *ts_clk;
+ struct ether_snf_chan txc;
+ struct ether_snf_chan rxc;
+};
+
+/* Interrupt handler */
+static irqreturn_t esnf_interrupt(int irq, void *dev_id)
+{
+ struct platform_device *pdev = (struct platform_device *)dev_id;
+ struct ether_snf *esnf = (struct ether_snf *)platform_get_drvdata(pdev);
+ u32 irq_status;
+
+ if (unlikely(esnf->irq != irq))
+ return IRQ_NONE;
+
+ irq_status = ioread32(esnf->regs + INTERRUPT_STATUS) &
+ ioread32(esnf->regs + INTERRUPT_ENABLE);
+
+ dev_dbg(&pdev->dev, "irq: 0x%08x\n", irq_status);
+
+ /* TX FIFO full */
+ if (unlikely(irq_status & TX_FULL_IRQ_BIT))
+ dev_notice(&pdev->dev, "TX FIFO full\n");
+
+ /* RX FIFO full */
+ if (unlikely(irq_status & RX_FULL_IRQ_BIT))
+ dev_notice(&pdev->dev, "RX FIFO full\n");
+
+ /* TX match data available */
+ if (irq_status & TX_DATA_IRQ_BIT) {
+ dev_dbg(&pdev->dev, "TX data\n");
+ channel_data_available(&esnf->txc);
+ }
+
+ /* RX match data available */
+ if (irq_status & RX_DATA_IRQ_BIT) {
+ dev_dbg(&pdev->dev, "RX data\n");
+ channel_data_available(&esnf->rxc);
+ }
+
+ /* Clear interrupts */
+ iowrite32(irq_status, esnf->regs + INTERRUPT_STATUS);
+
+ return IRQ_HANDLED;
+}
+
+/* Called when the packet sniffer device is bound with the driver */
+static int esnf_driver_probe(struct platform_device *pdev)
+{
+ struct ether_snf *esnf;
+ struct resource *res;
+ int ret, irq;
+ u32 fifo_blk_words, ts_hz, ts_bits;
+ void __iomem *regs;
+ struct device_node *ofn = pdev->dev.of_node;
+
+ /* Allocate the device data structure */
+ esnf = devm_kzalloc(&pdev->dev, sizeof(*esnf), GFP_KERNEL);
+ if (!esnf)
+ return -ENOMEM;
+
+ /* Retrieve and remap register memory space */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ esnf->regs = regs;
+
+ if (!ofn)
+ return -ENODEV;
+
+ /* Read requirede properties from the device tree node */
+ ret = of_property_read_u32(
+ ofn,
+ "fifo-block-words",
+ &fifo_blk_words);
+ if (ret < 0)
+ return ret;
+
+ if (((fifo_blk_words - 1) * 4) > MAX_MATCH_BYTES) {
+ dev_err(&pdev->dev,
+ "Invalid FIFO block size entry in device tree\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(
+ ofn,
+ "tstamp-bits",
+ &ts_bits);
+ if (ret < 0)
+ return ret;
+
+ /* Enable peripheral bus and timstamp clocks */
+ esnf->sys_clk = devm_clk_get(&pdev->dev, "sys");
+ if (IS_ERR(esnf->sys_clk)) {
+ ret = PTR_ERR(esnf->sys_clk);
+ return ret;
+ }
+ ret = clk_prepare_enable(esnf->sys_clk);
+ if (ret < 0)
+ return ret;
+
+ esnf->ts_clk = devm_clk_get(&pdev->dev, "tstamp");
+ if (IS_ERR(esnf->ts_clk)) {
+ ret = PTR_ERR(esnf->ts_clk);
+ goto fail1;
+ }
+ ret = clk_prepare_enable(esnf->ts_clk);
+ if (ret < 0)
+ goto fail1;
+
+ /* Initialise the TX and RX channels */
+ ts_hz = clk_get_rate(esnf->ts_clk);
+ ret = channel_init(
+ &esnf->txc,
+ pdev,
+ regs,
+ fifo_blk_words,
+ ts_hz,
+ ts_bits,
+ 1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to init TX channel (%d)\n", ret);
+ goto fail2;
+ }
+ ret = channel_init(
+ &esnf->rxc,
+ pdev,
+ regs,
+ fifo_blk_words,
+ ts_hz,
+ ts_bits,
+ 0);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to init RX channel (%d)\n", ret);
+ goto fail2;
+ }
+
+ /* Register the channels with the sniffer core module */
+ ret = channel_register(&esnf->txc, tx_channel_name);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register TX chan (%d)\n", ret);
+ goto fail2;
+ }
+ ret = channel_register(&esnf->rxc, rx_channel_name);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register RX chan (%d)\n", ret);
+ goto fail3;
+ }
+
+ platform_set_drvdata(pdev, esnf);
+
+ /* Register the interrupt handler */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ goto fail4;
+ esnf->irq = irq;
+ ret = devm_request_irq(
+ &pdev->dev,
+ irq,
+ esnf_interrupt,
+ 0,
+ KBUILD_MODNAME,
+ pdev);
+ if (ret < 0)
+ goto fail4;
+
+ return 0;
+
+fail4:
+ channel_unregister(&esnf->rxc);
+fail3:
+ channel_unregister(&esnf->txc);
+fail2:
+ clk_disable_unprepare(esnf->ts_clk);
+fail1:
+ clk_disable_unprepare(esnf->sys_clk);
+ return ret;
+}
+
+/* Called when the packet sniffer device unregisters with the driver */
+static int esnf_driver_remove(struct platform_device *pdev)
+{
+ struct ether_snf *esnf = (struct ether_snf *)platform_get_drvdata(pdev);
+ int ret;
+
+ ret = channel_unregister(&esnf->txc);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to unregister TX chan (%d)\n", ret);
+ return ret;
+ }
+ ret = channel_unregister(&esnf->rxc);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to unregister RX chan (%d)\n", ret);
+ return ret;
+ }
+ clk_disable_unprepare(esnf->ts_clk);
+ clk_disable_unprepare(esnf->sys_clk);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/* Driver suspend method */
+static int esnf_driver_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ether_snf *esnf = (struct ether_snf *)platform_get_drvdata(pdev);
+
+ channel_suspend(&esnf->txc);
+ channel_suspend(&esnf->rxc);
+ return 0;
+}
+
+/* Driver resume method */
+static int esnf_driver_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ether_snf *esnf = (struct ether_snf *)platform_get_drvdata(pdev);
+
+ channel_resume(&esnf->txc);
+ channel_resume(&esnf->rxc);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(esnf_pm_ops, esnf_driver_suspend, esnf_driver_resume);
+#endif
+
+static const struct of_device_id esnf_of_match_table[] = {
+ { .compatible = "linn,eth-packet-sniffer" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, esnf_of_match_table);
+
+static struct platform_driver esnf_platform_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+#ifdef CONFIG_PM
+ .pm = &esnf_pm_ops,
+#endif
+ .of_match_table = esnf_of_match_table,
+ },
+ .probe = esnf_driver_probe,
+ .remove = esnf_driver_remove,
+};
+
+module_platform_driver(esnf_platform_driver);
+
+MODULE_DESCRIPTION("Linn Ethernet Packet Sniffer");
+MODULE_AUTHOR("Linn Products Ltd");
+MODULE_LICENSE("GPL v2");
+
--
1.9.1
^ permalink raw reply [flat|nested] 15+ messages in thread