lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20190918183552.28959-6-TheSven73@gmail.com>
Date:   Wed, 18 Sep 2019 14:35:52 -0400
From:   Sven Van Asbroeck <thesven73@...il.com>
To:     Greg KH <gregkh@...uxfoundation.org>
Cc:     Andreas Färber <afaerber@...e.de>,
        Linus Walleij <linus.walleij@...aro.org>,
        Enrico Weigelt <lkml@...ux.net>,
        Oliver Hartkopp <socketcan@...tkopp.net>,
        jan.kiszka@...mens.com, Frank Iwanitz <friw@...-networks.de>,
        linux-kernel@...r.kernel.org, netdev@...r.kernel.org
Subject: [PATCH v1 5/5] staging: fieldbus: add support for HMS FL-NET industrial controller

The Anybus-S FL-NET provides full FL-NET Class 1 functionality via the
patented Anybus-S application interface. Any device supporting this
standard can take advantage of the features offered by the module,
providing seamless network integration regardless of network type.

FL-NET is a control network, primarily used for interconnection of devices
such as PLCs, Robot Controllers and Numerical Control Devices. It features
both cyclic and acyclic data exchange capabilities, and uses a token-based
communication scheme for data transmission. The Anybus module is classified
as a 'Class 1'-node, which means that it supports cyclic data exchange in
both directions.

Official documentation:
https://www.anybus.com/docs/librariesprovider7/default-document-library
/manuals-design-guides/hms-scm_1200_073.pdf

This implementation is an Anybus-S client driver, designed to be
instantiated by the Anybus-S bus driver when it discovers the FL-NET
card.

If loaded successfully, the driver registers itself as a fieldbus_dev,
and userspace can access it through the fieldbus_dev interface.

Signed-off-by: Sven Van Asbroeck <TheSven73@...il.com>
---
 drivers/staging/fieldbus/anybuss/Kconfig     |  17 +
 drivers/staging/fieldbus/anybuss/Makefile    |   1 +
 drivers/staging/fieldbus/anybuss/hms-flnet.c | 520 +++++++++++++++++++
 3 files changed, 538 insertions(+)
 create mode 100644 drivers/staging/fieldbus/anybuss/hms-flnet.c

diff --git a/drivers/staging/fieldbus/anybuss/Kconfig b/drivers/staging/fieldbus/anybuss/Kconfig
index 635a0a7b7dd2..8441c6ac210b 100644
--- a/drivers/staging/fieldbus/anybuss/Kconfig
+++ b/drivers/staging/fieldbus/anybuss/Kconfig
@@ -38,4 +38,21 @@ config HMS_PROFINET
 
 	  If unsure, say N.
 
+config HMS_FLNET
+	tristate "HMS FL-Net Controller (Anybus-S)"
+	depends on FIELDBUS_DEV && HMS_ANYBUSS_BUS
+	select FIELDBUS_DEV_CONFIG
+	help
+	  If you say yes here you get support for the HMS Industrial
+	  Networks FL-Net Controller.
+
+	  It will be registered with the kernel as a fieldbus_dev,
+	  so userspace can interact with it via the fieldbus_dev userspace
+	  interface(s).
+
+	  This driver can also be built as a module. If so, the module
+	  will be called hms-flnet.
+
+	  If unsure, say N.
+
 endif
diff --git a/drivers/staging/fieldbus/anybuss/Makefile b/drivers/staging/fieldbus/anybuss/Makefile
index 3ad3dcc6be56..79c4083508f4 100644
--- a/drivers/staging/fieldbus/anybuss/Makefile
+++ b/drivers/staging/fieldbus/anybuss/Makefile
@@ -8,3 +8,4 @@ anybuss_core-y			+= host.o
 
 obj-$(CONFIG_ARCX_ANYBUS_CONTROLLER) += arcx-anybus.o
 obj-$(CONFIG_HMS_PROFINET)	+= hms-profinet.o
