From patchwork Fri Jan 23 10:07:01 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stathis Voukelatos X-Patchwork-Id: 432119 Return-Path: X-Original-To: incoming-dt@patchwork.ozlabs.org Delivered-To: patchwork-incoming-dt@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 68EB414012C for ; Fri, 23 Jan 2015 21:07:59 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752874AbbAWKHe (ORCPT ); Fri, 23 Jan 2015 05:07:34 -0500 Received: from mail-wg0-f45.google.com ([74.125.82.45]:64979 "EHLO mail-wg0-f45.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752197AbbAWKHa (ORCPT ); Fri, 23 Jan 2015 05:07:30 -0500 Received: by mail-wg0-f45.google.com with SMTP id x12so6553828wgg.4; Fri, 23 Jan 2015 02:07:28 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id; bh=0zsjJH8ihPxoIxYGQZqZ5V8dD6SiPV/f6wFO6qydu0M=; b=gy0xfuKt40TsobhJatq3DHaPq0D4Hdnjoc6QGk4tZ6frflnaL6McJhGQUJQKuJA0aH FbTa+q1jrGhxMhC0DVdSAVcg+Mrk5Je0hmnHOgXTBZc8IbxkiYD1z1nEjP/5eHB+j3vT bbpKAcw1F/ShvUYNFyzsIqpzyrwrBO2W+UZQTjTWvHuHxCTCODOLAhfmPmd0BNPpWDYa ybEPwnsWVjVoevM9cxEMbR0hK1kBKv2qfS4b2fFZpjSra6SKu9PUkGz5jsfyryioFkW0 Os3Kcq2P14r0usRZR0htKZVFh/3TyLx3DNywihN21+zER0OGoXm6taImdxkOaMAI2SeN 2PSg== X-Received: by 10.194.248.201 with SMTP id yo9mr11862309wjc.75.1422007648828; Fri, 23 Jan 2015 02:07:28 -0800 (PST) Received: from ubuntuvm.linn.co.uk (mail.linn.co.uk. [195.59.102.251]) by mx.google.com with ESMTPSA id dt10sm1142014wib.23.2015.01.23.02.07.28 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 23 Jan 2015 02:07:28 -0800 (PST) From: Stathis Voukelatos X-Google-Original-From: Stathis Voukelatos To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org Cc: Stathis Voukelatos , abrestic@chromium.org Subject: [PATCH] net: Linn Ethernet Packet Sniffer driver Date: Fri, 23 Jan 2015 10:07:01 +0000 Message-Id: <1422007621-13567-1-git-send-email-stathis.voukelatos@linn.co.uk> X-Mailer: git-send-email 1.9.1 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org This patch adds support the Ethernet Packet Sniffer H/W module developed by Linn Products Ltd and found in the IMG Pistachio SoC. The module allows Ethernet packets to be parsed, matched against a user-defined pattern and timestamped. It sits between a 100M Ethernet MAC and PHY and is completely passive with respect to Ethernet frames. Matched packet bytes and timestamp values are returned through a FIFO. Timestamps are provided to the module through an externally generated Gray-encoded counter. The command pattern for packet matching is stored in module RAM and consists of a sequence of 16-bit entries. Each entry includes an 8-bit command code and and 8-bit data value. Valid command codes are: 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. The driver consists of two modules: - Core: it provides an API to user space using the Generic Netlink framework. Specific backend implementations, like the Ethernet Packet Sniffer, register one or more channels with the Core. For each channel a Genl family is created. User space can access a channel by sending Genl messages to the Genl family associated with the channel. Packet matching events are multicast. - Ethernet Packet Sniffer backend: provides the driver for the Linn Ethernet Packet Sniffer H/W modules. The split between a core and backend modules allows software-only implementations to be added for platforms where no H/W support is available. Based on 3.19-rc5 Signed-off-by: Stathis Voukelatos --- .../bindings/net/linn-ether-packet-sniffer.txt | 27 ++ .../devicetree/bindings/vendor-prefixes.txt | 1 + MAINTAINERS | 7 + drivers/net/Kconfig | 2 + drivers/net/Makefile | 1 + drivers/net/pkt-sniffer/Kconfig | 23 ++ drivers/net/pkt-sniffer/Makefile | 8 + drivers/net/pkt-sniffer/backends/ether/channel.c | 366 ++++++++++++++++++ drivers/net/pkt-sniffer/backends/ether/channel.h | 76 ++++ drivers/net/pkt-sniffer/backends/ether/hw.h | 46 +++ drivers/net/pkt-sniffer/backends/ether/platform.c | 231 +++++++++++ drivers/net/pkt-sniffer/core/dev_table.c | 124 ++++++ drivers/net/pkt-sniffer/core/module.c | 37 ++ drivers/net/pkt-sniffer/core/nl.c | 427 +++++++++++++++++++++ drivers/net/pkt-sniffer/core/nl.h | 34 ++ drivers/net/pkt-sniffer/core/snf_core.h | 64 +++ include/linux/pkt_sniffer.h | 89 +++++ 17 files changed, 1563 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/linn-ether-packet-sniffer.txt create mode 100644 drivers/net/pkt-sniffer/Kconfig create mode 100644 drivers/net/pkt-sniffer/Makefile create mode 100644 drivers/net/pkt-sniffer/backends/ether/channel.c create mode 100644 drivers/net/pkt-sniffer/backends/ether/channel.h create mode 100644 drivers/net/pkt-sniffer/backends/ether/hw.h create mode 100644 drivers/net/pkt-sniffer/backends/ether/platform.c create mode 100644 drivers/net/pkt-sniffer/core/dev_table.c create mode 100644 drivers/net/pkt-sniffer/core/module.c create mode 100644 drivers/net/pkt-sniffer/core/nl.c create mode 100644 drivers/net/pkt-sniffer/core/nl.h create mode 100644 drivers/net/pkt-sniffer/core/snf_core.h create mode 100644 include/linux/pkt_sniffer.h diff --git a/Documentation/devicetree/bindings/net/linn-ether-packet-sniffer.txt b/Documentation/devicetree/bindings/net/linn-ether-packet-sniffer.txt new file mode 100644 index 0000000..6b6e105 --- /dev/null +++ b/Documentation/devicetree/bindings/net/linn-ether-packet-sniffer.txt @@ -0,0 +1,27 @@ +* Linn Products Ethernet Packet Sniffer + +Required properties: +- compatible : must be "linn,eth-sniffer" +- reg : physical addresses and sizes of registers. Must contain 3 entries: + first entry: registers memory space + second entry: TX command memory + third entry: RX command memory +- reg-names : must contain the following 3 entries: + "regs", "tx-ram", "rx-ram" +- interrupts : sniffer interrupt specifier +- clocks : specify the system clock for the peripheral +- clock-names : must contain the "sys" entry +- fifo-block-words : number of words in one data FIFO entry + +Example: + +sniffer@1814a000 { + compatible = "linn,eth-sniffer"; + reg = <0x1814a000 0x100>, <0x1814a400 0x400>, <0x1814a800 0x400>; + reg-names = "regs", "tx-ram", "rx-ram"; + interrupts = ; + interrupt-names = "eth-sniffer-irq"; + clocks = <&system_clk>; + clock-names = "sys"; + fifo-block-words = <4>; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index b1df0ad..2c96f35 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -90,6 +90,7 @@ lacie LaCie lantiq Lantiq Semiconductor lenovo Lenovo Group Ltd. lg LG Corporation +linn Linn Products Ltd. linux Linux-specific binding lsi LSI Corp. (LSI Logic) lltc Linear Technology Corporation diff --git a/MAINTAINERS b/MAINTAINERS index 2fa3853..7dbc6e7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5708,6 +5708,13 @@ M: Sasha Levin S: Maintained F: tools/lib/lockdep/ +LINN PACKET SNIFFER DRIVER +M: Stathis Voukelatos +S: Maintained +F: include/linux/pkt_sniffer.h +F: drivers/net/pkt-sniffer/ +F: Documentation/devicetree/bindings/net/linn-ether-packet-sniffer.txt + LINUX FOR IBM pSERIES (RS/6000) M: Paul Mackerras W: http://www.ibm.com/linux/ltc/projects/ppc diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index d6607ee..219c786 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -380,4 +380,6 @@ config VMXNET3 source "drivers/net/hyperv/Kconfig" +source "drivers/net/pkt-sniffer/Kconfig" + endif # NETDEVICES diff --git a/drivers/net/Makefile b/drivers/net/Makefile index e25fdd7..441111b 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -66,3 +66,4 @@ obj-$(CONFIG_USB_NET_DRIVERS) += usb/ obj-$(CONFIG_HYPERV_NET) += hyperv/ obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o +obj-$(CONFIG_PKT_SNIFFER) += pkt-sniffer/ diff --git a/drivers/net/pkt-sniffer/Kconfig b/drivers/net/pkt-sniffer/Kconfig new file mode 100644 index 0000000..26b4f98 --- /dev/null +++ b/drivers/net/pkt-sniffer/Kconfig @@ -0,0 +1,23 @@ +menuconfig PKT_SNIFFER + tristate "Linn packet sniffer support" + ---help--- + Say Y to add support for Linn packet sniffer drivers. + + The core driver can also be built as a module. If so, the module + will be called snf_core. + +if PKT_SNIFFER + +config PKT_SNIFFER_ETHER + tristate "Ethernet packet sniffer" + depends on MIPS + default n + help + Say Y here if you want to use the Linn Ethernet packet sniffer + module. 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 # PKT_SNIFFER diff --git a/drivers/net/pkt-sniffer/Makefile b/drivers/net/pkt-sniffer/Makefile new file mode 100644 index 0000000..07e7339 --- /dev/null +++ b/drivers/net/pkt-sniffer/Makefile @@ -0,0 +1,8 @@ +snf_core-y += core/nl.o +snf_core-y += core/dev_table.o +snf_core-y += core/module.o +obj-$(CONFIG_PKT_SNIFFER) += snf_core.o + +snf_ether-y += backends/ether/platform.o +snf_ether-y += backends/ether/channel.o +obj-$(CONFIG_PKT_SNIFFER_ETHER) += snf_ether.o diff --git a/drivers/net/pkt-sniffer/backends/ether/channel.c b/drivers/net/pkt-sniffer/backends/ether/channel.c new file mode 100644 index 0000000..d483b58 --- /dev/null +++ b/drivers/net/pkt-sniffer/backends/ether/channel.c @@ -0,0 +1,366 @@ +/* + * 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 + */ +#include +#include +#include +#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) + +static int esnf_start(struct snf_chan *dev); +static int esnf_stop(struct snf_chan *dev); +static int esnf_set_pattern(struct snf_chan *dev, const u8 *pattern, int count); +static int esnf_num_recs_avail(struct snf_chan *dev); +static int esnf_max_ptn_entries(struct snf_chan *dev); +static int esnf_max_match_bytes(struct snf_chan *dev); +static int validate_pattern( + struct ether_snf_chan *ch, + const u8 *buf, + int count); +static void read_fifo_data(struct ether_snf_chan *ch); +static u32 gray_decode(u32 gray); + +/* Initialises a sniffer channel */ +int channel_init( + struct ether_snf_chan *ch, + struct platform_device *pdev, + void *regs, + int fifo_blk_words, + 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_occ = ch->regs + (tx ? TX_FIFO_OCC : RX_FIFO_OCC); + 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 pattern RAM */ + for (i = 0, ptr = ch->cmd_ram; i < resource_size(res); i += 4) + iowrite32((PTN_CMD_DONTCARE << 24) | (PTN_CMD_DONTCARE << 8), + ptr++); + + ch->fifo_blk_words = fifo_blk_words; + ch->started = 0; + + /* Register the channel methods */ + ch->chan.start = esnf_start; + ch->chan.stop = esnf_stop; + ch->chan.set_pattern = esnf_set_pattern; + ch->chan.num_recs_avail = esnf_num_recs_avail; + ch->chan.max_ptn_entries = esnf_max_ptn_entries; + ch->chan.max_match_bytes = esnf_max_match_bytes; + + 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 id; + + id = snf_channel_add(&ch->chan, name); + if (id < 0) + return id; + + ch->id = id; + 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->id); + 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); +} + +/* 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) { + /* 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 channel started\n", ch->name); + } else { + dev_dbg(ch->dev, "%s channel 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 channel stopped\n", ch->name); + } else { + dev_dbg(ch->dev, "%s channel 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; + + if (ch->started) { + dev_err(ch->dev, + "cannot apply cmd pattern. %s channel is active\n", + ch->name); + return -EBUSY; + } + + if (!validate_pattern(ch, pattern, count)) { + dev_err(ch->dev, + "invalid cmd pattern for %s channel\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 the number of pending match events that are + * available to retrieve + */ +static int esnf_num_recs_avail(struct snf_chan *dev) +{ + struct ether_snf_chan *ch = to_ether_snf_chan(dev); + + return ioread32(ch->reg_occ); +} + +/* 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; +} + +/* Returns max number of bytes that can be returned by a match */ +static int esnf_max_match_bytes(struct snf_chan *dev) +{ + struct ether_snf_chan *ch = to_ether_snf_chan(dev); + + /* Subtract the word that may be used for the timestamp */ + return (ch->fifo_blk_words - 1) * 4; +} + +/* Checks if the supplied command string is compatible with the + * capabilities of the H/W + */ +static int 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 0; + + /* Iterate through the commands in the string */ + for (i = 0, complete = 0; (i < count) && !complete; i++) { + u8 cmd = buf[2*i]; + + switch (cmd) { + case PTN_CMD_DONTCARE: + case PTN_CMD_MATCH: + break; + + case PTN_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 0; + /* Signal that a timestamp will be present */ + ts = 1; + break; + + case PTN_CMD_COPY: + /* Increment count of bytes that will be returned */ + if (ts) + copy_after++; + else + copy_before++; + break; + + case PTN_CMD_COPYDONE: + /* Increment count of bytes that will be returned + * This command terminates the string + */ + if (ts) + copy_after++; + else + copy_before++; + complete = 1; + break; + + default: + return 0; + } + } + + /* 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; + if ((copy_before + copy_after) > max_copy_bytes) + return 0; + ch->ts_present = ts; + ch->nfb_before = copy_before; + ch->nfb_after = copy_after; + return 1; + } else { + return 0; + } +} + +/* 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->ts_present; + 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) { + /* Timestamp is Gray encoded */ + ch->evt.ts = (u64)gray_decode(val); + ts = 0; + } else if (bytes_after > 0) { + /* Bytes after the timestamp */ + *ptr++ = cpu_to_be32(val); + bytes_after -= 4; + } + } + + ch->evt.ts_valid = ch->ts_present; + ch->evt.len = ch->nfb_before + ch->nfb_after; +} + +/* 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; +} + diff --git a/drivers/net/pkt-sniffer/backends/ether/channel.h b/drivers/net/pkt-sniffer/backends/ether/channel.h new file mode 100644 index 0000000..4f00b33 --- /dev/null +++ b/drivers/net/pkt-sniffer/backends/ether/channel.h @@ -0,0 +1,76 @@ +/* + * 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 + */ +#ifndef _ETHER_SNIFFER_CHANNEL_H_ +#define _ETHER_SNIFFER_CHANNEL_H_ + +#include +#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; + /* FIFO occupancy register */ + u8 __iomem *reg_occ; + /* 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; + /* Timestamp present flag */ + int ts_present; + /* ID assigned to channel by the sniffer core */ + int id; + /* Channel active flag */ + int started; + /* 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; +}; + +int channel_init( + struct ether_snf_chan *ch, + struct platform_device *pdev, + void *regs, + int fifo_blk_words, + 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); + +#endif diff --git a/drivers/net/pkt-sniffer/backends/ether/hw.h b/drivers/net/pkt-sniffer/backends/ether/hw.h new file mode 100644 index 0000000..edb1093 --- /dev/null +++ b/drivers/net/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 + */ +#ifndef _ETHER_SNIFFER_HW_H_ +#define _ETHER_SNIFFER_HW_H_ + +#include + +/* 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/pkt-sniffer/backends/ether/platform.c b/drivers/net/pkt-sniffer/backends/ether/platform.c new file mode 100644 index 0000000..78e7e1c --- /dev/null +++ b/drivers/net/pkt-sniffer/backends/ether/platform.c @@ -0,0 +1,231 @@ +/* + * Ethernet Mii 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 + */ +#include +#include +#include +#include +#include +#include +#include "../../core/snf_core.h" +#include "hw.h" +#include "channel.h" + +static const char esnf_driver_name[] = "eth-sniffer"; + +/* Names for the TX and RX channel. + * User space will used these names to access the channels + * through the generic netlink interface + */ +static const char tx_channel_name[] = "snf_ether_tx"; +static const char rx_channel_name[] = "snf_ether_rx"; + +struct ether_snf { + u8 __iomem *regs; + int irq; + struct clk *sys_clk; + struct ether_snf_chan txc; + struct ether_snf_chan rxc; +}; + +/* Interrupt thread function */ +static irqreturn_t esnf_irq_thread(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"); + + /* RX FIFO full */ + if (unlikely(irq_status & RX_FULL_IRQ_BIT)) + dev_notice(&pdev->dev, "RX FIFO full"); + + /* TX match data available */ + if (irq_status & TX_DATA_IRQ_BIT) { + dev_dbg(&pdev->dev, "TX data"); + channel_data_available(&esnf->txc); + } + + /* RX match data available */ + if (irq_status & RX_DATA_IRQ_BIT) { + dev_dbg(&pdev->dev, "RX data"); + 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; + 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"); + if (!res) + return -ENODEV; + + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + esnf->regs = regs; + + /* Read the FIFO block size from the DT */ + if (!ofn) + return -ENODEV; + + 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; + } + + 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; + + /* Initialise the TX and RX channels */ + ret = channel_init(&esnf->txc, pdev, regs, fifo_blk_words, 1); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to init TX channel (%d)\n", ret); + goto fail1; + } + ret = channel_init(&esnf->rxc, pdev, regs, fifo_blk_words, 0); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to init RX channel (%d)\n", ret); + goto fail1; + } + + /* 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 fail1; + } + ret = channel_register(&esnf->rxc, rx_channel_name); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register RX chan (%d)\n", ret); + goto fail2; + } + + platform_set_drvdata(pdev, esnf); + + /* Register the interrupt handler */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto fail3; + esnf->irq = irq; + ret = devm_request_threaded_irq( + &pdev->dev, + irq, + NULL, + esnf_irq_thread, + IRQF_ONESHOT, + esnf_driver_name, + pdev); + if (ret < 0) + goto fail3; + + return 0; + +fail3: + channel_unregister(&esnf->rxc); +fail2: + channel_unregister(&esnf->txc); +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->sys_clk); + return 0; +} + +static const struct of_device_id esnf_of_match_table[] = { + { .compatible = "linn,eth-sniffer", .data = NULL }, + {}, +}; +MODULE_DEVICE_TABLE(of, esnf_of_match_table); + +static struct platform_driver esnf_platform_driver = { + .driver = { + .name = esnf_driver_name, + .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"); + diff --git a/drivers/net/pkt-sniffer/core/dev_table.c b/drivers/net/pkt-sniffer/core/dev_table.c new file mode 100644 index 0000000..3a07838 --- /dev/null +++ b/drivers/net/pkt-sniffer/core/dev_table.c @@ -0,0 +1,124 @@ +/* + * Packet sniffer core driver: channel management + * + * Copyright (C) 2014 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 + */ +#include +#include +#include "snf_core.h" +#include "nl.h" + +#define MAX_CHANNELS 256 + +static DEFINE_MUTEX(tlock); + +static struct snf_chan *dev_tab[MAX_CHANNELS]; +static unsigned int ref_count[MAX_CHANNELS]; + +static inline int verify_args(int id) +{ + return (id < MAX_CHANNELS); +} + +/* Registers a sniffer channel and returns and id for it */ +int snf_channel_add(struct snf_chan *dev, const char *name) +{ + int i; + int ret = -EEXIST; + + mutex_lock(&tlock); + + for (i = 0; i < MAX_CHANNELS; i++) { + if (!dev_tab[i]) { + /* Initialise the netlink interface for the channel */ + ret = snf_netlink_init(i, dev, name); + if (ret < 0) + goto fail; + + dev_tab[i] = dev; + ref_count[i] = 0; + mutex_unlock(&tlock); + return i; + } + } + +fail: + mutex_unlock(&tlock); + return ret; +} +EXPORT_SYMBOL(snf_channel_add); + +/* Removes a sniffer channel */ +int snf_channel_remove(int id) +{ + int ret = 0; + struct snf_chan *dev; + + if (!verify_args(id)) + return -EINVAL; + + mutex_lock(&tlock); + + dev = dev_tab[id]; + + if (!dev) { + ret = -ENODEV; + goto fail; + } + + if (ref_count[id] > 0) { + ret = -EBUSY; + goto fail; + } + + dev_tab[id] = NULL; + + /* Release netlink API resources */ + snf_netlink_release(dev); + +fail: + mutex_unlock(&tlock); + return ret; +} +EXPORT_SYMBOL(snf_channel_remove); + +/* Returns a pointer to the specified sniffer channel + * and increments its reference counter + */ +struct snf_chan *snf_channel_get(int id) +{ + struct snf_chan *dev; + + if (!verify_args(id)) + return NULL; + + mutex_lock(&tlock); + dev = dev_tab[id]; + if (dev) + ref_count[id]++; + mutex_unlock(&tlock); + + return dev; +} + +/* Decrements the reference counter for the channel */ +void snf_channel_put(int id) +{ + mutex_lock(&tlock); + if (ref_count[id] > 0) + ref_count[id]--; + mutex_unlock(&tlock); +} + diff --git a/drivers/net/pkt-sniffer/core/module.c b/drivers/net/pkt-sniffer/core/module.c new file mode 100644 index 0000000..af6a1aa --- /dev/null +++ b/drivers/net/pkt-sniffer/core/module.c @@ -0,0 +1,37 @@ +/* + * Packet sniffer core driver: + * - backend channel management + * - interface to userland via generic netlink + * + * 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 + */ +#include +#include + +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/pkt-sniffer/core/nl.c b/drivers/net/pkt-sniffer/core/nl.c new file mode 100644 index 0000000..6839147 --- /dev/null +++ b/drivers/net/pkt-sniffer/core/nl.c @@ -0,0 +1,427 @@ +/* + * Packet sniffer core driver: generic netlink 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 + */ +#include +#include +#include +#include +#include +#include +#include "snf_core.h" +#include "nl.h" + +/* Netlink API data for a sniffer channel */ +struct snf_netlink { + /* genl family */ + struct genl_family fml; + /* genl operations */ + struct genl_ops *ops; + /* genl mcast group, where sniffer match + * events will be sent + */ + struct genl_multicast_group grp; +}; + +/* Attribute policies */ +static struct nla_policy snf_policy[SNF_ATTR_MAX + 1] = { + [SNF_ATTR_PATTERN] = { .type = NLA_NESTED }, +}; + +static struct nla_policy snf_ptn_policy[SNF_ATTR_PTN_MAX + 1] = { + [SNF_ATTR_PTN_ENTRY] = { .type = NLA_U16 }, +}; + +/* Generic Netlink family template definition */ +static int pre_doit_func(const struct genl_ops *ops, + struct sk_buff *skb, + struct genl_info *info); + +static void post_doit_func(const struct genl_ops *ops, + struct sk_buff *skb, + struct genl_info *info); + +static struct genl_family snf_family_tmpl = { + .id = GENL_ID_GENERATE, + .hdrsize = 0, + .version = SNF_GNL_VERSION, + .maxattr = SNF_ATTR_MAX, + .pre_doit = pre_doit_func, + .post_doit = post_doit_func +}; + +static int send_reply_uint32( + struct genl_info *info, + int cmd, + int attr, + u32 val); + +/* Generic Netlink operations template definition */ + +static int do_it_start(struct sk_buff *skb, struct genl_info *info); +static int do_it_stop(struct sk_buff *skb, struct genl_info *info); +static int do_it_set_pattern(struct sk_buff *skb, struct genl_info *info); +static int do_it_num_rec_avail(struct sk_buff *skb, struct genl_info *info); +static int do_it_ptn_max_cmds(struct sk_buff *skb, struct genl_info *info); +static int do_it_max_match_bytes(struct sk_buff *skb, struct genl_info *info); + +#define SNF_GENL_OP(_cmd, _func) \ + { \ + .cmd = _cmd, \ + .policy = snf_policy, \ + .doit = _func, \ + .dumpit = NULL, \ + .flags = 0, \ + .internal_flags = 0 \ + } + +#define SNF_CHAN_OPS \ + { \ + SNF_GENL_OP(SNF_CMD_START, do_it_start), \ + SNF_GENL_OP(SNF_CMD_STOP, do_it_stop), \ + SNF_GENL_OP(SNF_CMD_SETPATTERN, do_it_set_pattern), \ + SNF_GENL_OP(SNF_CMD_NUMRECAVAIL, do_it_num_rec_avail), \ + SNF_GENL_OP(SNF_CMD_PTNMAXCMDS, do_it_ptn_max_cmds), \ + SNF_GENL_OP(SNF_CMD_MAXMATCHBYTES, do_it_max_match_bytes) \ + } + +static struct genl_ops snf_ops_tmpl[] = SNF_CHAN_OPS; + +#define NUM_GENL_OPS ARRAY_SIZE(snf_ops_tmpl) + +/* Multicast a netlink event containing data from a sniffer match event. + * Data are included in the netlink message as a nested attribute + * containing the timestamp (optional) and matching packet bytes +*/ +int snf_channel_notify_match(struct snf_chan *dev, struct snf_match_evt *mt) +{ + int i, ret = 0; + struct sk_buff *msg = NULL; + void *msg_hdr, *nest_hdr; + struct snf_netlink *nl = (struct snf_netlink *)dev->cstate; + + if (!nl) { + ret = -EINVAL; + goto fail; + } + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); + if (!msg) { + ret = -ENOMEM; + goto fail; + } + + msg_hdr = genlmsg_put(msg, + 0, + 0, + &nl->fml, + 0, + SNF_CMD_MATCHEVENT); + if (!msg_hdr) { + ret = -EMSGSIZE; + goto fail; + } + + /* Add the nested attribute with the data */ + if ((mt->ts_valid) || (mt->len > 0)) { + nest_hdr = nla_nest_start(msg, SNF_ATTR_MATCHEVENT); + if (!nest_hdr) { + ret = -EMSGSIZE; + goto fail; + } + if (mt->ts_valid) { + ret = nla_put_u64(msg, SNF_ATTR_MATCH_TS, mt->ts); + if (ret < 0) + goto fail; + } + for (i = 0; i < mt->len; i++) { + ret = nla_put_u8(msg, + SNF_ATTR_MATCH_PKTBYTE, + mt->data[i]); + if (ret < 0) + goto fail; + } + nla_nest_end(msg, nest_hdr); + } + + ret = genlmsg_end(msg, msg_hdr); + if (ret < 0) + goto fail; + ret = genlmsg_multicast(&nl->fml, msg, 0, 0, GFP_ATOMIC); + + /* The ESRCH code is returned when there is no socket listening on the + * multicast group, so we do not really treat is as an error + */ + if (ret == -ESRCH) + ret = 0; + return ret; + +fail: + if (msg) + nlmsg_free(msg); + return ret; +} +EXPORT_SYMBOL(snf_channel_notify_match); + +/* Initialise the generic netlink API for a sniffer channel + * Registers family, ops and multicast group for events + */ +int snf_netlink_init(int id, struct snf_chan *dev, const char *name) +{ + int i, ret; + struct snf_netlink *nl = NULL; + + nl = kmalloc(sizeof(*nl), GFP_KERNEL); + if (!nl) { + ret = -ENOMEM; + goto fail1; + } + + nl->fml = snf_family_tmpl; + + /* Set the family name */ + strncpy(nl->fml.name, name, GENL_NAMSIZ-1); + + /* Allocate ops array and copy template data */ + nl->ops = kmalloc(sizeof(snf_ops_tmpl), GFP_KERNEL); + if (!nl->ops) { + ret = -ENOMEM; + goto fail2; + } + memcpy(nl->ops, snf_ops_tmpl, sizeof(snf_ops_tmpl)); + + /* In the internal_flags field we store the id + * of the device the ops belong to + */ + for (i = 0; i < NUM_GENL_OPS; i++) + nl->ops[i].internal_flags = id; + + snprintf(nl->grp.name, GENL_NAMSIZ-1, SNF_EVENT_GRP); + + ret = _genl_register_family_with_ops_grps( + &nl->fml, + nl->ops, + NUM_GENL_OPS, + &nl->grp, + 1); + if (ret < 0) { + pr_err("%s: failed to register family %s (%d)\n", + KBUILD_MODNAME, name, ret); + goto fail3; + } + + pr_info("%s: registered genl family for channel %d: %s\n", + KBUILD_MODNAME, id, name); + dev->cstate = nl; + + return 0; + +fail3: + kfree(nl->ops); +fail2: + kfree(nl); +fail1: + return ret; +} + +/* Shuts down the netlink API for a sniffer channel + * and frees resources + */ +void snf_netlink_release(struct snf_chan *dev) +{ + struct snf_netlink *nl = (struct snf_netlink *)dev->cstate; + /* Unregister family and free ops + * NOTE: this will also unregister the ops and the mcast group + */ + genl_unregister_family(&nl->fml); + pr_info("%s: unregistered genl family: %s\n", + KBUILD_MODNAME, nl->fml.name); + + kfree(nl->ops); + kfree(nl); + dev->cstate = NULL; +} + +/* pre_doit function that retrieves a pointer to the sniffer channel device + * from the instance and channel number stored in the ops internal_flag field + */ +static int pre_doit_func(const struct genl_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + info->user_ptr[0] = snf_channel_get(ops->internal_flags); + return info->user_ptr[0] ? 0 : -ENODEV; +} + +/* post_doit function that releases a sniffer channel device */ +static void post_doit_func(const struct genl_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + snf_channel_put(ops->internal_flags); +} + +/* doit command handlers */ + +/* Start the sniffer channel */ +static int do_it_start(struct sk_buff *skb, struct genl_info *info) +{ + struct snf_chan *dev = (struct snf_chan *)info->user_ptr[0]; + + return dev->start(dev); +} + +/* Stop the sniffer channel */ +static int do_it_stop(struct sk_buff *skb, struct genl_info *info) +{ + int ret; + struct snf_match_evt mt; + struct snf_chan *dev = (struct snf_chan *)info->user_ptr[0]; + + ret = dev->stop(dev); + if (ret < 0) + return ret; + + /* Multicast an empty match event to notify any user-space + * listeners that the sniffer is stopping + */ + memset(&mt, 0, sizeof(mt)); + return snf_channel_notify_match(dev, &mt); +} + +/* Set the command pattern. The string is received in a nested + * attribute containing a sequence of 16-bit valued. + * Each value consists of a command (8 bits) and data (8 bits) +*/ +static int do_it_set_pattern(struct sk_buff *skb, struct genl_info *info) +{ + int ret = 0; + int rem, count; + struct nlattr *nla; + u16 entry; + u8 *buf = NULL; + struct snf_chan *dev = (struct snf_chan *)info->user_ptr[0]; + int max_entries = dev->max_ptn_entries(dev); + + if (!info->attrs[SNF_ATTR_PATTERN]) { + ret = -EINVAL; + goto fail; + } + + ret = nla_validate_nested(info->attrs[SNF_ATTR_PATTERN], + SNF_ATTR_PTN_ENTRY, snf_ptn_policy); + if (ret < 0) + goto fail; + + buf = kmalloc(max_entries*2, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto fail; + } + + /* Iterate over the contents of the nested attribute + * and extract into the buffer + */ + count = 0; + nla_for_each_nested(nla, info->attrs[SNF_ATTR_PATTERN], rem) { + if (count >= max_entries) { + ret = -EINVAL; + goto fail; + } + entry = nla_get_u16(nla); + buf[2*count] = PTNENTRY_CMD(entry); + buf[2*count + 1] = PTNENTRY_DATA(entry); + count++; + } + + ret = dev->set_pattern(dev, buf, count); + +fail: + kfree(buf); + return ret; +} + +/* Number of pending match events */ +static int do_it_num_rec_avail(struct sk_buff *skb, struct genl_info *info) +{ + struct snf_chan *dev = (struct snf_chan *)info->user_ptr[0]; + + return send_reply_uint32( + info, + SNF_CMD_NUMRECAVAIL, + SNF_ATTR_NUMRECAVAIL, + dev->num_recs_avail(dev)); +} + +/* Max number of commands that are supported in the command pattern */ +static int do_it_ptn_max_cmds(struct sk_buff *skb, struct genl_info *info) +{ + struct snf_chan *dev = (struct snf_chan *)info->user_ptr[0]; + + return send_reply_uint32( + info, + SNF_CMD_PTNMAXCMDS, + SNF_ATTR_PTNMAXCMDS, + dev->max_ptn_entries(dev)); +} + +/* Max bytes that can be returned by a packet match event */ +static int do_it_max_match_bytes(struct sk_buff *skb, struct genl_info *info) +{ + struct snf_chan *dev = (struct snf_chan *)info->user_ptr[0]; + + return send_reply_uint32( + info, + SNF_CMD_MAXMATCHBYTES, + SNF_ATTR_MAXMATCHBYTES, + dev->max_match_bytes(dev)); +} + +/* Helper function for sending a reply containing a single u32 attribute */ +static int send_reply_uint32(struct genl_info *info, int cmd, int attr, u32 val) +{ + void *hdr; + int ret = 0; + struct sk_buff *msg; + struct snf_chan *dev = (struct snf_chan *)info->user_ptr[0]; + struct snf_netlink *nl = (struct snf_netlink *)dev->cstate; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) { + ret = -ENOMEM; + goto fail; + } + + hdr = genlmsg_put_reply(msg, info, &nl->fml, 0, cmd); + if (!hdr) { + ret = -EMSGSIZE; + goto fail; + } + ret = nla_put_u32(msg, attr, val); + if (ret < 0) + goto fail; + ret = genlmsg_end(msg, hdr); + if (ret < 0) + goto fail; + + return genlmsg_reply(msg, info); + +fail: + if (msg) + nlmsg_free(msg); + return ret; +} + diff --git a/drivers/net/pkt-sniffer/core/nl.h b/drivers/net/pkt-sniffer/core/nl.h new file mode 100644 index 0000000..f7bf579 --- /dev/null +++ b/drivers/net/pkt-sniffer/core/nl.h @@ -0,0 +1,34 @@ +/* + * Packet sniffer core driver: generic netlink 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 + */ +#ifndef __SNF_NL_H +#define __SNF_NL_H + +#include +#include "snf_core.h" + +/* Initialise netlink interface for a sniffer channel, + * register netlink families etc. + */ +int snf_netlink_init(int id, struct snf_chan *dev, const char *name); + +/* Shutdown netlink interface, unregister + * families etc. + */ +void snf_netlink_release(struct snf_chan *dev); + +#endif diff --git a/drivers/net/pkt-sniffer/core/snf_core.h b/drivers/net/pkt-sniffer/core/snf_core.h new file mode 100644 index 0000000..062de70 --- /dev/null +++ b/drivers/net/pkt-sniffer/core/snf_core.h @@ -0,0 +1,64 @@ +/* + * 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 + */ +#ifndef __SNF_CORE_H +#define __SNF_CORE_H + +#include + +/* 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 *dev); + int (*stop)(struct snf_chan *dev); + int (*set_pattern)(struct snf_chan *dev, const u8 *pattern, int count); + int (*num_recs_avail)(struct snf_chan *dev); + int (*max_ptn_entries)(struct snf_chan *dev); + int (*max_match_bytes)(struct snf_chan *dev); + + 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 */ +}; + +/* Adds a sniffer channel to the registry */ +int snf_channel_add(struct snf_chan *dev, const char *name); + +/* Removes the channel with the specified id from the registry */ +int snf_channel_remove(int id); + +/* Returns handle to a channel and increments reference count */ +struct snf_chan *snf_channel_get(int id); + +/* Decrements reference count for the specified channel */ +void snf_channel_put(int id); + +/* Multicast a notification to user space for a sniffer match event */ +int snf_channel_notify_match(struct snf_chan *dev, struct snf_match_evt *mt); + +#endif + diff --git a/include/linux/pkt_sniffer.h b/include/linux/pkt_sniffer.h new file mode 100644 index 0000000..3e73d14 --- /dev/null +++ b/include/linux/pkt_sniffer.h @@ -0,0 +1,89 @@ +/* + * Linn packet sniffer driver 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 + */ +#ifndef __PKT_SNIFFER_H +#define __PKT_SNIFFER_H + +/* Commands for the pattern string */ +#define PTN_CMD_DONTCARE 0 +#define PTN_CMD_MATCH 1 +#define PTN_CMD_COPY 2 +#define PTN_CMD_MATCHSTAMP 3 +#define PTN_CMD_COPYDONE 4 + +/* Creates an entry for the pattern string. + * An entry consists of a command byte and a data byte +*/ +#define MAKE_PTN_ENTRY(cmd, data) (((((int)cmd) & 0xff) << 8) | (data & 0xff)) +/* Gets the cmd and data portion of a pattern string entry */ +#define PTNENTRY_CMD(val) (((val) >> 8) & 0xff) +#define PTNENTRY_DATA(val) ((val) & 0xff) + +/* Generic Netlink commands */ +enum { + SNF_CMD_UNSPEC, + SNF_CMD_START, /* start the sniffer */ + SNF_CMD_STOP, /* stop the sniffer */ + SNF_CMD_SETPATTERN, /* set the command pattern */ + SNF_CMD_NUMRECAVAIL, /* number of pending match events */ + SNF_CMD_MATCHEVENT, /* match event notification */ + SNF_CMD_PTNMAXCMDS, /* max number of commands supported */ + SNF_CMD_MAXMATCHBYTES, /* max number of bytes in match event */ + __SNF_CMD_MAX +}; +#define SNF_CMD_MAX (__SNF_CMD_MAX - 1) + +/* Generic Netlink attributes */ +enum { + SNF_ATTR_UNSPEC, + SNF_ATTR_PTNMAXCMDS, /* max number of commands supported */ + SNF_ATTR_PATTERN, /* nested attribute containing commands */ + SNF_ATTR_NUMRECAVAIL, /* number of pending match events */ + SNF_ATTR_MATCHEVENT, /* nested attribute for a match event */ + SNF_ATTR_MAXMATCHBYTES, /* max bytes in a match event */ + __SNF_ATTR_MAX +}; +#define SNF_ATTR_MAX (__SNF_ATTR_MAX - 1) + +/* Attributes that are included in the nested attribute + * for the command string + */ +enum { + SNF_ATTR_PTN_UNSPEC, + SNF_ATTR_PTN_ENTRY, /* command entry containing a command id */ + __SNF_ATTR_PTN_MAX /* and data byte */ +}; +#define SNF_ATTR_PTN_MAX (__SNF_ATTR_PTN_MAX - 1) + +/* Attributes included in the nested attribute + * of a match event notification + */ +enum { + SNF_ATTR_MATCH_UNSPEC, + SNF_ATTR_MATCH_TS, /* timestamp (returned with a match event) */ + SNF_ATTR_MATCH_PKTBYTE, /* packet data (returned with a match event) */ + __SNF_ATTR_MATCH_MAX +}; +#define SNF_ATTR_MATCH_MAX (__SNF_ATTR_MATCH_MAX - 1) + +/* Family version */ +#define SNF_GNL_VERSION 1 + +/* Multicast group name */ +#define SNF_EVENT_GRP "snf_evt_grp" + +#endif