[PATCH anybus v2 5/5] misc: support HMS Profinet IRT industrial controller.
From: thesven73
Date: Wed Oct 31 2018 - 15:44:42 EST
From: Sven Van Asbroeck <svendev@xxxxxxxx>
The Anybus-S PROFINET IRT communication module provides instant integration
to any Ethernet based LAN via SMTP, FTP, HTTP as well as PROFINET and
Modbus-TCP. Additional protocols can be implemented on top of TCP/IP
or UDP using the transparent socket interface.
Official documentation:
https://www.anybus.com/docs/librariesprovider7/default-document-library
/manuals-design-guides/hms-hmsi-168-52.pdf
This implementation is an Anybus-S client driver, designed to be
instantiated by the Anybus-S bus driver when it discovers the Profinet
card.
If loaded successfully, the driver creates a /dev/profinet%d devnode,
and a /sys/class/misc/profinet%d sysfs subdir:
- the card can be configured with a single, atomic ioctl on the devnode;
- the card's internal dpram is accessed by calling read/write/seek
on the devnode.
- the card's "fieldbus specific area" properties can be accessed via
the sysfs dir.
Signed-off-by: Sven Van Asbroeck <svendev@xxxxxxxx>
---
Documentation/ioctl/ioctl-number.txt | 1 +
drivers/misc/Kconfig | 10 +
drivers/misc/Makefile | 1 +
drivers/misc/hms-profinet.c | 753 +++++++++++++++++++++++++++
include/uapi/linux/hms-common.h | 14 +
include/uapi/linux/hms-profinet.h | 102 ++++
6 files changed, 881 insertions(+)
create mode 100644 drivers/misc/hms-profinet.c
create mode 100644 include/uapi/linux/hms-common.h
create mode 100644 include/uapi/linux/hms-profinet.h
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index 13a7c999c04a..a389a4ec1429 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -241,6 +241,7 @@ Code Seq#(hex) Include File Comments
<http://web.archive.org/web/*/http://mikonos.dia.unisa.it/tcfs>
'l' 40-7F linux/udf_fs_i.h in development:
<http://sourceforge.net/projects/linux-udf/>
+'l' 80-9F linux/hms-profinet.h Anybus-S
'm' 00-09 linux/mmtimer.h conflict!
'm' all linux/mtio.h conflict!
'm' all linux/soundcard.h conflict!
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index dd7fdbf659b7..d17b581c06f7 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -406,6 +406,16 @@ config SPEAR13XX_PCIE_GADGET
entry will be created for that controller. User can use these
sysfs node to configure PCIe EP as per his requirements.
+config HMS_PROFINET
+ tristate "HMS Profinet IRT Controller (Anybus-S)"
+ select HMS_ANYBUSS_HOST
+ help
+ If you say yes here you get support for the HMS Industrial
+ Networks Profinet IRT Controller.
+ This driver can also be built as a module. If so, the module
+ will be called hms-profinet.
+ If unsure, say N.
+
config VMWARE_BALLOON
tristate "VMware Balloon Driver"
depends on VMWARE_VMCI && X86 && HYPERVISOR_GUEST
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 20c9fbfb100b..087c48273179 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
obj-$(CONFIG_DS1682) += ds1682.o
obj-$(CONFIG_C2PORT) += c2port/
obj-$(CONFIG_HMC6352) += hmc6352.o
+obj-$(CONFIG_HMS_PROFINET) += hms-profinet.o
obj-y += eeprom/
obj-y += cb710/
obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o
diff --git a/drivers/misc/hms-profinet.c b/drivers/misc/hms-profinet.c
new file mode 100644
index 000000000000..dd6c0f715bf1
--- /dev/null
+++ b/drivers/misc/hms-profinet.c
@@ -0,0 +1,753 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HMS Profinet Client Driver
+ *
+ * Copyright (C) 2018 Arcx Inc
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/idr.h>
+#include <linux/miscdevice.h>
+
+#include <linux/anybuss-client.h>
+#include <uapi/linux/hms-profinet.h>
+
+#define PROFI_DPRAM_SIZE 512
+
+/*
+ * --------------------------------------------------------------
+ * Anybus Profinet mailbox messages - definitions
+ * --------------------------------------------------------------
+ */
+
+/*
+ * note that we're depending on the layout of these structures being
+ * exactly as advertised - which means they need to be packed.
+ */
+
+struct msgEthConfig {
+ u32 ip_addr, subnet_msk, gateway_addr;
+} __packed;
+
+struct msgMacAddr {
+ u8 addr[6];
+} __packed;
+
+struct msgStr {
+ char s[128];
+} __packed;
+
+struct msgShortStr {
+ char s[64];
+} __packed;
+
+struct msgHicp {
+ char enable;
+} __packed;
+
+/*
+ * --------------------------------------------------------------
+ * Fieldbus Specific Area - memory locations
+ * --------------------------------------------------------------
+ */
+#define FSA_NETWORK_STATUS 0x700
+#define FSA_LAYER_STATUS 0x7B2
+#define FSA_IO_CTRL_STATUS 0x7B0
+#define FSA_LAYER_FAULT_CODE 0x7B4
+
+struct profi_priv {
+ struct anybuss_client *client;
+ int id;
+ atomic_t refcount;
+ char node_name[16];
+ struct miscdevice misc;
+ struct device *dev; /* just a link to the misc device */
+ struct mutex enable_lock;
+};
+
+static int profinet_configure(struct anybuss_client *ab,
+ struct ProfinetConfig *cfg)
+{
+ int ret;
+
+ if (cfg->eth.is_valid) {
+ struct msgEthConfig msg = {
+ .ip_addr = cfg->eth.ip_addr,
+ .subnet_msk = cfg->eth.subnet_msk,
+ .gateway_addr = cfg->eth.gateway_addr,
+ };
+ ret = anybuss_send_msg(ab, 0x0001, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->dev_id.is_valid) {
+ u16 ext[2] = {
+ cpu_to_be16(cfg->dev_id.vendorid),
+ cpu_to_be16(cfg->dev_id.deviceid)
+ };
+ ret = anybuss_send_ext(ab, 0x0102, ext, sizeof(ext));
+ if (ret)
+ return ret;
+ }
+ if (cfg->station_name.is_valid) {
+ struct msgStr msg = { 0 };
+
+ strncpy(msg.s, cfg->station_name.name, sizeof(msg.s));
+ ret = anybuss_send_msg(ab, 0x0103, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->station_type.is_valid) {
+ struct msgShortStr msg = { 0 };
+
+ strncpy(msg.s, cfg->station_type.name, sizeof(msg.s));
+ ret = anybuss_send_msg(ab, 0x0104, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->mac_addr.is_valid) {
+ struct msgMacAddr msg = { 0 };
+
+ memcpy(msg.addr, cfg->mac_addr.addr, sizeof(msg.addr));
+ ret = anybuss_send_msg(ab, 0x0019, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->host_domain.is_valid) {
+ size_t len;
+ struct msgStr msg = { 0 };
+ /*
+ * check if host and domain names fit in msg structure
+ */
+ len = strnlen(cfg->host_domain.hostname,
+ sizeof(cfg->host_domain.hostname))
+ + 1 +
+ strnlen(cfg->host_domain.domainname,
+ sizeof(cfg->host_domain.domainname))
+ + 1;
+ if (len > sizeof(msg.s))
+ return -ENAMETOOLONG;
+ strncpy(msg.s, cfg->host_domain.hostname,
+ sizeof(msg.s));
+ len = strnlen(msg.s, sizeof(msg.s)) + 1; /* NULL term */
+ strncpy(msg.s + len, cfg->host_domain.domainname,
+ sizeof(msg.s) - len);
+ ret = anybuss_send_msg(ab, 0x0032, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->hicp.is_valid) {
+ struct msgHicp msg = {
+ .enable = cfg->hicp.enable ? 1 : 0,
+ };
+ ret = anybuss_send_msg(ab, 0x0013, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->web_server.is_valid) {
+ ret = anybuss_send_msg(ab,
+ cfg->web_server.enable ? 0x0005 : 0x0004,
+ NULL, 0);
+ if (ret)
+ return ret;
+ }
+ if (cfg->ftp_server.disable) {
+ ret = anybuss_send_msg(ab, 0x0006, NULL, 0);
+ if (ret)
+ return ret;
+ }
+ if (cfg->global_admin_mode.enable) {
+ ret = anybuss_send_msg(ab, 0x000B, NULL, 0);
+ if (ret)
+ return ret;
+ }
+ if (cfg->vfs.disable) {
+ ret = anybuss_send_msg(ab, 0x0011, NULL, 0);
+ if (ret)
+ return ret;
+ }
+ if (cfg->stop_mode.is_valid) {
+ u16 action;
+
+ switch (cfg->stop_mode.action) {
+ case HMS_SMA_CLEAR:
+ action = 0;
+ break;
+ case HMS_SMA_FREEZE:
+ action = 1;
+ break;
+ case HMS_SMA_SET:
+ action = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+ action = cpu_to_be16(action);
+ ret = anybuss_send_ext(ab, 0x0101, &action,
+ sizeof(action));
+ if (ret)
+ return ret;
+ }
+ if (cfg->snmp_system_descr.is_valid) {
+ struct msgStr msg = { 0 };
+
+ strncpy(msg.s, cfg->snmp_system_descr.description,
+ sizeof(msg.s));
+ ret = anybuss_send_msg(ab, 0x0120, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->snmp_iface_descr.is_valid) {
+ struct msgStr msg = { 0 };
+
+ strncpy(msg.s, cfg->snmp_iface_descr.description,
+ sizeof(msg.s));
+ ret = anybuss_send_msg(ab, 0x0121, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->mib2_system_descr.is_valid) {
+ struct msgStr msg = { 0 };
+
+ strncpy(msg.s, cfg->mib2_system_descr.description,
+ sizeof(msg.s));
+ ret = anybuss_send_msg(ab, 0x0124, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->mib2_system_contact.is_valid) {
+ struct msgStr msg = { 0 };
+
+ strncpy(msg.s, cfg->mib2_system_contact.contact,
+ sizeof(msg.s));
+ ret = anybuss_send_msg(ab, 0x0125, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ if (cfg->mib2_system_location.is_valid) {
+ struct msgStr msg = { 0 };
+
+ strncpy(msg.s, cfg->mib2_system_location.location,
+ sizeof(msg.s));
+ ret = anybuss_send_msg(ab, 0x0126, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int profinet_enable(struct profi_priv *priv,
+ struct ProfinetConfig *cfg)
+{
+ int ret;
+ struct anybuss_client *client = priv->client;
+
+ /* Initialization Sequence, Generic Anybus Mode */
+ const struct anybuss_memcfg mem_cfg = {
+ .input_io = 220,
+ .input_dpram = PROFI_DPRAM_SIZE,
+ .input_total = PROFI_DPRAM_SIZE,
+ .output_io = 220,
+ .output_dpram = PROFI_DPRAM_SIZE,
+ .output_total = PROFI_DPRAM_SIZE,
+ .offl_mode = AB_OFFL_MODE_CLEAR,
+ };
+ if (mutex_lock_interruptible(&priv->enable_lock))
+ return -ERESTARTSYS;
+ /*
+ * 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_init;
+ ret = anybuss_start_init(client, &mem_cfg);
+ if (ret)
+ goto err_init;
+ if (cfg)
+ ret = profinet_configure(client, cfg);
+ if (ret)
+ goto err_init;
+ ret = anybuss_finish_init(client);
+ if (ret)
+ goto err_init;
+ mutex_unlock(&priv->enable_lock);
+ return 0;
+err_init:
+ anybuss_set_power(client, false);
+ mutex_unlock(&priv->enable_lock);
+ return ret;
+}
+
+static int profinet_disable(struct profi_priv *priv)
+{
+ int ret;
+
+ if (mutex_lock_interruptible(&priv->enable_lock))
+ return -ERESTARTSYS;
+ ret = anybuss_set_power(priv->client, false);
+ mutex_unlock(&priv->enable_lock);
+ return ret;
+}
+
+static int fbctrl_readw(struct anybuss_client *client, u16 addr)
+{
+ int ret;
+ u16 val;
+
+ ret = anybuss_read_fbctrl(client, addr, &val, sizeof(val));
+ if (ret < 0)
+ return ret;
+ return (int)be16_to_cpu(val);
+}
+
+static ssize_t mac_addr_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ struct msgMacAddr response;
+ int ret;
+
+ ret = anybuss_recv_msg(priv->client, 0x0010, &response,
+ sizeof(response));
+ if (ret)
+ return ret;
+ return snprintf(buf, PAGE_SIZE, "%02X:%02X:%02X:%02X:%02X:%02X\n",
+ response.addr[0], response.addr[1],
+ response.addr[2], response.addr[3],
+ response.addr[4], response.addr[5]);
+}
+
+static DEVICE_ATTR_RO(mac_addr);
+
+static ssize_t start_defaults_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ unsigned long num;
+
+ if (kstrtoul(buf, 0, &num))
+ return -EINVAL;
+ if (num)
+ profinet_enable(priv, NULL);
+ return count;
+}
+
+static DEVICE_ATTR_WO(start_defaults);
+
+static ssize_t ip_addr_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ struct msgEthConfig response;
+ int ret;
+
+ ret = anybuss_recv_msg(priv->client, 0x0002, &response,
+ sizeof(response));
+ if (ret)
+ return ret;
+ return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n",
+ response.ip_addr & 0xFF,
+ (response.ip_addr >> 8) & 0xFF,
+ (response.ip_addr >> 16) & 0xFF,
+ (response.ip_addr >> 24) & 0xFF);
+}
+
+static DEVICE_ATTR_RO(ip_addr);
+
+static ssize_t subnet_mask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ struct msgEthConfig response;
+ int ret;
+
+ ret = anybuss_recv_msg(priv->client, 0x0002, &response,
+ sizeof(response));
+ if (ret)
+ return ret;
+ return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n",
+ response.subnet_msk & 0xFF,
+ (response.subnet_msk >> 8) & 0xFF,
+ (response.subnet_msk >> 16) & 0xFF,
+ (response.subnet_msk >> 24) & 0xFF);
+}
+
+static DEVICE_ATTR_RO(subnet_mask);
+
+static ssize_t gateway_addr_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ struct msgEthConfig response;
+ int ret;
+
+ ret = anybuss_recv_msg(priv->client, 0x0002, &response,
+ sizeof(response));
+ if (ret)
+ return ret;
+ return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n",
+ response.gateway_addr & 0xFF,
+ (response.gateway_addr >> 8) & 0xFF,
+ (response.gateway_addr >> 16) & 0xFF,
+ (response.gateway_addr >> 24) & 0xFF);
+}
+
+static DEVICE_ATTR_RO(gateway_addr);
+
+static ssize_t hostname_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ struct msgStr response;
+ int ret;
+
+ ret = anybuss_recv_msg(priv->client, 0x0034, &response,
+ sizeof(response));
+ if (ret)
+ return ret;
+ return snprintf(buf, PAGE_SIZE, "%s\n", response.s);
+}
+
+static DEVICE_ATTR_RO(hostname);
+
+static ssize_t domainname_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ struct msgStr response;
+ int ret, pos;
+
+ ret = anybuss_recv_msg(priv->client, 0x0034, &response,
+ sizeof(response));
+ if (ret)
+ return ret;
+ /*
+ * domain name string located right behind null-terminated
+ * host name string.
+ */
+ pos = strnlen(response.s, sizeof(response.s)) + 1;
+ if (pos >= sizeof(response.s))
+ return -ENAMETOOLONG;
+ return snprintf(buf, PAGE_SIZE, "%s\n", response.s + pos);
+}
+
+static DEVICE_ATTR_RO(domainname);
+
+static ssize_t network_link_on_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ int ns;
+
+ ns = fbctrl_readw(priv->client, FSA_NETWORK_STATUS);
+ if (ns < 0)
+ return ns;
+ return snprintf(buf, PAGE_SIZE, "%d\n", ns & 1);
+}
+
+static DEVICE_ATTR_RO(network_link_on);
+
+static ssize_t network_ip_in_use_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ int ns;
+
+ ns = fbctrl_readw(priv->client, FSA_NETWORK_STATUS);
+ if (ns < 0)
+ return ns;
+ return snprintf(buf, PAGE_SIZE, "%d\n", (ns>>1) & 1);
+}
+
+static DEVICE_ATTR_RO(network_ip_in_use);
+
+static ssize_t layer_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ const char *s;
+ int ls;
+
+ ls = fbctrl_readw(priv->client, FSA_LAYER_STATUS);
+ if (ls < 0)
+ return ls;
+ switch (ls) {
+ case 0x0000:
+ s = "not yet initialized";
+ break;
+ case 0x0001:
+ s = "successfully initialized";
+ break;
+ case 0x0002:
+ s = "failed to initialize";
+ break;
+ default:
+ return -EINVAL;
+ }
+ return snprintf(buf, PAGE_SIZE, "%s\n", s);
+}
+
+static DEVICE_ATTR_RO(layer_status);
+
+static ssize_t io_controller_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct profi_priv *priv = dev_get_drvdata(dev);
+ const char *s;
+ int w;
+
+ w = fbctrl_readw(priv->client, FSA_IO_CTRL_STATUS);
+ if (w < 0)
+ return w;
+ switch (w) {
+ case 0x0000:
+ s = "No connection made";
+ break;
+ case 0x0001:
+ s = "STOP";
+ break;
+ case 0x0002:
+ s = "RUN";
+ break;
+ case 0x0004:
+ s = "STATION OK";
+ break;
+ case 0x0008:
+ s = "STATION PROBLEM";
+ break;
+ case 0x0010:
+ s = "PRIMARY";
+ break;
+ case 0x0020:
+ s = "BACKUP";
+ break;
+ default:
+ return -EINVAL;
+ }
+ return snprintf(buf, PAGE_SIZE, "%s\n", s);
+}
+
+static DEVICE_ATTR_RO(io_controller_status);
+
+static ssize_t layer_fault_code_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int fc;
+ struct profi_priv *priv = dev_get_drvdata(dev);
+
+ fc = fbctrl_readw(priv->client, FSA_LAYER_FAULT_CODE);
+ if (fc < 0)
+ return fc;
+ return snprintf(buf, PAGE_SIZE, "%d\n", fc);
+}
+
+static DEVICE_ATTR_RO(layer_fault_code);
+
+static struct attribute *ctrl_attrs[] = {
+ &dev_attr_mac_addr.attr,
+ &dev_attr_start_defaults.attr,
+ &dev_attr_ip_addr.attr,
+ &dev_attr_subnet_mask.attr,
+ &dev_attr_gateway_addr.attr,
+ &dev_attr_hostname.attr,
+ &dev_attr_domainname.attr,
+ &dev_attr_network_link_on.attr,
+ &dev_attr_network_ip_in_use.attr,
+ &dev_attr_io_controller_status.attr,
+ &dev_attr_layer_status.attr,
+ &dev_attr_layer_fault_code.attr,
+ NULL
+};
+
+static struct attribute_group ctrl_group = { .attrs = ctrl_attrs };
+
+struct profi_open_file {
+ struct profi_priv *priv;
+ int event;
+};
+
+static int profi_open(struct inode *node, struct file *filp)
+{
+ struct profi_open_file *of;
+ struct profi_priv *priv = container_of(filp->private_data,
+ struct profi_priv, misc);
+
+ of = kzalloc(sizeof(*of), GFP_KERNEL);
+ if (!of)
+ return -ENOMEM;
+ of->priv = priv;
+ filp->private_data = of;
+ atomic_inc(&priv->refcount);
+ return 0;
+}
+
+static int profi_release(struct inode *node, struct file *filp)
+{
+ struct profi_open_file *of = filp->private_data;
+ struct profi_priv *priv = of->priv;
+
+ kfree(of);
+ if (!atomic_dec_and_test(&priv->refcount))
+ return 0;
+ return profinet_disable(priv);
+}
+
+static long profi_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct profi_open_file *of = filp->private_data;
+ struct profi_priv *priv = of->priv;
+ void __user *argp = (void __user *)arg;
+ struct ProfinetConfig config;
+
+ if (_IOC_TYPE(cmd) != PROFINET_IOC_MAGIC)
+ return -EINVAL;
+ if (!(_IOC_DIR(cmd) & _IOC_WRITE))
+ return -EINVAL;
+ switch (cmd) {
+ case PROFINET_IOCSETCONFIG:
+ if (copy_from_user(&config, argp, sizeof(config)))
+ return -EFAULT;
+ return profinet_enable(priv, &config);
+ default:
+ break;
+ }
+ return -ENOTTY;
+}
+
+static ssize_t
+profi_read(struct file *filp, char __user *buf, size_t size,
+ loff_t *offset)
+{
+ struct profi_open_file *of = filp->private_data;
+ struct profi_priv *priv = of->priv;
+
+ return anybuss_read_output(priv->client, &of->event, buf, size,
+ offset);
+}
+
+static ssize_t
+profi_write(struct file *filp, const char __user *buf, size_t size,
+ loff_t *offset)
+{
+ struct profi_open_file *of = filp->private_data;
+ struct profi_priv *priv = of->priv;
+
+ return anybuss_write_input(priv->client, buf, size, offset);
+}
+
+static unsigned int profi_poll(struct file *filp, poll_table *wait)
+{
+ struct profi_open_file *of = filp->private_data;
+ struct profi_priv *priv = of->priv;
+
+ return anybuss_poll(priv->client, of->event, filp, wait);
+}
+
+static const struct file_operations fops = {
+ .open = profi_open,
+ .release = profi_release,
+ .read = profi_read,
+ .write = profi_write,
+ .unlocked_ioctl = profi_ioctl,
+ .poll = profi_poll,
+ .llseek = generic_file_llseek,
+ .owner = THIS_MODULE,
+};
+
+static DEFINE_IDA(profi_index_ida);
+
+static int profinet_probe(struct anybuss_client *client)
+{
+ struct profi_priv *priv;
+ struct device *dev = &client->dev;
+ int err;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ atomic_set(&priv->refcount, 0);
+ mutex_init(&priv->enable_lock);
+ priv->client = client;
+ priv->misc.minor = MISC_DYNAMIC_MINOR;
+ priv->id = ida_simple_get(&profi_index_ida, 0, 0, GFP_KERNEL);
+ if (priv->id < 0)
+ return priv->id;
+ snprintf(priv->node_name, sizeof(priv->node_name), "profinet%d",
+ priv->id);
+ priv->misc.name = priv->node_name;
+ priv->misc.fops = &fops;
+ priv->misc.parent = client->dev.parent;
+ err = misc_register(&priv->misc);
+ if (err < 0) {
+ dev_err(dev, "could not register device (%d)", err);
+ goto err_ida;
+ }
+ priv->dev = priv->misc.this_device;
+ dev_set_drvdata(priv->dev, priv);
+ err = sysfs_create_group(&priv->dev->kobj, &ctrl_group);
+ if (err < 0) {
+ dev_err(dev, "could not create sysfs group (%d)", err);
+ goto err_register;
+ }
+ dev_info(priv->dev, "detected on %s", dev_name(&client->dev));
+ anybuss_set_drvdata(client, priv);
+ return 0;
+err_register:
+ misc_deregister(&priv->misc);
+err_ida:
+ ida_simple_remove(&profi_index_ida, priv->id);
+ return err;
+}
+
+static int profinet_remove(struct anybuss_client *client)
+{
+ struct profi_priv *priv = anybuss_get_drvdata(client);
+
+ sysfs_remove_group(&priv->dev->kobj, &ctrl_group);
+ misc_deregister(&priv->misc);
+ ida_simple_remove(&profi_index_ida, priv->id);
+ return 0;
+}
+
+static struct anybuss_client_driver profinet_driver = {
+ .probe = profinet_probe,
+ .remove = profinet_remove,
+ .driver = {
+ .name = "hms-profinet",
+ .owner = THIS_MODULE,
+ },
+ .fieldbus_type = 0x0089,
+};
+
+static int __init profinet_init(void)
+{
+ return anybuss_client_driver_register(&profinet_driver);
+}
+module_init(profinet_init);
+
+static void __exit profinet_exit(void)
+{
+ return anybuss_client_driver_unregister(&profinet_driver);
+}
+module_exit(profinet_exit);
+
+MODULE_AUTHOR("Sven Van Asbroeck <svendev@xxxxxxxx>");
+MODULE_DESCRIPTION("HMS Profinet IRT Driver (Anybus-S)");
+MODULE_LICENSE("GPL v2");
diff --git a/include/uapi/linux/hms-common.h b/include/uapi/linux/hms-common.h
new file mode 100644
index 000000000000..4b69963a3863
--- /dev/null
+++ b/include/uapi/linux/hms-common.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2018 Archronix Corp. All Rights Reserved.
+ *
+ */
+
+#ifndef _UAPILINUX_HMSCOMMON_H_
+#define _UAPILINUX_HMSCOMMON_H_
+
+#define HMS_SMA_CLEAR 0
+#define HMS_SMA_FREEZE 1
+#define HMS_SMA_SET 2
+
+#endif /* _UAPILINUX_HMSCOMMON_H_ */
diff --git a/include/uapi/linux/hms-profinet.h b/include/uapi/linux/hms-profinet.h
new file mode 100644
index 000000000000..beed797191f2
--- /dev/null
+++ b/include/uapi/linux/hms-profinet.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2018 Archronix Corp. All Rights Reserved.
+ *
+ */
+
+#ifndef _UAPILINUX_PROFINET_H_
+#define _UAPILINUX_PROFINET_H_
+
+#include <asm/types.h>
+#include <linux/hms-common.h>
+
+#define PROFI_CFG_STRLEN 64
+
+struct ProfinetConfig {
+ struct {
+ /* addresses IN NETWORK ORDER! */
+ __u32 ip_addr;
+ __u32 subnet_msk;
+ __u32 gateway_addr;
+ __u8 is_valid:1;
+ } eth;
+ struct {
+ __u16 vendorid, deviceid;
+ __u8 is_valid:1;
+ } dev_id;
+ struct {
+ char name[PROFI_CFG_STRLEN];
+ __u8 is_valid:1;
+ } station_name;
+ struct {
+ char name[PROFI_CFG_STRLEN];
+ __u8 is_valid:1;
+ } station_type;
+ struct {
+ __u8 addr[6];
+ __u8 is_valid:1;
+ } mac_addr;
+ struct {
+ char hostname[PROFI_CFG_STRLEN];
+ char domainname[PROFI_CFG_STRLEN];
+ __u8 is_valid:1;
+ } host_domain;
+ struct {
+ __u8 enable:1;
+ __u8 is_valid:1;
+ } hicp;
+ struct {
+ __u8 enable:1;
+ __u8 is_valid:1;
+ } web_server;
+ struct {
+ __u8 disable:1;
+ } ftp_server;
+ struct {
+ __u8 enable:1;
+ } global_admin_mode;
+ struct {
+ __u8 disable:1;
+ } vfs;
+ struct {
+ /* one of HMS_SMA_CLEAR/FREEZE/SET */
+ int action;
+ __u8 is_valid:1;
+ } stop_mode;
+ struct {
+ char description[PROFI_CFG_STRLEN];
+ __u8 is_valid:1;
+ } snmp_system_descr;
+ struct {
+ char description[PROFI_CFG_STRLEN];
+ __u8 is_valid:1;
+ } snmp_iface_descr;
+ struct {
+ char description[PROFI_CFG_STRLEN];
+ __u8 is_valid:1;
+ } mib2_system_descr;
+ struct {
+ char contact[PROFI_CFG_STRLEN];
+ __u8 is_valid:1;
+ } mib2_system_contact;
+ struct {
+ char location[PROFI_CFG_STRLEN];
+ __u8 is_valid:1;
+ } mib2_system_location;
+ /*
+ * use non-volatile defaults for any properties not specified.
+ * when in doubt, keep this OFF.
+ */
+ __u8 use_nv_defaults:1;
+};
+
+#define PROFINET_IOC_MAGIC 'l'
+
+/*
+ * Configures profinet according to the ProfinetConfig structure, and
+ * switches the card on if it was previously off.
+ */
+#define PROFINET_IOCSETCONFIG _IOW(PROFINET_IOC_MAGIC, 0x80,\
+ struct ProfinetConfig)
+
+#endif /* _UAPILINUX_PROFINET_H_ */
--
2.17.1