+obj-$(CONFIG_HMS_FLNET)		+= hms-flnet.o
diff --git a/drivers/staging/fieldbus/anybuss/hms-flnet.c b/drivers/staging/fieldbus/anybuss/hms-flnet.c
new file mode 100644
index 000000000000..4dd0f66fe8ce
--- /dev/null
+++ b/drivers/staging/fieldbus/anybuss/hms-flnet.c
@@ -0,0 +1,520 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HMS FL-Net Client Driver
+ *
+ * Copyright (C) 2019 Arcx Inc
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+
+/* move to <linux/fieldbus_dev.h> when taking this out of staging */
+#include "../fieldbus_dev.h"
+
+/* move to <linux/anybuss-client.h> when taking this out of staging */
+#include "anybuss-client.h"
+
+#define FL_DPRAM_SIZE	512
+
+/*
+ * ---------------------------------------------------------------
+ * Anybus FL-Net mailbox messages - definitions
+ * ---------------------------------------------------------------
+ * note that we're depending on the layout of these structures being
+ * exactly as advertised.
+ */
+
+struct msg_ip_addr {
+	u8 addr[4];
+};
+
+struct msg_map {
+	__be16 start;
+	__be16 size;
+};
+
+struct msg_map_io {
+	struct msg_map a1_in;
+	struct msg_map a2_in;
+	struct msg_map a1_out;
+	struct msg_map a2_out;
+};
+
+#define FL_ID_BUFSZ	10
+
+struct msg_set_profile {
+	u8	rev_number;
+	u8	rev_year_hi;
+	u8	rev_year_lo;
+	u8	rev_month;
+	u8	rev_day;
+	/* id_XXX must be space-padded and not null terminated */
+	char	id_type[FL_ID_BUFSZ];
+	char	null0;
+	char	id_vendor[FL_ID_BUFSZ];
+	char	null1;
+	char	id_product[FL_ID_BUFSZ];
+	char	null2;
+};
+
+struct fl_priv {
+	struct fieldbus_dev fbdev;
+	struct anybuss_client *client;
+	struct mutex enable_lock; /* serializes card enable */
+	bool power_on;
+	/* configuration properties */
+	enum fieldbus_dev_offl_mode offl_mode;
+	struct msg_ip_addr ip;
+	struct msg_map_io mapio;
+	u8 rev_number;
+	u16 rev_year;
+	u8 rev_month;
+	u8 rev_day;
+	char id_type[FL_ID_BUFSZ + 1];
+	char id_vendor[FL_ID_BUFSZ + 1];
+	char id_product[FL_ID_BUFSZ + 1];
+};
+
+static void fl_on_area_updated(struct anybuss_client *client)
+{
+	struct fl_priv *priv = anybuss_get_drvdata(client);
+
+	fieldbus_dev_area_updated(&priv->fbdev);
+}
+
+static void fl_on_online_changed(struct anybuss_client *client, bool online)
+{
+	struct fl_priv *priv = anybuss_get_drvdata(client);
+
+	fieldbus_dev_online_changed(&priv->fbdev, online);
+}
+
+static ssize_t
+fl_read_area(struct fieldbus_dev *fbdev, char __user *buf, size_t size,
+	     loff_t *offset)
+{
+	struct fl_priv *priv = container_of(fbdev, struct fl_priv, fbdev);
+
+	return anybuss_read_output(priv->client, buf, size, offset);
+}
+
+static ssize_t
+fl_write_area(struct fieldbus_dev *fbdev, const char __user *buf,
+	      size_t size, loff_t *offset)
+{
+	struct fl_priv *priv = container_of(fbdev, struct fl_priv, fbdev);
+
+	return anybuss_write_input(priv->client, buf, size, offset);
+}
+
+static int fl_id_get(struct fieldbus_dev *fbdev, char *buf, size_t max_size)
+{
+	struct fl_priv *priv = container_of(fbdev, struct fl_priv, fbdev);
+	struct msg_ip_addr response;
+	int ret;
+
+	ret = anybuss_recv_msg(priv->client, 0x0002, &response,
+			       sizeof(response));
+	if (ret < 0)
+		return ret;
+	return sprintf(buf, "%pI4\n", response.addr);
+}
+
+static bool fl_enable_get(struct fieldbus_dev *fbdev)
+{
+	struct fl_priv *priv = container_of(fbdev, struct fl_priv, fbdev);
+	bool power_on;
+
+	mutex_lock(&priv->enable_lock);
+	power_on = priv->power_on;
+	mutex_unlock(&priv->enable_lock);
+
+	return power_on;
+}
+
+static bool ip_is_null(struct msg_ip_addr *ip)
+{
+	return !ip->addr[0] && !ip->addr[1] && !ip->addr[2] && !ip->addr[3];
+}
+
+static bool map_is_empty(struct msg_map_io *map)
+{
+	return !map->a1_in.size && !map->a1_out.size && !map->a2_in.size &&
+	       !map->a2_out.size;
+}
+
+static void fl_id_copy_pad(char *id, const char *src)
+{
+	memset(id, ' ', FL_ID_BUFSZ);
+	memcpy(id, src, min_t(size_t, strlen(src), FL_ID_BUFSZ));
+}
+
+static int __fl_enable(struct fl_priv *priv)
+{
+	int ret;
+	size_t len;
+	struct anybuss_client *client = priv->client;
+	/* Initialization Sequence, Generic Anybus Mode */
+	struct anybuss_memcfg mem_cfg = {
+		.input_io = FL_DPRAM_SIZE,
+		.input_dpram = FL_DPRAM_SIZE,
+		.input_total = FL_DPRAM_SIZE,
+		.output_io = FL_DPRAM_SIZE,
+		.output_dpram = FL_DPRAM_SIZE,
+		.output_total = FL_DPRAM_SIZE,
+		.offl_mode = priv->offl_mode,
+	};
+	struct msg_set_profile profile = {
+		.rev_number = priv->rev_number,
+		.rev_day = priv->rev_day,
+		.rev_month = priv->rev_month,
+		.rev_year_hi = priv->rev_year >> 8,
+		.rev_year_lo = priv->rev_year & 0xFF,
+	};
+
+	if (map_is_empty(&priv->mapio)) {
+		dev_err(&client->dev, "empty I/O area mapping");
+		return -EINVAL;
+	}
+	len = be16_to_cpu(priv->mapio.a1_in.size) +
+			be16_to_cpu(priv->mapio.a2_in.size);
+	if (len > FL_DPRAM_SIZE) {
+		dev_err(&client->dev, "I/O input area mapping too large");
+		return -EINVAL;
+	}
+	len = be16_to_cpu(priv->mapio.a1_out.size) +
+			be16_to_cpu(priv->mapio.a2_out.size);
+	if (len > FL_DPRAM_SIZE) {
+		dev_err(&client->dev, "I/O output area mapping too large");
+		return -EINVAL;
+	}
+	if (ip_is_null(&priv->ip)) {
+		dev_err(&client->dev, "null ip address");
+		return -EINVAL;
+	}
+
+	/*
+	 * switch anybus off then on, this ensures we can do a complete
+	 * configuration cycle in case anybus was already on.
+	 */
+	anybuss_set_power(client, false);
+	ret = anybuss_set_power(client, true);
+	if (ret)
+		goto err;
+	ret = anybuss_start_init(client, &mem_cfg);
+	if (ret)
+		goto err;
+	/* set ip address */
+	ret = anybuss_send_msg(client, 0x0001, &priv->ip,
+			       sizeof(priv->ip));
+	if (ret)
+		goto err;
+	/* map io */
+	ret = anybuss_send_msg(client, 0x0020, &priv->mapio,
+			       sizeof(priv->mapio));
+	if (ret)
+		goto err;
+	/* set profile */
+	fl_id_copy_pad(profile.id_type, priv->id_type);
+	fl_id_copy_pad(profile.id_vendor, priv->id_vendor);
+	fl_id_copy_pad(profile.id_product, priv->id_product);
+	ret = anybuss_send_msg(client, 0x0023, &profile, sizeof(profile));
+	if (ret)
+		goto err;
+	ret = anybuss_finish_init(client);
+	if (ret)
+		goto err;
+	priv->power_on = true;
+	return 0;
+
+err:
+	anybuss_set_power(client, false);
+	priv->power_on = false;
+	return ret;
+}
+
+static int __fl_disable(struct fl_priv *priv)
+{
+	struct anybuss_client *client = priv->client;
+
+	anybuss_set_power(client, false);
+	priv->power_on = false;
+	return 0;
+}
+
+static int fl_enable(struct fieldbus_dev *fbdev, bool enable)
+{
+	int ret;
+	struct fl_priv *priv = container_of(fbdev, struct fl_priv, fbdev);
+
+	mutex_lock(&priv->enable_lock);
+	if (enable)
+		ret = __fl_enable(priv);
+	else
+		ret = __fl_disable(priv);
+	mutex_unlock(&priv->enable_lock);
+
+	return ret;
+}
+
+static const enum fieldbus_dev_cfgprop config_props[] = {
+	FIELDBUS_DEV_PROP_IP_ADDR,
+	FIELDBUS_DEV_PROP_OFFLINE_MODE,
+	FIELDBUS_DEV_PROP_A1_IN_START,
+	FIELDBUS_DEV_PROP_A1_IN_SIZE,
+	FIELDBUS_DEV_PROP_A1_OUT_START,
+	FIELDBUS_DEV_PROP_A1_OUT_SIZE,
+	FIELDBUS_DEV_PROP_A2_IN_START,
+	FIELDBUS_DEV_PROP_A2_IN_SIZE,
+	FIELDBUS_DEV_PROP_A2_OUT_START,
+	FIELDBUS_DEV_PROP_A2_OUT_SIZE,
+	FIELDBUS_DEV_PROP_REV_NUMBER,
+	FIELDBUS_DEV_PROP_REV_DATE_YEAR,
+	FIELDBUS_DEV_PROP_REV_DATE_MONTH,
+	FIELDBUS_DEV_PROP_REV_DATE_DAY,
+	FIELDBUS_DEV_PROP_ID_TYPE,
+	FIELDBUS_DEV_PROP_ID_VENDOR,
+	FIELDBUS_DEV_PROP_ID_PRODUCT
+};
+
+static int fl_get_cfgprop(struct fieldbus_dev *fbdev,
+			  enum fieldbus_dev_cfgprop prop,
+			  union fieldbus_dev_propval *val)
+{
+	struct fl_priv *priv = container_of(fbdev, struct fl_priv, fbdev);
+	struct msg_map_io *mapio = &priv->mapio;
+
+	switch (prop) {
+	case FIELDBUS_DEV_PROP_OFFLINE_MODE:
+		val->intval = priv->offl_mode;
+		break;
+	case FIELDBUS_DEV_PROP_IP_ADDR:
+		memcpy(val->addrval, &priv->ip.addr, sizeof(priv->ip.addr));
+		break;
+	case FIELDBUS_DEV_PROP_A1_IN_START:
+		val->intval = be16_to_cpu(mapio->a1_in.start);
+		break;
+	case FIELDBUS_DEV_PROP_A1_IN_SIZE:
+		val->intval = be16_to_cpu(mapio->a1_in.size);
+		break;
+	case FIELDBUS_DEV_PROP_A1_OUT_START:
+		val->intval = be16_to_cpu(mapio->a1_out.start);
+		break;
+	case FIELDBUS_DEV_PROP_A1_OUT_SIZE:
+		val->intval = be16_to_cpu(mapio->a1_out.size);
+		break;
+	case FIELDBUS_DEV_PROP_A2_IN_START:
+		val->intval = be16_to_cpu(mapio->a2_in.start);
+		break;
+	case FIELDBUS_DEV_PROP_A2_IN_SIZE:
+		val->intval = be16_to_cpu(mapio->a2_in.size);
+		break;
+	case FIELDBUS_DEV_PROP_A2_OUT_START:
+		val->intval = be16_to_cpu(mapio->a2_out.start);
+		break;
+	case FIELDBUS_DEV_PROP_A2_OUT_SIZE:
+		val->intval = be16_to_cpu(mapio->a2_out.size);
+		break;
+	case FIELDBUS_DEV_PROP_REV_NUMBER:
+		val->intval = priv->rev_number;
+		break;
+	case FIELDBUS_DEV_PROP_REV_DATE_YEAR:
+		val->intval = priv->rev_year;
+		break;
+	case FIELDBUS_DEV_PROP_REV_DATE_MONTH:
+		val->intval = priv->rev_month;
+		break;
+	case FIELDBUS_DEV_PROP_REV_DATE_DAY:
+		val->intval = priv->rev_day;
+		break;
+	case FIELDBUS_DEV_PROP_ID_TYPE:
+		strcpy(val->strval, priv->id_type);
+		break;
+	case FIELDBUS_DEV_PROP_ID_VENDOR:
+		strcpy(val->strval, priv->id_vendor);
+		break;
+	case FIELDBUS_DEV_PROP_ID_PRODUCT:
+		strcpy(val->strval, priv->id_product);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int fl_set_cfgprop(struct fieldbus_dev *fbdev,
+			  enum fieldbus_dev_cfgprop prop,
+			  const union fieldbus_dev_propval *val)
+{
+	struct fl_priv *priv = container_of(fbdev, struct fl_priv, fbdev);
+	struct msg_map_io *mapio = &priv->mapio;
+
+	/* validate userspace inputs */
+	switch (prop) {
+	case FIELDBUS_DEV_PROP_A1_IN_START:
+	case FIELDBUS_DEV_PROP_A1_IN_SIZE:
+	case FIELDBUS_DEV_PROP_A1_OUT_START:
+	case FIELDBUS_DEV_PROP_A1_OUT_SIZE:
+	case FIELDBUS_DEV_PROP_A2_IN_START:
+	case FIELDBUS_DEV_PROP_A2_IN_SIZE:
+	case FIELDBUS_DEV_PROP_A2_OUT_START:
+	case FIELDBUS_DEV_PROP_A2_OUT_SIZE:
+	case FIELDBUS_DEV_PROP_REV_DATE_YEAR:
+		/* must be u16 */
+		if (val->intval & ~0xFFFF)
+			return -EINVAL;
+		break;
+	case FIELDBUS_DEV_PROP_REV_NUMBER:
+	case FIELDBUS_DEV_PROP_REV_DATE_MONTH:
+	case FIELDBUS_DEV_PROP_REV_DATE_DAY:
+		/* must be u8 */
+		if (val->intval & ~0xFF)
+			return -EINVAL;
+		break;
+	case FIELDBUS_DEV_PROP_ID_TYPE:
+	case FIELDBUS_DEV_PROP_ID_VENDOR:
+	case FIELDBUS_DEV_PROP_ID_PRODUCT:
+		if (strlen(val->strval) > FL_ID_BUFSZ)
+			return -EINVAL;
+		break;
+	default:
+		break;
+	}
+
+	switch (prop) {
+	case FIELDBUS_DEV_PROP_OFFLINE_MODE:
+		priv->offl_mode = val->intval;
+		break;
+	case FIELDBUS_DEV_PROP_IP_ADDR:
+		memcpy(&priv->ip.addr, val->addrval, sizeof(priv->ip.addr));
+		break;
+	case FIELDBUS_DEV_PROP_A1_IN_START:
+		mapio->a1_in.start = cpu_to_be16(val->intval);
+		break;
+	case FIELDBUS_DEV_PROP_A1_IN_SIZE:
+		mapio->a1_in.size = cpu_to_be16(val->intval);
+		break;
+	case FIELDBUS_DEV_PROP_A1_OUT_START:
+		mapio->a1_out.start = cpu_to_be16(val->intval);
+		break;
+	case FIELDBUS_DEV_PROP_A1_OUT_SIZE:
+		mapio->a1_out.size = cpu_to_be16(val->intval);
+		break;
+	case FIELDBUS_DEV_PROP_A2_IN_START:
+		mapio->a2_in.start = cpu_to_be16(val->intval);
+		break;
+	case FIELDBUS_DEV_PROP_A2_IN_SIZE:
+		mapio->a2_in.size = cpu_to_be16(val->intval);
+		break;
+	case FIELDBUS_DEV_PROP_A2_OUT_START:
+		mapio->a2_out.start = cpu_to_be16(val->intval);
+		break;
+	case FIELDBUS_DEV_PROP_A2_OUT_SIZE:
+		mapio->a2_out.size = cpu_to_be16(val->intval);
+		break;
+	case FIELDBUS_DEV_PROP_REV_NUMBER:
+		priv->rev_number = val->intval;
+		break;
+	case FIELDBUS_DEV_PROP_REV_DATE_YEAR:
+		priv->rev_year = val->intval;
+		break;
+	case FIELDBUS_DEV_PROP_REV_DATE_MONTH:
+		priv->rev_month = val->intval;
+		break;
+	case FIELDBUS_DEV_PROP_REV_DATE_DAY:
+		priv->rev_day = val->intval;
+		break;
+	case FIELDBUS_DEV_PROP_ID_TYPE:
+		strcpy(priv->id_type, val->strval);
+		break;
+	case FIELDBUS_DEV_PROP_ID_VENDOR:
+		strcpy(priv->id_vendor, val->strval);
+		break;
+	case FIELDBUS_DEV_PROP_ID_PRODUCT:
+		strcpy(priv->id_product, val->strval);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int flnet_probe(struct anybuss_client *client)
+{
+	struct fl_priv *priv;
+	struct device *dev = &client->dev;
+	int err;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	mutex_init(&priv->enable_lock);
+	priv->offl_mode = FIELDBUS_DEV_OFFL_MODE_CLEAR;
+	priv->rev_year = 2005;
+	priv->rev_month = 7;
+	priv->rev_day = 1;
+	strcpy(priv->id_type, "OTHER");
+	strcpy(priv->id_vendor, "HMS");
+	strcpy(priv->id_product, "ABS-FLN");
+	client->on_area_updated = fl_on_area_updated;
+	client->on_online_changed = fl_on_online_changed;
+	priv->client = client;
+	priv->fbdev.read_area_sz = FL_DPRAM_SIZE;
+	priv->fbdev.write_area_sz = FL_DPRAM_SIZE;
+	priv->fbdev.card_name = "HMS FL-Net (Anybus-S)";
+	priv->fbdev.fieldbus_type = FIELDBUS_DEV_TYPE_FLNET;
+	priv->fbdev.read_area = fl_read_area;
+	priv->fbdev.write_area = fl_write_area;
+	priv->fbdev.fieldbus_id_get = fl_id_get;
+	priv->fbdev.enable_get = fl_enable_get;
+	priv->fbdev.cfgprops = config_props;
+	priv->fbdev.num_cfgprops = ARRAY_SIZE(config_props);
+	priv->fbdev.get_cfgprop = fl_get_cfgprop;
+	priv->fbdev.set_cfgprop = fl_set_cfgprop;
+	priv->fbdev.enable_set = fl_enable;
+	priv->fbdev.parent = dev;
+	err = fieldbus_dev_register(&priv->fbdev);
+	if (err < 0)
+		return err;
+	dev_info(dev, "card detected, registered as %s",
+		 dev_name(priv->fbdev.dev));
+	anybuss_set_drvdata(client, priv);
+
+	return 0;
+}
+
+static int flnet_remove(struct anybuss_client *client)
+{
+	struct fl_priv *priv = anybuss_get_drvdata(client);
+
+	fieldbus_dev_unregister(&priv->fbdev);
+	return 0;
+}
+
+static struct anybuss_client_driver flnet_driver = {
+	.probe = flnet_probe,
+	.remove = flnet_remove,
+	.driver		= {
+		.name   = "hms-flnet",
+		.owner	= THIS_MODULE,
+	},
+	.anybus_id = 0x0086,
+};
+
+static int __init flnet_init(void)
+{
+	return anybuss_client_driver_register(&flnet_driver);
+}
+module_init(flnet_init);
+
+static void __exit flnet_exit(void)
+{
+	return anybuss_client_driver_unregister(&flnet_driver);
+}
+module_exit(flnet_exit);
+
+MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@...il.com>");
+MODULE_DESCRIPTION("HMS FL-Net Driver (Anybus-S)");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