linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Sven Van Asbroeck <svendev@arcx.com>
To: <svendev@arcx.com>, <lee.jones@linaro.org>, <robh+dt@kernel.org>,
	<mark.rutland@arm.com>, <afaerber@suse.de>, <treding@nvidia.com>,
	<david@lechnology.com>, <noralf@tronnes.org>, <johan@kernel.org>,
	<monstr@monstr.eu>, <michal.vokac@ysoft.com>, <arnd@arndb.de>,
	<gregkh@linuxfoundation.org>, <john.garry@huawei.com>,
	<andriy.shevchenko@linux.intel.com>, <geert+renesas@glider.be>,
	<robin.murphy@arm.com>, <paul.gortmaker@windriver.com>,
	<sebastien.bourdelin@savoirfairelinux.com>, <icenowy@aosc.io>,
	<yuanzhichang@hisilicon.com>, <stuyoder@gmail.com>,
	<linus.walleij@linaro.org>, <maxime.ripard@bootlin.com>,
	<bogdan.purcareata@nxp.com>
Cc: <linux-kernel@vger.kernel.org>, <devicetree@vger.kernel.org>
Subject: [PATCH anybus v1 4/4] misc: support HMS Profinet IRT industrial controller.
Date: Wed, 24 Oct 2018 10:24:56 -0400	[thread overview]
Message-ID: <20181024142456.10084-5-svendev@arcx.com> (raw)
In-Reply-To: <20181024142456.10084-1-svendev@arcx.com>

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@arcx.com>
---
 drivers/misc/Kconfig              |  11 +
 drivers/misc/Makefile             |   1 +
 drivers/misc/hms-profinet.c       | 747 ++++++++++++++++++++++++++++++
 include/uapi/linux/hms-common.h   |  14 +
 include/uapi/linux/hms-profinet.h | 101 ++++
 5 files changed, 874 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/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 3726eacdf65d..377fea2e3003 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -406,6 +406,17 @@ 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
+	default n
+	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 af22bbc3d00c..dcf0468187b6 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..7338a49cbddd
--- /dev/null
+++ b/drivers/misc/hms-profinet.c
@@ -0,0 +1,747 @@
+// 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@arcx.com>");
+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..4ae5eab8ab43
--- /dev/null
+++ b/include/uapi/linux/hms-profinet.h
@@ -0,0 +1,101 @@
+/* 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, 1,\
+						struct ProfinetConfig)
+
+#endif /* _UAPILINUX_PROFINET_H_ */
-- 
2.17.1


  parent reply	other threads:[~2018-10-24 14:40 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-10-24 14:24 [PATCH anybus v1 0/4] Support HMS Profinet Card over Anybus Sven Van Asbroeck
2018-10-24 14:24 ` [PATCH anybus v1 1/4] mfd: support the Arcx anybus bridge Sven Van Asbroeck
2018-10-24 15:58   ` Randy Dunlap
2018-10-26  8:34   ` Lee Jones
2018-10-26 13:40     ` Sven Van Asbroeck
2018-10-24 14:24 ` [PATCH anybus v1 2/4] dt-bindings: anybus-bridge: document devicetree binding Sven Van Asbroeck
2018-10-25  0:06   ` Rob Herring
2018-10-25  5:19   ` Lee Jones
2018-10-25 10:16   ` Linus Walleij
2018-10-24 14:24 ` [PATCH anybus v1 3/4] bus: support HMS Anybus-S bus Sven Van Asbroeck
2018-10-24 15:58   ` Randy Dunlap
2018-10-25 11:08   ` Linus Walleij
2018-10-24 14:24 ` Sven Van Asbroeck [this message]
2018-10-24 15:58   ` [PATCH anybus v1 4/4] misc: support HMS Profinet IRT industrial controller Randy Dunlap
2018-10-25  9:18 ` [PATCH anybus v1 0/4] Support HMS Profinet Card over Anybus Andy Shevchenko
2018-11-05 14:49   ` Sven Van Asbroeck

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20181024142456.10084-5-svendev@arcx.com \
    --to=svendev@arcx.com \
    --cc=afaerber@suse.de \
    --cc=andriy.shevchenko@linux.intel.com \
    --cc=arnd@arndb.de \
    --cc=bogdan.purcareata@nxp.com \
    --cc=david@lechnology.com \
    --cc=devicetree@vger.kernel.org \
    --cc=geert+renesas@glider.be \
    --cc=gregkh@linuxfoundation.org \
    --cc=icenowy@aosc.io \
    --cc=johan@kernel.org \
    --cc=john.garry@huawei.com \
    --cc=lee.jones@linaro.org \
    --cc=linus.walleij@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=maxime.ripard@bootlin.com \
    --cc=michal.vokac@ysoft.com \
    --cc=monstr@monstr.eu \
    --cc=noralf@tronnes.org \
    --cc=paul.gortmaker@windriver.com \
    --cc=robh+dt@kernel.org \
    --cc=robin.murphy@arm.com \
    --cc=sebastien.bourdelin@savoirfairelinux.com \
    --cc=stuyoder@gmail.com \
    --cc=treding@nvidia.com \
    --cc=yuanzhichang@hisilicon.com \
    --subject='Re: [PATCH anybus v1 4/4] misc: support HMS Profinet IRT industrial controller.' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).