[PATCH v1 5/5] staging: fieldbus: add support for HMS FL-NET industrial controller

From: Sven Van Asbroeck
Date: Wed Sep 18 2019 - 14:36:09 EST


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@xxxxxxxxx>
---
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@xxxxxxxxx>");
+MODULE_DESCRIPTION("HMS FL-Net Driver (Anybus-S)");
+MODULE_LICENSE("GPL v2");
--
2.17.1