Messages in this thread Patch in this message | | | From | Sven Van Asbroeck <> | Subject | [PATCH v1 5/5] staging: fieldbus: add support for HMS FL-NET industrial controller | Date | Wed, 18 Sep 2019 14:35:52 -0400 |
| |
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@gmail.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@gmail.com>"); +MODULE_DESCRIPTION("HMS FL-Net Driver (Anybus-S)"); +MODULE_LICENSE("GPL v2"); -- 2.17.1
| |