diff options
author | Minda Chen <minda.chen@starfivetech.com> | 2023-06-27 15:23:39 +0300 |
---|---|---|
committer | Minda Chen <minda.chen@starfivetech.com> | 2023-11-06 14:47:35 +0300 |
commit | c606d37f6e41251c0de22144f07d2d0c17e96cd4 (patch) | |
tree | f798e13fe60929cadf77392fcb91bc87787a5f69 | |
parent | 5322763afa20323701d14a95e2e07a5db3ae48b7 (diff) | |
download | linux-c606d37f6e41251c0de22144f07d2d0c17e96cd4.tar.xz |
add ethercat codes.
Signed-off-by: Minda Chen <minda.chen@starfivetech.com>
94 files changed, 42449 insertions, 0 deletions
diff --git a/include/net/ecdev.h b/include/net/ecdev.h new file mode 100644 index 000000000000..8da467cd6641 --- /dev/null +++ b/include/net/ecdev.h @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * + * EtherCAT interface for EtherCAT device drivers. + * + * \defgroup DeviceInterface EtherCAT Device Interface + * + * Master interface for EtherCAT-capable network device drivers. Through the + * EtherCAT device interface, EtherCAT-capable network device drivers are able + * to connect their device(s) to the master, pass received frames and notify + * the master about status changes. The master on his part, can send his + * frames through connected devices. + */ + +/*****************************************************************************/ + +#ifndef __ECDEV_H__ +#define __ECDEV_H__ + +#include <linux/netdevice.h> + +/*****************************************************************************/ + +struct ec_device; +typedef struct ec_device ec_device_t; /**< \see ec_device */ + +/** Device poll function type. + */ +typedef void (*ec_pollfunc_t)(struct net_device *); + +/****************************************************************************** + * Offering/withdrawal functions + *****************************************************************************/ + +ec_device_t *ecdev_offer(struct net_device *net_dev, ec_pollfunc_t poll, + struct module *module); +void ecdev_withdraw(ec_device_t *device); + +/****************************************************************************** + * Device methods + *****************************************************************************/ + +int ecdev_open(ec_device_t *device); +void ecdev_close(ec_device_t *device); +void ecdev_receive(ec_device_t *device, const void *data, size_t size); +void ecdev_set_link(ec_device_t *device, uint8_t state); +uint8_t ecdev_get_link(const ec_device_t *device); + +/*****************************************************************************/ + +#endif diff --git a/net/Kconfig b/net/Kconfig index 074472dfa94a..1374de4f9def 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -387,6 +387,7 @@ source "net/ceph/Kconfig" source "net/nfc/Kconfig" source "net/psample/Kconfig" source "net/ife/Kconfig" +source "net/ethercat/Kconfig" config LWTUNNEL bool "Network light weight tunnels" diff --git a/net/Makefile b/net/Makefile index fbfeb8a0bb37..2ace88ddf75b 100644 --- a/net/Makefile +++ b/net/Makefile @@ -79,3 +79,4 @@ obj-$(CONFIG_NET_NCSI) += ncsi/ obj-$(CONFIG_XDP_SOCKETS) += xdp/ obj-$(CONFIG_MPTCP) += mptcp/ obj-$(CONFIG_MCTP) += mctp/ +obj-y += ethercat/ diff --git a/net/ethercat/Kconfig b/net/ethercat/Kconfig new file mode 100644 index 000000000000..62ad882da950 --- /dev/null +++ b/net/ethercat/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ethercat +# + +config ETHERCAT + tristate "ETHERCAT master driver" + select ETHERCAT_MASTER + default n + help + Support for ethercat master driver. + If unsure, say Y. + +source "net/ethercat/master/Kconfig" diff --git a/net/ethercat/Makefile b/net/ethercat/Makefile new file mode 100644 index 000000000000..ff0760b802be --- /dev/null +++ b/net/ethercat/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the ethercat driver. +# + +obj-y += master/ +obj-$(CONFIG_ETHERCAT) += ethercat.o +ethercat-y += generic.o diff --git a/net/ethercat/config.h b/net/ethercat/config.h new file mode 100644 index 000000000000..385071ae2194 --- /dev/null +++ b/net/ethercat/config.h @@ -0,0 +1,97 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Debug interfaces enabled */ +/* #undef EC_DEBUG_IF */ + +/* Debug ring enabled */ +/* #undef EC_DEBUG_RING */ + +/* EoE support enabled */ +#define EC_EOE 1 + +/* Use CPU timestamp counter */ +#define EC_HAVE_CYCLES 1 + +/* Use vendor id / product code wildcards */ +/* #undef EC_IDENT_WILDCARDS */ + +/* Max. number of Ethernet devices per master */ +#define EC_MAX_NUM_DEVICES 1 + +/* Read alias adresses from register */ +/* #undef EC_REGALIAS */ + +/* RTDM interface enabled */ +/* #undef EC_RTDM */ + +/* Output to syslog in RT context */ +#define EC_RT_SYSLOG 1 + +/* Assign SII to PDI */ +#define EC_SII_ASSIGN 1 + +/* Use hrtimer for scheduling */ +#define EC_USE_HRTIMER 1 + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "ethercat" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "fp@igh-essen.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "ethercat" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "ethercat 1.5.2" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "ethercat" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "1.5.2" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "1.5.2" + +#define cpu_khz (1500000) diff --git a/net/ethercat/generic.c b/net/ethercat/generic.c new file mode 100644 index 000000000000..4c7f02dcaebc --- /dev/null +++ b/net/ethercat/generic.c @@ -0,0 +1,476 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT generic Ethernet device module. + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/version.h> +#include <linux/if_arp.h> /* ARPHRD_ETHER */ +#include <linux/etherdevice.h> + +#include "globals.h" +#include <net/ecdev.h> + +#define PFX "ec_generic: " + +#define ETH_P_ETHERCAT 0x88A4 + +#define EC_GEN_RX_BUF_SIZE 1600 + +/*****************************************************************************/ + +int __init ec_gen_init_module(void); +void __exit ec_gen_cleanup_module(void); + +/*****************************************************************************/ + +/** \cond */ + +MODULE_AUTHOR("Florian Pose <fp@igh-essen.com>"); +MODULE_DESCRIPTION("EtherCAT master generic Ethernet device module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(EC_MASTER_VERSION); + +/** \endcond */ + +struct list_head generic_devices; + +typedef struct { + struct list_head list; + struct net_device *netdev; + struct net_device *used_netdev; + struct socket *socket; + ec_device_t *ecdev; + uint8_t *rx_buf; +} ec_gen_device_t; + +typedef struct { + struct list_head list; + struct net_device *netdev; + char name[IFNAMSIZ]; + int ifindex; + uint8_t dev_addr[ETH_ALEN]; +} ec_gen_interface_desc_t; + +int ec_gen_device_open(ec_gen_device_t *); +int ec_gen_device_stop(ec_gen_device_t *); +int ec_gen_device_start_xmit(ec_gen_device_t *, struct sk_buff *); +void ec_gen_device_poll(ec_gen_device_t *); + +/*****************************************************************************/ + +static int ec_gen_netdev_open(struct net_device *dev) +{ + ec_gen_device_t *gendev = *((ec_gen_device_t **) netdev_priv(dev)); + return ec_gen_device_open(gendev); +} + +/*****************************************************************************/ + +static int ec_gen_netdev_stop(struct net_device *dev) +{ + ec_gen_device_t *gendev = *((ec_gen_device_t **) netdev_priv(dev)); + return ec_gen_device_stop(gendev); +} + +/*****************************************************************************/ + +static int ec_gen_netdev_start_xmit( + struct sk_buff *skb, + struct net_device *dev + ) +{ + ec_gen_device_t *gendev = *((ec_gen_device_t **) netdev_priv(dev)); + return ec_gen_device_start_xmit(gendev, skb); +} + +/*****************************************************************************/ + +void ec_gen_poll(struct net_device *dev) +{ + ec_gen_device_t *gendev = *((ec_gen_device_t **) netdev_priv(dev)); + ec_gen_device_poll(gendev); +} + +/*****************************************************************************/ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) +static const struct net_device_ops ec_gen_netdev_ops = { + .ndo_open = ec_gen_netdev_open, + .ndo_stop = ec_gen_netdev_stop, + .ndo_start_xmit = ec_gen_netdev_start_xmit, +}; +#endif + +/*****************************************************************************/ + +/** Init generic device. + */ +int ec_gen_device_init( + ec_gen_device_t *dev + ) +{ + ec_gen_device_t **priv; + char null = 0x00; + + dev->ecdev = NULL; + dev->socket = NULL; + dev->rx_buf = NULL; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) + dev->netdev = alloc_netdev(sizeof(ec_gen_device_t *), &null, + NET_NAME_UNKNOWN, ether_setup); +#else + dev->netdev = alloc_netdev(sizeof(ec_gen_device_t *), &null, ether_setup); +#endif + if (!dev->netdev) { + return -ENOMEM; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + dev->netdev->netdev_ops = &ec_gen_netdev_ops; +#else + dev->netdev->open = ec_gen_netdev_open; + dev->netdev->stop = ec_gen_netdev_stop; + dev->netdev->hard_start_xmit = ec_gen_netdev_start_xmit; +#endif + + priv = netdev_priv(dev->netdev); + *priv = dev; + + return 0; +} + +/*****************************************************************************/ + +/** Clear generic device. + */ +void ec_gen_device_clear( + ec_gen_device_t *dev + ) +{ + if (dev->ecdev) { + ecdev_close(dev->ecdev); + ecdev_withdraw(dev->ecdev); + } + if (dev->socket) { + sock_release(dev->socket); + } + free_netdev(dev->netdev); + + if (dev->rx_buf) { + kfree(dev->rx_buf); + } +} + +/*****************************************************************************/ + +/** Creates a network socket. + */ +int ec_gen_device_create_socket( + ec_gen_device_t *dev, + ec_gen_interface_desc_t *desc + ) +{ + int ret; + struct sockaddr_ll sa; + + dev->rx_buf = kmalloc(EC_GEN_RX_BUF_SIZE, GFP_KERNEL); + if (!dev->rx_buf) { + return -ENOMEM; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) + ret = sock_create_kern(&init_net, PF_PACKET, SOCK_RAW, + htons(ETH_P_ETHERCAT), &dev->socket); +#else + ret = sock_create_kern(PF_PACKET, SOCK_RAW, htons(ETH_P_ETHERCAT), + &dev->socket); +#endif + if (ret) { + printk(KERN_ERR PFX "Failed to create socket (ret = %i).\n", ret); + return ret; + } + + printk(KERN_ERR PFX "Binding socket to interface %i (%s).\n", + desc->ifindex, desc->name); + + memset(&sa, 0x00, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(ETH_P_ETHERCAT); + sa.sll_ifindex = desc->ifindex; + ret = kernel_bind(dev->socket, (struct sockaddr *) &sa, sizeof(sa)); + if (ret) { + printk(KERN_ERR PFX "Failed to bind() socket to interface" + " (ret = %i).\n", ret); + sock_release(dev->socket); + dev->socket = NULL; + return ret; + } + + return 0; +} + +/*****************************************************************************/ + +/** Offer generic device to master. + */ +int ec_gen_device_offer( + ec_gen_device_t *dev, + ec_gen_interface_desc_t *desc + ) +{ + int ret = 0; + + dev->used_netdev = desc->netdev; + memcpy(dev->netdev->dev_addr, desc->dev_addr, ETH_ALEN); + + dev->ecdev = ecdev_offer(dev->netdev, ec_gen_poll, THIS_MODULE); + if (dev->ecdev) { + if (ec_gen_device_create_socket(dev, desc)) { + ecdev_withdraw(dev->ecdev); + dev->ecdev = NULL; + } else if (ecdev_open(dev->ecdev)) { + ecdev_withdraw(dev->ecdev); + dev->ecdev = NULL; + } else { + ecdev_set_link(dev->ecdev, netif_carrier_ok(dev->used_netdev)); // FIXME + ret = 1; + } + } + + return ret; +} + +/*****************************************************************************/ + +/** Open the device. + */ +int ec_gen_device_open( + ec_gen_device_t *dev + ) +{ + return 0; +} + +/*****************************************************************************/ + +/** Stop the device. + */ +int ec_gen_device_stop( + ec_gen_device_t *dev + ) +{ + return 0; +} + +/*****************************************************************************/ + +int ec_gen_device_start_xmit( + ec_gen_device_t *dev, + struct sk_buff *skb + ) +{ + struct msghdr msg; + struct kvec iov; + size_t len = skb->len; + int ret; + + ecdev_set_link(dev->ecdev, netif_carrier_ok(dev->used_netdev)); + + iov.iov_base = skb->data; + iov.iov_len = len; + memset(&msg, 0, sizeof(msg)); + + ret = kernel_sendmsg(dev->socket, &msg, &iov, 1, len); + + return ret == len ? NETDEV_TX_OK : NETDEV_TX_BUSY; +} + +/*****************************************************************************/ + +/** Polls the device. + */ +void ec_gen_device_poll( + ec_gen_device_t *dev + ) +{ + struct msghdr msg; + struct kvec iov; + int ret, budget = 10; // FIXME + + ecdev_set_link(dev->ecdev, netif_carrier_ok(dev->used_netdev)); + + do { + iov.iov_base = dev->rx_buf; + iov.iov_len = EC_GEN_RX_BUF_SIZE; + memset(&msg, 0, sizeof(msg)); + + ret = kernel_recvmsg(dev->socket, &msg, &iov, 1, iov.iov_len, + MSG_DONTWAIT); + if (ret > 0) { + ecdev_receive(dev->ecdev, dev->rx_buf, ret); + } else if (ret < 0) { + break; + } + budget--; + } while (budget); +} + +/*****************************************************************************/ + +/** Offer device. + */ +int offer_device( + ec_gen_interface_desc_t *desc + ) +{ + ec_gen_device_t *gendev; + int ret = 0; + + gendev = kmalloc(sizeof(ec_gen_device_t), GFP_KERNEL); + if (!gendev) { + return -ENOMEM; + } + + ret = ec_gen_device_init(gendev); + if (ret) { + kfree(gendev); + return ret; + } + + if (ec_gen_device_offer(gendev, desc)) { + list_add_tail(&gendev->list, &generic_devices); + } else { + ec_gen_device_clear(gendev); + kfree(gendev); + } + + return ret; +} + +/*****************************************************************************/ + +/** Clear devices. + */ +void clear_devices(void) +{ + ec_gen_device_t *gendev, *next; + + list_for_each_entry_safe(gendev, next, &generic_devices, list) { + list_del(&gendev->list); + ec_gen_device_clear(gendev); + kfree(gendev); + } +} + +/*****************************************************************************/ + +/** Module initialization. + * + * Initializes \a master_count masters. + * \return 0 on success, else < 0 + */ +int __init ec_gen_init_module(void) +{ + int ret = 0; + struct list_head descs; + struct net_device *netdev; + ec_gen_interface_desc_t *desc, *next; + + printk(KERN_INFO PFX "EtherCAT master generic Ethernet device module %s\n", + EC_MASTER_VERSION); + + INIT_LIST_HEAD(&generic_devices); + INIT_LIST_HEAD(&descs); + + read_lock(&dev_base_lock); + for_each_netdev(&init_net, netdev) { + if (netdev->type != ARPHRD_ETHER) + continue; + desc = kmalloc(sizeof(ec_gen_interface_desc_t), GFP_ATOMIC); + if (!desc) { + ret = -ENOMEM; + read_unlock(&dev_base_lock); + goto out_err; + } + strncpy(desc->name, netdev->name, IFNAMSIZ); + desc->netdev = netdev; + desc->ifindex = netdev->ifindex; + memcpy(desc->dev_addr, netdev->dev_addr, ETH_ALEN); + list_add_tail(&desc->list, &descs); + } + read_unlock(&dev_base_lock); + + list_for_each_entry_safe(desc, next, &descs, list) { + ret = offer_device(desc); + if (ret) { + goto out_err; + } + kfree(desc); + } + return ret; + +out_err: + list_for_each_entry_safe(desc, next, &descs, list) { + list_del(&desc->list); + kfree(desc); + } + clear_devices(); + return ret; +} + +/*****************************************************************************/ + +/** Module cleanup. + * + * Clears all master instances. + */ +void __exit ec_gen_cleanup_module(void) +{ + clear_devices(); + printk(KERN_INFO PFX "Unloading.\n"); +} + +/*****************************************************************************/ + +/** \cond */ + +module_init(ec_gen_init_module); +module_exit(ec_gen_cleanup_module); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/globals.h b/net/ethercat/globals.h new file mode 100644 index 000000000000..b5927729a639 --- /dev/null +++ b/net/ethercat/globals.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Global definitions and macros. +*/ + +/*****************************************************************************/ + +#ifndef __EC_GLOBALS_H__ +#define __EC_GLOBALS_H__ + +#include "config.h" + +/****************************************************************************** + * Overall macros + *****************************************************************************/ + +/** Helper macro for EC_STR(), literates a macro argument. + * + * \param X argument to literate. + */ +#define EC_LIT(X) #X + +/** Converts a macro argument to a string. + * + * \param X argument to stringify. + */ +#define EC_STR(X) EC_LIT(X) + +/** Master version string + */ +#define EC_MASTER_VERSION VERSION " " EC_STR(REV) + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/include/ecrt.h b/net/ethercat/include/ecrt.h new file mode 100644 index 000000000000..9d673ecb278a --- /dev/null +++ b/net/ethercat/include/ecrt.h @@ -0,0 +1,2340 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT master userspace library. + * + * The IgH EtherCAT master userspace library is free software; you can + * redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation; version 2.1 + * of the License. + * + * The IgH EtherCAT master userspace library is distributed in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the IgH EtherCAT master userspace library. If not, see + * <http://www.gnu.org/licenses/>. + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * + * EtherCAT master application interface. + * + * \defgroup ApplicationInterface EtherCAT Application Interface + * + * EtherCAT interface for realtime applications. This interface is designed + * for realtime modules that want to use EtherCAT. There are functions to + * request a master, to map process data, to communicate with slaves via CoE + * and to configure and activate the bus. + * + * Changes in version 1.5.2: + * + * - Added redundancy_active flag to ec_domain_state_t. + * - Added ecrt_master_link_state() method and ec_master_link_state_t to query + * the state of a redundant link. + * - Added the EC_HAVE_REDUNDANCY define, to check, if the interface contains + * redundancy features. + * - Added ecrt_sdo_request_index() to change SDO index and subindex after + * handler creation. + * - Added interface for retrieving CoE emergency messages, i. e. + * ecrt_slave_config_emerg_size(), ecrt_slave_config_emerg_pop(), + * ecrt_slave_config_emerg_clear(), ecrt_slave_config_emerg_overruns() and + * the defines EC_HAVE_EMERGENCY and EC_COE_EMERGENCY_MSG_SIZE. + * - Added interface for direct EtherCAT register access: Added data type + * ec_reg_request_t and methods ecrt_slave_config_create_reg_request(), + * ecrt_reg_request_data(), ecrt_reg_request_state(), + * ecrt_reg_request_write(), ecrt_reg_request_read() and the feature flag + * EC_HAVE_REG_ACCESS. + * - Added method to select the reference clock, + * ecrt_master_select_reference_clock() and the feature flag + * EC_HAVE_SELECT_REF_CLOCK to check, if the method is available. + * - Added method to get the reference clock time, + * ecrt_master_reference_clock_time() and the feature flag + * EC_HAVE_REF_CLOCK_TIME to have the possibility to synchronize the master + * clock to the reference clock. + * - Changed the data types of the shift times in ecrt_slave_config_dc() to + * int32_t to correctly display negative shift times. + * - Added ecrt_slave_config_reg_pdo_entry_pos() and the feature flag + * EC_HAVE_REG_BY_POS for registering PDO entries with non-unique indices + * via their positions in the mapping. + * + * Changes in version 1.5: + * + * - Added the distributed clocks feature and the respective method + * ecrt_slave_config_dc() to configure a slave for cyclic operation, and + * ecrt_master_application_time(), ecrt_master_sync_reference_clock() and + * ecrt_master_sync_slave_clocks() for offset and drift compensation. The + * EC_TIMEVAL2NANO() macro can be used for epoch time conversion, while the + * ecrt_master_sync_monitor_queue() and ecrt_master_sync_monitor_process() + * methods can be used to monitor the synchrony. + * - Improved the callback mechanism. ecrt_master_callbacks() now takes two + * callback functions for sending and receiving datagrams. + * ecrt_master_send_ext() is used to execute the sending of non-application + * datagrams. + * - Added watchdog configuration (method ecrt_slave_config_watchdog(), + * #ec_watchdog_mode_t, \a watchdog_mode parameter in ec_sync_info_t and + * ecrt_slave_config_sync_manager()). + * - Added ecrt_slave_config_complete_sdo() method to download an SDO during + * configuration via CompleteAccess. + * - Added ecrt_master_deactivate() to remove the bus configuration. + * - Added ecrt_open_master() and ecrt_master_reserve() separation for + * userspace. + * - Added bus information interface (methods ecrt_master(), + * ecrt_master_get_slave(), ecrt_master_get_sync_manager(), + * ecrt_master_get_pdo() and ecrt_master_get_pdo_entry()) to get information + * about the currently connected slaves and the PDO entries provided. + * - Added ecrt_master_sdo_download(), ecrt_master_sdo_download_complete() and + * ecrt_master_sdo_upload() methods to let an application transfer SDOs + * before activating the master. + * - Changed the meaning of the negative return values of + * ecrt_slave_config_reg_pdo_entry() and ecrt_slave_config_sdo*(). + * - Implemented the Vendor-specific over EtherCAT mailbox protocol. See + * ecrt_slave_config_create_voe_handler(). + * - Renamed ec_sdo_request_state_t to #ec_request_state_t, because it is also + * used by VoE handlers. + * - Removed 'const' from argument of ecrt_sdo_request_state(), because the + * userspace library has to modify object internals. + * - Added 64-bit data access macros. + * - Added ecrt_slave_config_idn() method for storing SoE IDN configurations, + * and ecrt_master_read_idn() and ecrt_master_write_idn() to read/write IDNs + * ad-hoc via the user-space library. + * - Added ecrt_master_reset() to initiate retrying to configure slaves. + * + * @{ + */ + +/*****************************************************************************/ + +#ifndef __ECRT_H__ +#define __ECRT_H__ + +#ifdef __KERNEL__ +#include <asm/byteorder.h> +#include <linux/types.h> +#include <linux/time.h> +#else +#include <stdlib.h> // for size_t +#include <stdint.h> +#include <sys/time.h> // for struct timeval +#endif + +/****************************************************************************** + * Global definitions + *****************************************************************************/ + +/** EtherCAT realtime interface major version number. + */ +#define ECRT_VER_MAJOR 1 + +/** EtherCAT realtime interface minor version number. + */ +#define ECRT_VER_MINOR 5 + +/** EtherCAT realtime interface version word generator. + */ +#define ECRT_VERSION(a, b) (((a) << 8) + (b)) + +/** EtherCAT realtime interface version word. + */ +#define ECRT_VERSION_MAGIC ECRT_VERSION(ECRT_VER_MAJOR, ECRT_VER_MINOR) + +/****************************************************************************** + * Feature flags + *****************************************************************************/ + +/** Defined, if the redundancy features are available. + * + * I. e. if the \a redundancy_active flag in ec_domain_state_t and the + * ecrt_master_link_state() method are available. + */ +#define EC_HAVE_REDUNDANCY + +/** Defined, if the CoE emergency ring feature is available. + * + * I. e. if the ecrt_slave_config_emerg_*() methods are available. + */ +#define EC_HAVE_EMERGENCY + +/** Defined, if the register access interface is available. + * + * I. e. if the methods ecrt_slave_config_create_reg_request(), + * ecrt_reg_request_data(), ecrt_reg_request_state(), ecrt_reg_request_write() + * and ecrt_reg_request_read() are available. + */ +#define EC_HAVE_REG_ACCESS + +/** Defined if the method ecrt_master_select_reference_clock() is available. + */ +#define EC_HAVE_SELECT_REF_CLOCK + +/** Defined if the method ecrt_master_reference_clock_time() is available. + */ +#define EC_HAVE_REF_CLOCK_TIME + +/** Defined if the method ecrt_slave_config_reg_pdo_entry_pos() is available. + */ +#define EC_HAVE_REG_BY_POS + +/** Defined if the method ecrt_master_sync_reference_clock_to() is available. + */ +#define EC_HAVE_SYNC_TO + +/*****************************************************************************/ + +/** End of list marker. + * + * This can be used with ecrt_slave_config_pdos(). + */ +#define EC_END ~0U + +/** Maximum number of sync managers per slave. + */ +#define EC_MAX_SYNC_MANAGERS 16 + +/** Maximum string length. + * + * Used in ec_slave_info_t. + */ +#define EC_MAX_STRING_LENGTH 64 + +/** Maximum number of slave ports. */ +#define EC_MAX_PORTS 4 + +/** Timeval to nanoseconds conversion. + * + * This macro converts a Unix epoch time to EtherCAT DC time. + * + * \see void ecrt_master_application_time() + * + * \param TV struct timeval containing epoch time. + */ +#define EC_TIMEVAL2NANO(TV) \ + (((TV).tv_sec - 946684800ULL) * 1000000000ULL + (TV).tv_usec * 1000ULL) + +/** Size of a CoE emergency message in byte. + * + * \see ecrt_slave_config_emerg_pop(). + */ +#define EC_COE_EMERGENCY_MSG_SIZE 8 + +/****************************************************************************** + * Data types + *****************************************************************************/ + +struct ec_master; +typedef struct ec_master ec_master_t; /**< \see ec_master */ + +struct ec_slave_config; +typedef struct ec_slave_config ec_slave_config_t; /**< \see ec_slave_config */ + +struct ec_domain; +typedef struct ec_domain ec_domain_t; /**< \see ec_domain */ + +struct ec_sdo_request; +typedef struct ec_sdo_request ec_sdo_request_t; /**< \see ec_sdo_request. */ + +struct ec_voe_handler; +typedef struct ec_voe_handler ec_voe_handler_t; /**< \see ec_voe_handler. */ + +struct ec_reg_request; +typedef struct ec_reg_request ec_reg_request_t; /**< \see ec_sdo_request. */ + +/*****************************************************************************/ + +/** Master state. + * + * This is used for the output parameter of ecrt_master_state(). + * + * \see ecrt_master_state(). + */ +typedef struct { + unsigned int slaves_responding; /**< Sum of responding slaves on all + Ethernet devices. */ + unsigned int al_states : 4; /**< Application-layer states of all slaves. + The states are coded in the lower 4 bits. + If a bit is set, it means that at least one + slave in the bus is in the corresponding + state: + - Bit 0: \a INIT + - Bit 1: \a PREOP + - Bit 2: \a SAFEOP + - Bit 3: \a OP */ + unsigned int link_up : 1; /**< \a true, if at least one Ethernet link is + up. */ +} ec_master_state_t; + +/*****************************************************************************/ + +/** Redundant link state. + * + * This is used for the output parameter of ecrt_master_link_state(). + * + * \see ecrt_master_link_state(). + */ +typedef struct { + unsigned int slaves_responding; /**< Sum of responding slaves on the given + link. */ + unsigned int al_states : 4; /**< Application-layer states of the slaves on + the given link. The states are coded in the + lower 4 bits. If a bit is set, it means + that at least one slave in the bus is in the + corresponding state: + - Bit 0: \a INIT + - Bit 1: \a PREOP + - Bit 2: \a SAFEOP + - Bit 3: \a OP */ + unsigned int link_up : 1; /**< \a true, if the given Ethernet link is up. + */ +} ec_master_link_state_t; + +/*****************************************************************************/ + +/** Slave configuration state. + * + * This is used as an output parameter of ecrt_slave_config_state(). + * + * \see ecrt_slave_config_state(). + */ +typedef struct { + unsigned int online : 1; /**< The slave is online. */ + unsigned int operational : 1; /**< The slave was brought into \a OP state + using the specified configuration. */ + unsigned int al_state : 4; /**< The application-layer state of the slave. + - 1: \a INIT + - 2: \a PREOP + - 4: \a SAFEOP + - 8: \a OP + + Note that each state is coded in a different + bit! */ +} ec_slave_config_state_t; + +/*****************************************************************************/ + +/** Master information. + * + * This is used as an output parameter of ecrt_master(). + * + * \see ecrt_master(). + */ +typedef struct { + unsigned int slave_count; /**< Number of slaves in the bus. */ + unsigned int link_up : 1; /**< \a true, if the network link is up. */ + uint8_t scan_busy; /**< \a true, while the master is scanning the bus */ + uint64_t app_time; /**< Application time. */ +} ec_master_info_t; + +/*****************************************************************************/ + +/** EtherCAT slave port descriptor. + */ +typedef enum { + EC_PORT_NOT_IMPLEMENTED, /**< Port is not implemented. */ + EC_PORT_NOT_CONFIGURED, /**< Port is not configured. */ + EC_PORT_EBUS, /**< Port is an E-Bus. */ + EC_PORT_MII /**< Port is a MII. */ +} ec_slave_port_desc_t; + +/*****************************************************************************/ + +/** EtherCAT slave port information. + */ +typedef struct { + uint8_t link_up; /**< Link detected. */ + uint8_t loop_closed; /**< Loop closed. */ + uint8_t signal_detected; /**< Detected signal on RX port. */ +} ec_slave_port_link_t; + +/*****************************************************************************/ + +/** Slave information. + * + * This is used as an output parameter of ecrt_master_get_slave(). + * + * \see ecrt_master_get_slave(). + */ +typedef struct { + uint16_t position; /**< Offset of the slave in the ring. */ + uint32_t vendor_id; /**< Vendor-ID stored on the slave. */ + uint32_t product_code; /**< Product-Code stored on the slave. */ + uint32_t revision_number; /**< Revision-Number stored on the slave. */ + uint32_t serial_number; /**< Serial-Number stored on the slave. */ + uint16_t alias; /**< The slaves alias if not equal to 0. */ + int16_t current_on_ebus; /**< Used current in mA. */ + struct { + ec_slave_port_desc_t desc; /**< Physical port type. */ + ec_slave_port_link_t link; /**< Port link state. */ + uint32_t receive_time; /**< Receive time on DC transmission delay + measurement. */ + uint16_t next_slave; /**< Ring position of next DC slave on that + port. */ + uint32_t delay_to_next_dc; /**< Delay [ns] to next DC slave. */ + } ports[EC_MAX_PORTS]; /**< Port information. */ + uint8_t al_state; /**< Current state of the slave. */ + uint8_t error_flag; /**< Error flag for that slave. */ + uint8_t sync_count; /**< Number of sync managers. */ + uint16_t sdo_count; /**< Number of SDOs. */ + char name[EC_MAX_STRING_LENGTH]; /**< Name of the slave. */ +} ec_slave_info_t; + +/*****************************************************************************/ + +/** Domain working counter interpretation. + * + * This is used in ec_domain_state_t. + */ +typedef enum { + EC_WC_ZERO = 0, /**< No registered process data were exchanged. */ + EC_WC_INCOMPLETE, /**< Some of the registered process data were + exchanged. */ + EC_WC_COMPLETE /**< All registered process data were exchanged. */ +} ec_wc_state_t; + +/*****************************************************************************/ + +/** Domain state. + * + * This is used for the output parameter of ecrt_domain_state(). + */ +typedef struct { + unsigned int working_counter; /**< Value of the last working counter. */ + ec_wc_state_t wc_state; /**< Working counter interpretation. */ + unsigned int redundancy_active; /**< Redundant link is in use. */ +} ec_domain_state_t; + +/*****************************************************************************/ + +/** Direction type for PDO assignment functions. + */ +typedef enum { + EC_DIR_INVALID, /**< Invalid direction. Do not use this value. */ + EC_DIR_OUTPUT, /**< Values written by the master. */ + EC_DIR_INPUT, /**< Values read by the master. */ + EC_DIR_COUNT /**< Number of directions. For internal use only. */ +} ec_direction_t; + +/*****************************************************************************/ + +/** Watchdog mode for sync manager configuration. + * + * Used to specify, if a sync manager's watchdog is to be enabled. + */ +typedef enum { + EC_WD_DEFAULT, /**< Use the default setting of the sync manager. */ + EC_WD_ENABLE, /**< Enable the watchdog. */ + EC_WD_DISABLE, /**< Disable the watchdog. */ +} ec_watchdog_mode_t; + +/*****************************************************************************/ + +/** PDO entry configuration information. + * + * This is the data type of the \a entries field in ec_pdo_info_t. + * + * \see ecrt_slave_config_pdos(). + */ +typedef struct { + uint16_t index; /**< PDO entry index. */ + uint8_t subindex; /**< PDO entry subindex. */ + uint8_t bit_length; /**< Size of the PDO entry in bit. */ +} ec_pdo_entry_info_t; + +/*****************************************************************************/ + +/** PDO configuration information. + * + * This is the data type of the \a pdos field in ec_sync_info_t. + * + * \see ecrt_slave_config_pdos(). + */ +typedef struct { + uint16_t index; /**< PDO index. */ + unsigned int n_entries; /**< Number of PDO entries in \a entries to map. + Zero means, that the default mapping shall be + used (this can only be done if the slave is + present at bus configuration time). */ + ec_pdo_entry_info_t *entries; /**< Array of PDO entries to map. Can either + be \a NULL, or must contain at + least \a n_entries values. */ +} ec_pdo_info_t; + +/*****************************************************************************/ + +/** Sync manager configuration information. + * + * This can be use to configure multiple sync managers including the PDO + * assignment and PDO mapping. It is used as an input parameter type in + * ecrt_slave_config_pdos(). + */ +typedef struct { + uint8_t index; /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS for a valid sync manager, + but can also be \a 0xff to mark the end of the list. */ + ec_direction_t dir; /**< Sync manager direction. */ + unsigned int n_pdos; /**< Number of PDOs in \a pdos. */ + ec_pdo_info_t *pdos; /**< Array with PDOs to assign. This must contain + at least \a n_pdos PDOs. */ + ec_watchdog_mode_t watchdog_mode; /**< Watchdog mode. */ +} ec_sync_info_t; + +/*****************************************************************************/ + +/** List record type for PDO entry mass-registration. + * + * This type is used for the array parameter of the + * ecrt_domain_reg_pdo_entry_list() + */ +typedef struct { + uint16_t alias; /**< Slave alias address. */ + uint16_t position; /**< Slave position. */ + uint32_t vendor_id; /**< Slave vendor ID. */ + uint32_t product_code; /**< Slave product code. */ + uint16_t index; /**< PDO entry index. */ + uint8_t subindex; /**< PDO entry subindex. */ + unsigned int *offset; /**< Pointer to a variable to store the PDO entry's + (byte-)offset in the process data. */ + unsigned int *bit_position; /**< Pointer to a variable to store a bit + position (0-7) within the \a offset. Can be + NULL, in which case an error is raised if the + PDO entry does not byte-align. */ +} ec_pdo_entry_reg_t; + +/*****************************************************************************/ + +/** Request state. + * + * This is used as return type for ecrt_sdo_request_state() and + * ecrt_voe_handler_state(). + */ +typedef enum { + EC_REQUEST_UNUSED, /**< Not requested. */ + EC_REQUEST_BUSY, /**< Request is being processed. */ + EC_REQUEST_SUCCESS, /**< Request was processed successfully. */ + EC_REQUEST_ERROR, /**< Request processing failed. */ +} ec_request_state_t; + +/*****************************************************************************/ + +/** Application-layer state. + */ +typedef enum { + EC_AL_STATE_INIT = 1, /**< Init. */ + EC_AL_STATE_PREOP = 2, /**< Pre-operational. */ + EC_AL_STATE_SAFEOP = 4, /**< Safe-operational. */ + EC_AL_STATE_OP = 8, /**< Operational. */ +} ec_al_state_t; + +/****************************************************************************** + * Global functions + *****************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Returns the version magic of the realtime interface. + * + * \return Value of ECRT_VERSION_MAGIC() at EtherCAT master compile time. + */ +unsigned int ecrt_version_magic(void); + +/** Requests an EtherCAT master for realtime operation. + * + * Before an application can access an EtherCAT master, it has to reserve one + * for exclusive use. + * + * In userspace, this is a convenience function for ecrt_open_master() and + * ecrt_master_reserve(). + * + * This function has to be the first function an application has to call to + * use EtherCAT. The function takes the index of the master as its argument. + * The first master has index 0, the n-th master has index n - 1. The number + * of masters has to be specified when loading the master module. + * + * \return Pointer to the reserved master, otherwise \a NULL. + */ +ec_master_t *ecrt_request_master( + unsigned int master_index /**< Index of the master to request. */ + ); + +#ifndef __KERNEL__ + +/** Opens an EtherCAT master for userspace access. + * + * This function has to be the first function an application has to call to + * use EtherCAT. The function takes the index of the master as its argument. + * The first master has index 0, the n-th master has index n - 1. The number + * of masters has to be specified when loading the master module. + * + * For convenience, the function ecrt_request_master() can be used. + * + * \return Pointer to the opened master, otherwise \a NULL. + */ +ec_master_t *ecrt_open_master( + unsigned int master_index /**< Index of the master to request. */ + ); + +#endif // #ifndef __KERNEL__ + +/** Releases a requested EtherCAT master. + * + * After use, a master it has to be released to make it available for other + * applications. + * + * This method frees all created data structures. It should not be called in + * realtime context. + * + * If the master was activated, ecrt_master_deactivate() is called internally. + */ +void ecrt_release_master( + ec_master_t *master /**< EtherCAT master */ + ); + +/****************************************************************************** + * Master methods + *****************************************************************************/ + +#ifndef __KERNEL__ + +/** Reserves an EtherCAT master for realtime operation. + * + * Before an application can use PDO/domain registration functions or SDO + * request functions on the master, it has to reserve one for exclusive use. + * + * \return 0 in case of success, else < 0 + */ +int ecrt_master_reserve( + ec_master_t *master /**< EtherCAT master */ + ); + +#endif // #ifndef __KERNEL__ + +#ifdef __KERNEL__ + +/** Sets the locking callbacks. + * + * For concurrent master access, i. e. if other instances than the application + * want to send and receive datagrams on the bus, the application has to + * provide a callback mechanism. This method takes two function pointers as + * its parameters. Asynchronous master access (like EoE processing) is only + * possible if the callbacks have been set. + * + * The task of the send callback (\a send_cb) is to decide, if the bus is + * currently accessible and whether or not to call the ecrt_master_send_ext() + * method. + * + * The task of the receive callback (\a receive_cb) is to decide, if a call to + * ecrt_master_receive() is allowed and to execute it respectively. + * + * \attention This method has to be called before ecrt_master_activate(). + */ +void ecrt_master_callbacks( + ec_master_t *master, /**< EtherCAT master */ + void (*send_cb)(void *), /**< Datagram sending callback. */ + void (*receive_cb)(void *), /**< Receive callback. */ + void *cb_data /**< Arbitrary pointer passed to the callback functions. + */ + ); + +#endif /* __KERNEL__ */ + +/** Creates a new process data domain. + * + * For process data exchange, at least one process data domain is needed. + * This method creates a new process data domain and returns a pointer to the + * new domain object. This object can be used for registering PDOs and + * exchanging them in cyclic operation. + * + * This method allocates memory and should be called in non-realtime context + * before ecrt_master_activate(). + * + * \return Pointer to the new domain on success, else NULL. + */ +ec_domain_t *ecrt_master_create_domain( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Obtains a slave configuration. + * + * Creates a slave configuration object for the given \a alias and \a position + * tuple and returns it. If a configuration with the same \a alias and \a + * position already exists, it will be re-used. In the latter case, the given + * vendor ID and product code are compared to the stored ones. On mismatch, an + * error message is raised and the function returns \a NULL. + * + * Slaves are addressed with the \a alias and \a position parameters. + * - If \a alias is zero, \a position is interpreted as the desired slave's + * ring position. + * - If \a alias is non-zero, it matches a slave with the given alias. In this + * case, \a position is interpreted as ring offset, starting from the + * aliased slave, so a position of zero means the aliased slave itself and a + * positive value matches the n-th slave behind the aliased one. + * + * If the slave with the given address is found during the bus configuration, + * its vendor ID and product code are matched against the given value. On + * mismatch, the slave is not configured and an error message is raised. + * + * If different slave configurations are pointing to the same slave during bus + * configuration, a warning is raised and only the first configuration is + * applied. + * + * This method allocates memory and should be called in non-realtime context + * before ecrt_master_activate(). + * + * \retval >0 Pointer to the slave configuration structure. + * \retval NULL in the error case. + */ +ec_slave_config_t *ecrt_master_slave_config( + ec_master_t *master, /**< EtherCAT master */ + uint16_t alias, /**< Slave alias. */ + uint16_t position, /**< Slave position. */ + uint32_t vendor_id, /**< Expected vendor ID. */ + uint32_t product_code /**< Expected product code. */ + ); + +/** Selects the reference clock for distributed clocks. + * + * If this method is not called for a certain master, or if the slave + * configuration pointer is NULL, then the first slave with DC functionality + * will provide the reference clock. + * + * \return 0 on success, otherwise negative error code. + */ +int ecrt_master_select_reference_clock( + ec_master_t *master, /**< EtherCAT master. */ + ec_slave_config_t *sc /**< Slave config of the slave to use as the + * reference slave (or NULL). */ + ); + +/** Obtains master information. + * + * No memory is allocated on the heap in + * this function. + * + * \attention The pointer to this structure must point to a valid variable. + * + * \return 0 in case of success, else < 0 + */ +int ecrt_master( + ec_master_t *master, /**< EtherCAT master */ + ec_master_info_t *master_info /**< Structure that will output the + information */ + ); + +/** Obtains slave information. + * + * Tries to find the slave with the given ring position. The obtained + * information is stored in a structure. No memory is allocated on the heap in + * this function. + * + * \attention The pointer to this structure must point to a valid variable. + * + * \return 0 in case of success, else < 0 + */ +int ecrt_master_get_slave( + ec_master_t *master, /**< EtherCAT master */ + uint16_t slave_position, /**< Slave position. */ + ec_slave_info_t *slave_info /**< Structure that will output the + information */ + ); + +#ifndef __KERNEL__ + +/** Returns the proposed configuration of a slave's sync manager. + * + * Fills a given ec_sync_info_t structure with the attributes of a sync + * manager. The \a pdos field of the return value is left empty. Use + * ecrt_master_get_pdo() to get the PDO information. + * + * \return zero on success, else non-zero + */ +int ecrt_master_get_sync_manager( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + ec_sync_info_t *sync /**< Pointer to output structure. */ + ); + +/** Returns information about a currently assigned PDO. + * + * Fills a given ec_pdo_info_t structure with the attributes of a currently + * assigned PDO of the given sync manager. The \a entries field of the return + * value is left empty. Use ecrt_master_get_pdo_entry() to get the PDO + * entry information. + * + * \retval zero on success, else non-zero + */ +int ecrt_master_get_pdo( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + uint16_t pos, /**< Zero-based PDO position. */ + ec_pdo_info_t *pdo /**< Pointer to output structure. */ + ); + +/** Returns information about a currently mapped PDO entry. + * + * Fills a given ec_pdo_entry_info_t structure with the attributes of a + * currently mapped PDO entry of the given PDO. + * + * \retval zero on success, else non-zero + */ +int ecrt_master_get_pdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + uint16_t pdo_pos, /**< Zero-based PDO position. */ + uint16_t entry_pos, /**< Zero-based PDO entry position. */ + ec_pdo_entry_info_t *entry /**< Pointer to output structure. */ + ); + +#endif /* #ifndef __KERNEL__ */ + +/** Executes an SDO download request to write data to a slave. + * + * This request is processed by the master state machine. This method blocks, + * until the request has been processed and may not be called in realtime + * context. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_master_sdo_download( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint16_t index, /**< Index of the SDO. */ + uint8_t subindex, /**< Subindex of the SDO. */ + uint8_t *data, /**< Data buffer to download. */ + size_t data_size, /**< Size of the data buffer. */ + uint32_t *abort_code /**< Abort code of the SDO download. */ + ); + +/** Executes an SDO download request to write data to a slave via complete + * access. + * + * This request is processed by the master state machine. This method blocks, + * until the request has been processed and may not be called in realtime + * context. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_master_sdo_download_complete( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint16_t index, /**< Index of the SDO. */ + uint8_t *data, /**< Data buffer to download. */ + size_t data_size, /**< Size of the data buffer. */ + uint32_t *abort_code /**< Abort code of the SDO download. */ + ); + +/** Executes an SDO upload request to read data from a slave. + * + * This request is processed by the master state machine. This method blocks, + * until the request has been processed and may not be called in realtime + * context. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_master_sdo_upload( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint16_t index, /**< Index of the SDO. */ + uint8_t subindex, /**< Subindex of the SDO. */ + uint8_t *target, /**< Target buffer for the upload. */ + size_t target_size, /**< Size of the target buffer. */ + size_t *result_size, /**< Uploaded data size. */ + uint32_t *abort_code /**< Abort code of the SDO upload. */ + ); + +/** Executes an SoE write request. + * + * Starts writing an IDN and blocks until the request was processed, or an + * error occurred. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_master_write_idn( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t drive_no, /**< Drive number. */ + uint16_t idn, /**< SoE IDN (see ecrt_slave_config_idn()). */ + uint8_t *data, /**< Pointer to data to write. */ + size_t data_size, /**< Size of data to write. */ + uint16_t *error_code /**< Pointer to variable, where an SoE error code + can be stored. */ + ); + +/** Executes an SoE read request. + * + * Starts reading an IDN and blocks until the request was processed, or an + * error occurred. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_master_read_idn( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t drive_no, /**< Drive number. */ + uint16_t idn, /**< SoE IDN (see ecrt_slave_config_idn()). */ + uint8_t *target, /**< Pointer to memory where the read data can be + stored. */ + size_t target_size, /**< Size of the memory \a target points to. */ + size_t *result_size, /**< Actual size of the received data. */ + uint16_t *error_code /**< Pointer to variable, where an SoE error code + can be stored. */ + ); + +/** Finishes the configuration phase and prepares for cyclic operation. + * + * This function tells the master that the configuration phase is finished and + * the realtime operation will begin. The function allocates internal memory + * for the domains and calculates the logical FMMU addresses for domain + * members. It tells the master state machine that the bus configuration is + * now to be applied. + * + * \attention After this function has been called, the realtime application is + * in charge of cyclically calling ecrt_master_send() and + * ecrt_master_receive() to ensure bus communication. Before calling this + * function, the master thread is responsible for that, so these functions may + * not be called! The method itself allocates memory and should not be called + * in realtime context. + * + * \return 0 in case of success, else < 0 + */ +int ecrt_master_activate( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Deactivates the master. + * + * Removes the bus configuration. All objects created by + * ecrt_master_create_domain(), ecrt_master_slave_config(), ecrt_domain_data() + * ecrt_slave_config_create_sdo_request() and + * ecrt_slave_config_create_voe_handler() are freed, so pointers to them + * become invalid. + * + * This method should not be called in realtime context. + */ +void ecrt_master_deactivate( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Set interval between calls to ecrt_master_send(). + * + * This information helps the master to decide, how much data can be appended + * to a frame by the master state machine. When the master is configured with + * --enable-hrtimers, this is used to calculate the scheduling of the master + * thread. + * + * \retval 0 on success. + * \retval <0 Error code. + */ +int ecrt_master_set_send_interval( + ec_master_t *master, /**< EtherCAT master. */ + size_t send_interval /**< Send interval in us */ + ); + +/** Sends all datagrams in the queue. + * + * This method takes all datagrams, that have been queued for transmission, + * puts them into frames, and passes them to the Ethernet device for sending. + * + * Has to be called cyclically by the application after ecrt_master_activate() + * has returned. + */ +void ecrt_master_send( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Fetches received frames from the hardware and processes the datagrams. + * + * Queries the network device for received frames by calling the interrupt + * service routine. Extracts received datagrams and dispatches the results to + * the datagram objects in the queue. Received datagrams, and the ones that + * timed out, will be marked, and dequeued. + * + * Has to be called cyclically by the realtime application after + * ecrt_master_activate() has returned. + */ +void ecrt_master_receive( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Sends non-application datagrams. + * + * This method has to be called in the send callback function passed via + * ecrt_master_callbacks() to allow the sending of non-application datagrams. + */ +void ecrt_master_send_ext( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Reads the current master state. + * + * Stores the master state information in the given \a state structure. + * + * This method returns a global state. For the link-specific states in a + * redundant bus topology, use the ecrt_master_link_state() method. + */ +void ecrt_master_state( + const ec_master_t *master, /**< EtherCAT master. */ + ec_master_state_t *state /**< Structure to store the information. */ + ); + +/** Reads the current state of a redundant link. + * + * Stores the link state information in the given \a state structure. + * + * \return Zero on success, otherwise negative error code. + */ +int ecrt_master_link_state( + const ec_master_t *master, /**< EtherCAT master. */ + unsigned int dev_idx, /**< Index of the device (0 = main device, 1 = + first backup device, ...). */ + ec_master_link_state_t *state /**< Structure to store the information. + */ + ); + +/** Sets the application time. + * + * The master has to know the application's time when operating slaves with + * distributed clocks. The time is not incremented by the master itself, so + * this method has to be called cyclically. + * + * \attention The time passed to this method is used to calculate the phase of + * the slaves' SYNC0/1 interrupts. It should be called constantly at the same + * point of the realtime cycle. So it is recommended to call it at the start + * of the calculations to avoid deviancies due to changing execution times. + * + * The time is used when setting the slaves' <tt>System Time Offset</tt> and + * <tt>Cyclic Operation Start Time</tt> registers and when synchronizing the + * DC reference clock to the application time via + * ecrt_master_sync_reference_clock(). + * + * The time is defined as nanoseconds from 2000-01-01 00:00. Converting an + * epoch time can be done with the EC_TIMEVAL2NANO() macro, but is not + * necessary, since the absolute value is not of any interest. + */ +void ecrt_master_application_time( + ec_master_t *master, /**< EtherCAT master. */ + uint64_t app_time /**< Application time. */ + ); + +/** Queues the DC reference clock drift compensation datagram for sending. + * + * The reference clock will by synchronized to the application time provided + * by the last call off ecrt_master_application_time(). + */ +void ecrt_master_sync_reference_clock( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Queues the DC reference clock drift compensation datagram for sending. + * + * The reference clock will by synchronized to the time passed in the + * sync_time parameter. + */ +void ecrt_master_sync_reference_clock_to( + ec_master_t *master, /**< EtherCAT master. */ + uint64_t sync_time /**< Sync reference clock to this time. */ + ); + +/** Queues the DC clock drift compensation datagram for sending. + * + * All slave clocks synchronized to the reference clock. + */ +void ecrt_master_sync_slave_clocks( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Get the lower 32 bit of the reference clock system time. + * + * This method can be used to synchronize the master to the reference clock. + * + * The reference clock system time is queried via the + * ecrt_master_sync_slave_clocks() method, that reads the system time of the + * reference clock and writes it to the slave clocks (so be sure to call it + * cyclically to get valid data). + * + * \attention The returned time is the system time of the reference clock + * minus the transmission delay of the reference clock. + * + * \retval 0 success, system time was written into \a time. + * \retval -ENXIO No reference clock found. + * \retval -EIO Slave synchronization datagram was not received. + */ +int ecrt_master_reference_clock_time( + ec_master_t *master, /**< EtherCAT master. */ + uint32_t *time /**< Pointer to store the queried system time. */ + ); + +/** Queues the DC synchrony monitoring datagram for sending. + * + * The datagram broadcast-reads all "System time difference" registers (\a + * 0x092c) to get an upper estimation of the DC synchrony. The result can be + * checked with the ecrt_master_sync_monitor_process() method. + */ +void ecrt_master_sync_monitor_queue( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Processes the DC synchrony monitoring datagram. + * + * If the sync monitoring datagram was sent before with + * ecrt_master_sync_monitor_queue(), the result can be queried with this + * method. + * + * \return Upper estimation of the maximum time difference in ns. + */ +uint32_t ecrt_master_sync_monitor_process( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Retry configuring slaves. + * + * Via this method, the application can tell the master to bring all slaves to + * OP state. In general, this is not necessary, because it is automatically + * done by the master. But with special slaves, that can be reconfigured by + * the vendor during runtime, it can be useful. + */ +void ecrt_master_reset( + ec_master_t *master /**< EtherCAT master. */ + ); + +/****************************************************************************** + * Slave configuration methods + *****************************************************************************/ + +/** Configure a sync manager. + * + * Sets the direction of a sync manager. This overrides the direction bits + * from the default control register from SII. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \return zero on success, else non-zero + */ +int ecrt_slave_config_sync_manager( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + ec_direction_t direction, /**< Input/Output. */ + ec_watchdog_mode_t watchdog_mode /** Watchdog mode. */ + ); + +/** Configure a slave's watchdog times. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + */ +void ecrt_slave_config_watchdog( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t watchdog_divider, /**< Number of 40 ns intervals. Used as a + base unit for all slave watchdogs. If set + to zero, the value is not written, so the + default is used. */ + uint16_t watchdog_intervals /**< Number of base intervals for process + data watchdog. If set to zero, the value + is not written, so the default is used. + */ + ); + +/** Add a PDO to a sync manager's PDO assignment. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_pdos() + * \return zero on success, else non-zero + */ +int ecrt_slave_config_pdo_assign_add( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + uint16_t index /**< Index of the PDO to assign. */ + ); + +/** Clear a sync manager's PDO assignment. + * + * This can be called before assigning PDOs via + * ecrt_slave_config_pdo_assign_add(), to clear the default assignment of a + * sync manager. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_pdos() + */ +void ecrt_slave_config_pdo_assign_clear( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t sync_index /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + ); + +/** Add a PDO entry to the given PDO's mapping. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_pdos() + * \return zero on success, else non-zero + */ +int ecrt_slave_config_pdo_mapping_add( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t pdo_index, /**< Index of the PDO. */ + uint16_t entry_index, /**< Index of the PDO entry to add to the PDO's + mapping. */ + uint8_t entry_subindex, /**< Subindex of the PDO entry to add to the + PDO's mapping. */ + uint8_t entry_bit_length /**< Size of the PDO entry in bit. */ + ); + +/** Clear the mapping of a given PDO. + * + * This can be called before mapping PDO entries via + * ecrt_slave_config_pdo_mapping_add(), to clear the default mapping. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_pdos() + */ +void ecrt_slave_config_pdo_mapping_clear( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t pdo_index /**< Index of the PDO. */ + ); + +/** Specify a complete PDO configuration. + * + * This function is a convenience wrapper for the functions + * ecrt_slave_config_sync_manager(), ecrt_slave_config_pdo_assign_clear(), + * ecrt_slave_config_pdo_assign_add(), ecrt_slave_config_pdo_mapping_clear() + * and ecrt_slave_config_pdo_mapping_add(), that are better suitable for + * automatic code generation. + * + * The following example shows, how to specify a complete configuration, + * including the PDO mappings. With this information, the master is able to + * reserve the complete process data, even if the slave is not present at + * configuration time: + * + * \code + * ec_pdo_entry_info_t el3162_channel1[] = { + * {0x3101, 1, 8}, // status + * {0x3101, 2, 16} // value + * }; + * + * ec_pdo_entry_info_t el3162_channel2[] = { + * {0x3102, 1, 8}, // status + * {0x3102, 2, 16} // value + * }; + * + * ec_pdo_info_t el3162_pdos[] = { + * {0x1A00, 2, el3162_channel1}, + * {0x1A01, 2, el3162_channel2} + * }; + * + * ec_sync_info_t el3162_syncs[] = { + * {2, EC_DIR_OUTPUT}, + * {3, EC_DIR_INPUT, 2, el3162_pdos}, + * {0xff} + * }; + * + * if (ecrt_slave_config_pdos(sc_ana_in, EC_END, el3162_syncs)) { + * // handle error + * } + * \endcode + * + * The next example shows, how to configure the PDO assignment only. The + * entries for each assigned PDO are taken from the PDO's default mapping. + * Please note, that PDO entry registration will fail, if the PDO + * configuration is left empty and the slave is offline. + * + * \code + * ec_pdo_info_t pdos[] = { + * {0x1600}, // Channel 1 + * {0x1601} // Channel 2 + * }; + * + * ec_sync_info_t syncs[] = { + * {3, EC_DIR_INPUT, 2, pdos}, + * }; + * + * if (ecrt_slave_config_pdos(slave_config_ana_in, 1, syncs)) { + * // handle error + * } + * \endcode + * + * Processing of \a syncs will stop, if + * - the number of processed items reaches \a n_syncs, or + * - the \a index member of an ec_sync_info_t item is 0xff. In this case, + * \a n_syncs should set to a number greater than the number of list items; + * using EC_END is recommended. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \return zero on success, else non-zero + */ +int ecrt_slave_config_pdos( + ec_slave_config_t *sc, /**< Slave configuration. */ + unsigned int n_syncs, /**< Number of sync manager configurations in + \a syncs. */ + const ec_sync_info_t syncs[] /**< Array of sync manager + configurations. */ + ); + +/** Registers a PDO entry for process data exchange in a domain. + * + * Searches the assigned PDOs for the given PDO entry. An error is raised, if + * the given entry is not mapped. Otherwise, the corresponding sync manager + * and FMMU configurations are provided for slave configuration and the + * respective sync manager's assigned PDOs are appended to the given domain, + * if not already done. The offset of the requested PDO entry's data inside + * the domain's process data is returned. Optionally, the PDO entry bit + * position (0-7) can be retrieved via the \a bit_position output parameter. + * This pointer may be \a NULL, in this case an error is raised if the PDO + * entry does not byte-align. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \retval >=0 Success: Offset of the PDO entry's process data. + * \retval <0 Error code. + */ +int ecrt_slave_config_reg_pdo_entry( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t entry_index, /**< Index of the PDO entry to register. */ + uint8_t entry_subindex, /**< Subindex of the PDO entry to register. */ + ec_domain_t *domain, /**< Domain. */ + unsigned int *bit_position /**< Optional address if bit addressing + is desired */ + ); + +/** Registers a PDO entry using its position. + * + * Similar to ecrt_slave_config_reg_pdo_entry(), but not using PDO indices but + * offsets in the PDO mapping, because PDO entry indices may not be unique + * inside a slave's PDO mapping. An error is raised, if + * one of the given positions is out of range. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \retval >=0 Success: Offset of the PDO entry's process data. + * \retval <0 Error code. + */ +int ecrt_slave_config_reg_pdo_entry_pos( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t sync_index, /**< Sync manager index. */ + unsigned int pdo_pos, /**< Position of the PDO inside the SM. */ + unsigned int entry_pos, /**< Position of the entry inside the PDO. */ + ec_domain_t *domain, /**< Domain. */ + unsigned int *bit_position /**< Optional address if bit addressing + is desired */ + ); + +/** Configure distributed clocks. + * + * Sets the AssignActivate word and the cycle and shift times for the sync + * signals. + * + * The AssignActivate word is vendor-specific and can be taken from the XML + * device description file (Device -> Dc -> AssignActivate). Set this to zero, + * if the slave shall be operated without distributed clocks (default). + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \attention The \a sync1_shift time is ignored. + */ +void ecrt_slave_config_dc( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t assign_activate, /**< AssignActivate word. */ + uint32_t sync0_cycle, /**< SYNC0 cycle time [ns]. */ + int32_t sync0_shift, /**< SYNC0 shift time [ns]. */ + uint32_t sync1_cycle, /**< SYNC1 cycle time [ns]. */ + int32_t sync1_shift /**< SYNC1 shift time [ns]. */ + ); + +/** Add an SDO configuration. + * + * An SDO configuration is stored in the slave configuration object and is + * downloaded to the slave whenever the slave is being configured by the + * master. This usually happens once on master activation, but can be repeated + * subsequently, for example after the slave's power supply failed. + * + * \attention The SDOs for PDO assignment (\p 0x1C10 - \p 0x1C2F) and PDO + * mapping (\p 0x1600 - \p 0x17FF and \p 0x1A00 - \p 0x1BFF) should not be + * configured with this function, because they are part of the slave + * configuration done by the master. Please use ecrt_slave_config_pdos() and + * friends instead. + * + * This is the generic function for adding an SDO configuration. Please note + * that the this function does not do any endianness correction. If + * datatype-specific functions are needed (that automatically correct the + * endianness), have a look at ecrt_slave_config_sdo8(), + * ecrt_slave_config_sdo16() and ecrt_slave_config_sdo32(). + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_slave_config_sdo( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t index, /**< Index of the SDO to configure. */ + uint8_t subindex, /**< Subindex of the SDO to configure. */ + const uint8_t *data, /**< Pointer to the data. */ + size_t size /**< Size of the \a data. */ + ); + +/** Add a configuration value for an 8-bit SDO. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_sdo(). + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_slave_config_sdo8( + ec_slave_config_t *sc, /**< Slave configuration */ + uint16_t sdo_index, /**< Index of the SDO to configure. */ + uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */ + uint8_t value /**< Value to set. */ + ); + +/** Add a configuration value for a 16-bit SDO. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_sdo(). + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_slave_config_sdo16( + ec_slave_config_t *sc, /**< Slave configuration */ + uint16_t sdo_index, /**< Index of the SDO to configure. */ + uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */ + uint16_t value /**< Value to set. */ + ); + +/** Add a configuration value for a 32-bit SDO. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_sdo(). + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_slave_config_sdo32( + ec_slave_config_t *sc, /**< Slave configuration */ + uint16_t sdo_index, /**< Index of the SDO to configure. */ + uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */ + uint32_t value /**< Value to set. */ + ); + +/** Add configuration data for a complete SDO. + * + * The SDO data are transferred via CompleteAccess. Data for the first + * subindex (0) have to be included. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_sdo(). + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_slave_config_complete_sdo( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t index, /**< Index of the SDO to configure. */ + const uint8_t *data, /**< Pointer to the data. */ + size_t size /**< Size of the \a data. */ + ); + +/** Set the size of the CoE emergency ring buffer. + * + * The initial size is zero, so all messages will be dropped. This method can + * be called even after master activation, but it will clear the ring buffer! + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \return 0 on success, or negative error code. + */ +int ecrt_slave_config_emerg_size( + ec_slave_config_t *sc, /**< Slave configuration. */ + size_t elements /**< Number of records of the CoE emergency ring. */ + ); + +/** Read and remove one record from the CoE emergency ring buffer. + * + * A record consists of 8 bytes: + * + * Byte 0-1: Error code (little endian) + * Byte 2: Error register + * Byte 3-7: Data + * + * \return 0 on success (record popped), or negative error code (i. e. + * -ENOENT, if ring is empty). + */ +int ecrt_slave_config_emerg_pop( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t *target /**< Pointer to target memory (at least + EC_COE_EMERGENCY_MSG_SIZE bytes). */ + ); + +/** Clears CoE emergency ring buffer and the overrun counter. + * + * \return 0 on success, or negative error code. + */ +int ecrt_slave_config_emerg_clear( + ec_slave_config_t *sc /**< Slave configuration. */ + ); + +/** Read the number of CoE emergency overruns. + * + * The overrun counter will be incremented when a CoE emergency message could + * not be stored in the ring buffer and had to be dropped. Call + * ecrt_slave_config_emerg_clear() to reset the counter. + * + * \return Number of overruns since last clear, or negative error code. + */ +int ecrt_slave_config_emerg_overruns( + ec_slave_config_t *sc /**< Slave configuration. */ + ); + +/** Create an SDO request to exchange SDOs during realtime operation. + * + * The created SDO request object is freed automatically when the master is + * released. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \return New SDO request, or NULL on error. + */ +ec_sdo_request_t *ecrt_slave_config_create_sdo_request( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t index, /**< SDO index. */ + uint8_t subindex, /**< SDO subindex. */ + size_t size /**< Data size to reserve. */ + ); + +/** Create an VoE handler to exchange vendor-specific data during realtime + * operation. + * + * The number of VoE handlers per slave configuration is not limited, but + * usually it is enough to create one for sending and one for receiving, if + * both can be done simultaneously. + * + * The created VoE handler object is freed automatically when the master is + * released. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \return New VoE handler, or NULL on error. + */ +ec_voe_handler_t *ecrt_slave_config_create_voe_handler( + ec_slave_config_t *sc, /**< Slave configuration. */ + size_t size /**< Data size to reserve. */ + ); + +/** Create a register request to exchange EtherCAT register contents during + * realtime operation. + * + * This interface should not be used to take over master functionality, + * instead it is intended for debugging and monitoring reasons. + * + * The created register request object is freed automatically when the master + * is released. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \return New register request, or NULL on error. + */ +ec_reg_request_t *ecrt_slave_config_create_reg_request( + ec_slave_config_t *sc, /**< Slave configuration. */ + size_t size /**< Data size to reserve. */ + ); + +/** Outputs the state of the slave configuration. + * + * Stores the state information in the given \a state structure. The state + * information is updated by the master state machine, so it may take a few + * cycles, until it changes. + * + * \attention If the state of process data exchange shall be monitored in + * realtime, ecrt_domain_state() should be used. + */ +void ecrt_slave_config_state( + const ec_slave_config_t *sc, /**< Slave configuration */ + ec_slave_config_state_t *state /**< State object to write to. */ + ); + +/** Add an SoE IDN configuration. + * + * A configuration for a Sercos-over-EtherCAT IDN is stored in the slave + * configuration object and is written to the slave whenever the slave is + * being configured by the master. This usually happens once on master + * activation, but can be repeated subsequently, for example after the slave's + * power supply failed. + * + * The \a idn parameter can be separated into several sections: + * - Bit 15: Standard data (0) or Product data (1) + * - Bit 14 - 12: Parameter set (0 - 7) + * - Bit 11 - 0: Data block number (0 - 4095) + * + * Please note that the this function does not do any endianness correction. + * Multi-byte data have to be passed in EtherCAT endianness (little-endian). + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ecrt_slave_config_idn( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t drive_no, /**< Drive number. */ + uint16_t idn, /**< SoE IDN. */ + ec_al_state_t state, /**< AL state in which to write the IDN (PREOP or + SAFEOP). */ + const uint8_t *data, /**< Pointer to the data. */ + size_t size /**< Size of the \a data. */ + ); + +/****************************************************************************** + * Domain methods + *****************************************************************************/ + +/** Registers a bunch of PDO entries for a domain. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_reg_pdo_entry() + * + * \attention The registration array has to be terminated with an empty + * structure, or one with the \a index field set to zero! + * \return 0 on success, else non-zero. + */ +int ecrt_domain_reg_pdo_entry_list( + ec_domain_t *domain, /**< Domain. */ + const ec_pdo_entry_reg_t *pdo_entry_regs /**< Array of PDO + registrations. */ + ); + +/** Returns the current size of the domain's process data. + * + * \return Size of the process data image, or a negative error code. + */ +size_t ecrt_domain_size( + const ec_domain_t *domain /**< Domain. */ + ); + +#ifdef __KERNEL__ + +/** Provide external memory to store the domain's process data. + * + * Call this after all PDO entries have been registered and before activating + * the master. + * + * The size of the allocated memory must be at least ecrt_domain_size(), after + * all PDO entries have been registered. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + */ +void ecrt_domain_external_memory( + ec_domain_t *domain, /**< Domain. */ + uint8_t *memory /**< Address of the memory to store the process + data in. */ + ); + +#endif /* __KERNEL__ */ + +/** Returns the domain's process data. + * + * - In kernel context: If external memory was provided with + * ecrt_domain_external_memory(), the returned pointer will contain the + * address of that memory. Otherwise it will point to the internally allocated + * memory. In the latter case, this method may not be called before + * ecrt_master_activate(). + * + * - In userspace context: This method has to be called after + * ecrt_master_activate() to get the mapped domain process data memory. + * + * \return Pointer to the process data memory. + */ +uint8_t *ecrt_domain_data( + ec_domain_t *domain /**< Domain. */ + ); + +/** Determines the states of the domain's datagrams. + * + * Evaluates the working counters of the received datagrams and outputs + * statistics, if necessary. This must be called after ecrt_master_receive() + * is expected to receive the domain datagrams in order to make + * ecrt_domain_state() return the result of the last process data exchange. + */ +void ecrt_domain_process( + ec_domain_t *domain /**< Domain. */ + ); + +/** (Re-)queues all domain datagrams in the master's datagram queue. + * + * Call this function to mark the domain's datagrams for exchanging at the + * next call of ecrt_master_send(). + */ +void ecrt_domain_queue( + ec_domain_t *domain /**< Domain. */ + ); + +/** Reads the state of a domain. + * + * Stores the domain state in the given \a state structure. + * + * Using this method, the process data exchange can be monitored in realtime. + */ +void ecrt_domain_state( + const ec_domain_t *domain, /**< Domain. */ + ec_domain_state_t *state /**< Pointer to a state object to store the + information. */ + ); + +/***************************************************************************** + * SDO request methods. + ****************************************************************************/ + +/** Set the SDO index and subindex. + * + * \attention If the SDO index and/or subindex is changed while + * ecrt_sdo_request_state() returns EC_REQUEST_BUSY, this may lead to + * unexpected results. + */ +void ecrt_sdo_request_index( + ec_sdo_request_t *req, /**< SDO request. */ + uint16_t index, /**< SDO index. */ + uint8_t subindex /**< SDO subindex. */ + ); + +/** Set the timeout for an SDO request. + * + * If the request cannot be processed in the specified time, if will be marked + * as failed. + * + * The timeout is permanently stored in the request object and is valid until + * the next call of this method. + */ +void ecrt_sdo_request_timeout( + ec_sdo_request_t *req, /**< SDO request. */ + uint32_t timeout /**< Timeout in milliseconds. Zero means no + timeout. */ + ); + +/** Access to the SDO request's data. + * + * This function returns a pointer to the request's internal SDO data memory. + * + * - After a read operation was successful, integer data can be evaluated using + * the EC_READ_*() macros as usual. Example: + * \code + * uint16_t value = EC_READ_U16(ecrt_sdo_request_data(sdo))); + * \endcode + * - If a write operation shall be triggered, the data have to be written to + * the internal memory. Use the EC_WRITE_*() macros, if you are writing + * integer data. Be sure, that the data fit into the memory. The memory size + * is a parameter of ecrt_slave_config_create_sdo_request(). + * \code + * EC_WRITE_U16(ecrt_sdo_request_data(sdo), 0xFFFF); + * \endcode + * + * \attention The return value can be invalid during a read operation, because + * the internal SDO data memory could be re-allocated if the read SDO data do + * not fit inside. + * + * \return Pointer to the internal SDO data memory. + */ +uint8_t *ecrt_sdo_request_data( + ec_sdo_request_t *req /**< SDO request. */ + ); + +/** Returns the current SDO data size. + * + * When the SDO request is created, the data size is set to the size of the + * reserved memory. After a read operation the size is set to the size of the + * read data. The size is not modified in any other situation. + * + * \return SDO data size in bytes. + */ +size_t ecrt_sdo_request_data_size( + const ec_sdo_request_t *req /**< SDO request. */ + ); + +/** Get the current state of the SDO request. + * + * \return Request state. + */ +#ifdef __KERNEL__ +ec_request_state_t ecrt_sdo_request_state( + const ec_sdo_request_t *req /**< SDO request. */ + ); +#else +ec_request_state_t ecrt_sdo_request_state( + ec_sdo_request_t *req /**< SDO request. */ + ); +#endif + +/** Schedule an SDO write operation. + * + * \attention This method may not be called while ecrt_sdo_request_state() + * returns EC_REQUEST_BUSY. + */ +void ecrt_sdo_request_write( + ec_sdo_request_t *req /**< SDO request. */ + ); + +/** Schedule an SDO read operation. + * + * \attention This method may not be called while ecrt_sdo_request_state() + * returns EC_REQUEST_BUSY. + * + * \attention After calling this function, the return value of + * ecrt_sdo_request_data() must be considered as invalid while + * ecrt_sdo_request_state() returns EC_REQUEST_BUSY. + */ +void ecrt_sdo_request_read( + ec_sdo_request_t *req /**< SDO request. */ + ); + +/***************************************************************************** + * VoE handler methods. + ****************************************************************************/ + +/** Sets the VoE header for future send operations. + * + * A VoE message shall contain a 4-byte vendor ID, followed by a 2-byte vendor + * type at as header. These numbers can be set with this function. The values + * are valid and will be used for future send operations until the next call + * of this method. + */ +void ecrt_voe_handler_send_header( + ec_voe_handler_t *voe, /**< VoE handler. */ + uint32_t vendor_id, /**< Vendor ID. */ + uint16_t vendor_type /**< Vendor-specific type. */ + ); + +/** Reads the header data of a received VoE message. + * + * This method can be used to get the received VoE header information after a + * read operation has succeeded. + * + * The header information is stored at the memory given by the pointer + * parameters. + */ +void ecrt_voe_handler_received_header( + const ec_voe_handler_t *voe, /**< VoE handler. */ + uint32_t *vendor_id, /**< Vendor ID. */ + uint16_t *vendor_type /**< Vendor-specific type. */ + ); + +/** Access to the VoE handler's data. + * + * This function returns a pointer to the VoE handler's internal memory, that + * points to the actual VoE data right after the VoE header (see + * ecrt_voe_handler_send_header()). + * + * - After a read operation was successful, the memory contains the received + * data. The size of the received data can be determined via + * ecrt_voe_handler_data_size(). + * - Before a write operation is triggered, the data have to be written to the + * internal memory. Be sure, that the data fit into the memory. The reserved + * memory size is a parameter of ecrt_slave_config_create_voe_handler(). + * + * \attention The returned pointer is not necessarily persistent: After a read + * operation, the internal memory may have been reallocated. This can be + * avoided by reserving enough memory via the \a size parameter of + * ecrt_slave_config_create_voe_handler(). + * + * \return Pointer to the internal memory. + */ +uint8_t *ecrt_voe_handler_data( + ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/** Returns the current data size. + * + * The data size is the size of the VoE data without the header (see + * ecrt_voe_handler_send_header()). + * + * When the VoE handler is created, the data size is set to the size of the + * reserved memory. At a write operation, the data size is set to the number + * of bytes to write. After a read operation the size is set to the size of + * the read data. The size is not modified in any other situation. + * + * \return Data size in bytes. + */ +size_t ecrt_voe_handler_data_size( + const ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/** Start a VoE write operation. + * + * After this function has been called, the ecrt_voe_handler_execute() method + * must be called in every bus cycle as long as it returns EC_REQUEST_BUSY. No + * other operation may be started while the handler is busy. + */ +void ecrt_voe_handler_write( + ec_voe_handler_t *voe, /**< VoE handler. */ + size_t size /**< Number of bytes to write (without the VoE header). */ + ); + +/** Start a VoE read operation. + * + * After this function has been called, the ecrt_voe_handler_execute() method + * must be called in every bus cycle as long as it returns EC_REQUEST_BUSY. No + * other operation may be started while the handler is busy. + * + * The state machine queries the slave's send mailbox for new data to be send + * to the master. If no data appear within the EC_VOE_RESPONSE_TIMEOUT + * (defined in master/voe_handler.c), the operation fails. + * + * On success, the size of the read data can be determined via + * ecrt_voe_handler_data_size(), while the VoE header of the received data + * can be retrieved with ecrt_voe_handler_received_header(). + */ +void ecrt_voe_handler_read( + ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/** Start a VoE read operation without querying the sync manager status. + * + * After this function has been called, the ecrt_voe_handler_execute() method + * must be called in every bus cycle as long as it returns EC_REQUEST_BUSY. No + * other operation may be started while the handler is busy. + * + * The state machine queries the slave by sending an empty mailbox. The slave + * fills its data to the master in this mailbox. If no data appear within the + * EC_VOE_RESPONSE_TIMEOUT (defined in master/voe_handler.c), the operation + * fails. + * + * On success, the size of the read data can be determined via + * ecrt_voe_handler_data_size(), while the VoE header of the received data + * can be retrieved with ecrt_voe_handler_received_header(). + */ +void ecrt_voe_handler_read_nosync( + ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/** Execute the handler. + * + * This method executes the VoE handler. It has to be called in every bus + * cycle as long as it returns EC_REQUEST_BUSY. + * + * \return Handler state. + */ +ec_request_state_t ecrt_voe_handler_execute( + ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/***************************************************************************** + * Register request methods. + ****************************************************************************/ + +/** Access to the register request's data. + * + * This function returns a pointer to the request's internal memory. + * + * - After a read operation was successful, integer data can be evaluated + * using the EC_READ_*() macros as usual. Example: + * \code + * uint16_t value = EC_READ_U16(ecrt_reg_request_data(reg_request))); + * \endcode + * - If a write operation shall be triggered, the data have to be written to + * the internal memory. Use the EC_WRITE_*() macros, if you are writing + * integer data. Be sure, that the data fit into the memory. The memory size + * is a parameter of ecrt_slave_config_create_reg_request(). + * \code + * EC_WRITE_U16(ecrt_reg_request_data(reg_request), 0xFFFF); + * \endcode + * + * \return Pointer to the internal memory. + */ +uint8_t *ecrt_reg_request_data( + ec_reg_request_t *req /**< Register request. */ + ); + +/** Get the current state of the register request. + * + * \return Request state. + */ +#ifdef __KERNEL__ +ec_request_state_t ecrt_reg_request_state( + const ec_reg_request_t *req /**< Register request. */ + ); +#else +ec_request_state_t ecrt_reg_request_state( + ec_reg_request_t *req /**< Register request. */ + ); +#endif + +/** Schedule an register write operation. + * + * \attention This method may not be called while ecrt_reg_request_state() + * returns EC_REQUEST_BUSY. + * + * \attention The \a size parameter is truncated to the size given at request + * creation. + */ +void ecrt_reg_request_write( + ec_reg_request_t *req, /**< Register request. */ + uint16_t address, /**< Register address. */ + size_t size /**< Size to write. */ + ); + +/** Schedule a register read operation. + * + * \attention This method may not be called while ecrt_reg_request_state() + * returns EC_REQUEST_BUSY. + * + * \attention The \a size parameter is truncated to the size given at request + * creation. + */ +void ecrt_reg_request_read( + ec_reg_request_t *req, /**< Register request. */ + uint16_t address, /**< Register address. */ + size_t size /**< Size to write. */ + ); + +/****************************************************************************** + * Bitwise read/write macros + *****************************************************************************/ + +/** Read a certain bit of an EtherCAT data byte. + * + * \param DATA EtherCAT data pointer + * \param POS bit position + */ +#define EC_READ_BIT(DATA, POS) ((*((uint8_t *) (DATA)) >> (POS)) & 0x01) + +/** Write a certain bit of an EtherCAT data byte. + * + * \param DATA EtherCAT data pointer + * \param POS bit position + * \param VAL new bit value + */ +#define EC_WRITE_BIT(DATA, POS, VAL) \ + do { \ + if (VAL) *((uint8_t *) (DATA)) |= (1 << (POS)); \ + else *((uint8_t *) (DATA)) &= ~(1 << (POS)); \ + } while (0) + +/****************************************************************************** + * Byte-swapping functions for user space + *****************************************************************************/ + +#ifndef __KERNEL__ + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +#define le16_to_cpu(x) x +#define le32_to_cpu(x) x +#define le64_to_cpu(x) x + +#define cpu_to_le16(x) x +#define cpu_to_le32(x) x +#define cpu_to_le64(x) x + +#elif __BYTE_ORDER == __BIG_ENDIAN + +#define swap16(x) \ + ((uint16_t)( \ + (((uint16_t)(x) & 0x00ffU) << 8) | \ + (((uint16_t)(x) & 0xff00U) >> 8) )) +#define swap32(x) \ + ((uint32_t)( \ + (((uint32_t)(x) & 0x000000ffUL) << 24) | \ + (((uint32_t)(x) & 0x0000ff00UL) << 8) | \ + (((uint32_t)(x) & 0x00ff0000UL) >> 8) | \ + (((uint32_t)(x) & 0xff000000UL) >> 24) )) +#define swap64(x) \ + ((uint64_t)( \ + (((uint64_t)(x) & 0x00000000000000ffULL) << 56) | \ + (((uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \ + (((uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \ + (((uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \ + (((uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \ + (((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \ + (((uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \ + (((uint64_t)(x) & 0xff00000000000000ULL) >> 56) )) + +#define le16_to_cpu(x) swap16(x) +#define le32_to_cpu(x) swap32(x) +#define le64_to_cpu(x) swap64(x) + +#define cpu_to_le16(x) swap16(x) +#define cpu_to_le32(x) swap32(x) +#define cpu_to_le64(x) swap64(x) + +#endif + +#define le16_to_cpup(x) le16_to_cpu(*((uint16_t *)(x))) +#define le32_to_cpup(x) le32_to_cpu(*((uint32_t *)(x))) +#define le64_to_cpup(x) le64_to_cpu(*((uint64_t *)(x))) + +#endif /* ifndef __KERNEL__ */ + +/****************************************************************************** + * Read macros + *****************************************************************************/ + +/** Read an 8-bit unsigned value from EtherCAT data. + * + * \return EtherCAT data value + */ +#define EC_READ_U8(DATA) \ + ((uint8_t) *((uint8_t *) (DATA))) + +/** Read an 8-bit signed value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_S8(DATA) \ + ((int8_t) *((uint8_t *) (DATA))) + +/** Read a 16-bit unsigned value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_U16(DATA) \ + ((uint16_t) le16_to_cpup((void *) (DATA))) + +/** Read a 16-bit signed value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_S16(DATA) \ + ((int16_t) le16_to_cpup((void *) (DATA))) + +/** Read a 32-bit unsigned value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_U32(DATA) \ + ((uint32_t) le32_to_cpup((void *) (DATA))) + +/** Read a 32-bit signed value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_S32(DATA) \ + ((int32_t) le32_to_cpup((void *) (DATA))) + +/** Read a 64-bit unsigned value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_U64(DATA) \ + ((uint64_t) le64_to_cpup((void *) (DATA))) + +/** Read a 64-bit signed value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_S64(DATA) \ + ((int64_t) le64_to_cpup((void *) (DATA))) + +/****************************************************************************** + * Floating-point read functions and macros (userspace only) + *****************************************************************************/ + +#ifndef __KERNEL__ + +/** Read a 32-bit floating-point value from EtherCAT data. + * + * \param data EtherCAT data pointer + * \return EtherCAT data value + */ +float ecrt_read_real(const void *data); + +/** Read a 32-bit floating-point value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_REAL(DATA) ecrt_read_real(DATA) + +/** Read a 64-bit floating-point value from EtherCAT data. + * + * \param data EtherCAT data pointer + * \return EtherCAT data value + */ +double ecrt_read_lreal(const void *data); + +/** Read a 64-bit floating-point value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_LREAL(DATA) ecrt_read_lreal(DATA) + +#endif // ifndef __KERNEL__ + +/****************************************************************************** + * Write macros + *****************************************************************************/ + +/** Write an 8-bit unsigned value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_U8(DATA, VAL) \ + do { \ + *((uint8_t *)(DATA)) = ((uint8_t) (VAL)); \ + } while (0) + +/** Write an 8-bit signed value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_S8(DATA, VAL) EC_WRITE_U8(DATA, VAL) + +/** Write a 16-bit unsigned value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_U16(DATA, VAL) \ + do { \ + *((uint16_t *) (DATA)) = cpu_to_le16((uint16_t) (VAL)); \ + } while (0) + +/** Write a 16-bit signed value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_S16(DATA, VAL) EC_WRITE_U16(DATA, VAL) + +/** Write a 32-bit unsigned value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_U32(DATA, VAL) \ + do { \ + *((uint32_t *) (DATA)) = cpu_to_le32((uint32_t) (VAL)); \ + } while (0) + +/** Write a 32-bit signed value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_S32(DATA, VAL) EC_WRITE_U32(DATA, VAL) + +/** Write a 64-bit unsigned value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_U64(DATA, VAL) \ + do { \ + *((uint64_t *) (DATA)) = cpu_to_le64((uint64_t) (VAL)); \ + } while (0) + +/** Write a 64-bit signed value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_S64(DATA, VAL) EC_WRITE_U64(DATA, VAL) + +/****************************************************************************** + * Floating-point write functions and macros (userspace only) + *****************************************************************************/ + +#ifndef __KERNEL__ + +/** Write a 32-bit floating-point value to EtherCAT data. + * + * \param data EtherCAT data pointer + * \param value new value + */ +void ecrt_write_real(void *data, float value); + +/** Write a 32-bit floating-point value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_REAL(DATA, VAL) ecrt_write_real(DATA, VAL) + +/** Write a 64-bit floating-point value to EtherCAT data. + * + * \param data EtherCAT data pointer + * \param value new value + */ +void ecrt_write_lreal(void *data, double value); + +/** Write a 64-bit floating-point value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_LREAL(DATA, VAL) ecrt_write_lreal(DATA, VAL) + +#endif // ifndef __KERNEL__ + +/*****************************************************************************/ + +#ifdef __cplusplus +} +#endif + +/*****************************************************************************/ + +/** @} */ + +#endif diff --git a/net/ethercat/include/ectty.h b/net/ethercat/include/ectty.h new file mode 100644 index 000000000000..4b45d96ec52a --- /dev/null +++ b/net/ethercat/include/ectty.h @@ -0,0 +1,114 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT master userspace library. + * + * The IgH EtherCAT master userspace library is free software; you can + * redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation; version 2.1 + * of the License. + * + * The IgH EtherCAT master userspace library is distributed in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the IgH EtherCAT master userspace library. If not, see + * <http://www.gnu.org/licenses/>. + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * + * EtherCAT virtual TTY interface. + * + * \defgroup TTYInterface EtherCAT Virtual TTY Interface + * + * @{ + */ + +/*****************************************************************************/ + +#ifndef __ECTTY_H__ +#define __ECTTY_H__ + +#include <linux/termios.h> + +/****************************************************************************** + * Data types + *****************************************************************************/ + +struct ec_tty; +typedef struct ec_tty ec_tty_t; /**< \see ec_tty */ + +/** Operations on the virtual TTY interface. + */ +typedef struct { + int (*cflag_changed)(void *, tcflag_t); /**< Called when the serial + * settings shall be changed. The + * \a cflag argument contains the + * new settings. */ +} ec_tty_operations_t; + +/****************************************************************************** + * Global functions + *****************************************************************************/ + +/** Create a virtual TTY interface. + * + * \param ops Set of callbacks. + * \param cb_data Arbitrary data, that is passed to any callback. + * + * \return Pointer to the interface object, otherwise an ERR_PTR value. + */ +ec_tty_t *ectty_create( + const ec_tty_operations_t *ops, + void *cb_data + ); + +/****************************************************************************** + * TTY interface methods + *****************************************************************************/ + +/** Releases a virtual TTY interface. + */ +void ectty_free( + ec_tty_t *tty /**< TTY interface. */ + ); + +/** Reads data to send from the TTY interface. + * + * If there are data to send, they are copied into the \a buffer. At maximum, + * \a size bytes are copied. The actual number of bytes copied is returned. + * + * \return Number of bytes copied. + */ +unsigned int ectty_tx_data( + ec_tty_t *tty, /**< TTY interface. */ + uint8_t *buffer, /**< Buffer for data to transmit. */ + size_t size /**< Available space in \a buffer. */ + ); + +/** Pushes received data to the TTY interface. + */ +void ectty_rx_data( + ec_tty_t *tty, /**< TTY interface. */ + const uint8_t *buffer, /**< Buffer with received data. */ + size_t size /**< Number of bytes in \a buffer. */ + ); + +/*****************************************************************************/ + +/** @} */ + +#endif diff --git a/net/ethercat/master/Kconfig b/net/ethercat/master/Kconfig new file mode 100644 index 000000000000..92482be5cb35 --- /dev/null +++ b/net/ethercat/master/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ethercat +# + +config ETHERCAT_MASTER + tristate "ethercat master" + default n + help + Support for ethercat master driver. + If unsure, say Y. + +config ETHERCAT_RTDM + bool "ethercat rtdm" + depends on ETHERCAT_MASTER + default n + help + Support for ethercat master driver. + If unsure, say Y. diff --git a/net/ethercat/master/Makefile b/net/ethercat/master/Makefile new file mode 100644 index 000000000000..dff96f52e2d8 --- /dev/null +++ b/net/ethercat/master/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the ethercat driver. +# + +obj-$(CONFIG_ETHERCAT_MASTER) += ethercat_master.o +ethercat_master-y += cdev.o coe_emerg_ring.o datagram.o datagram_pair.o debug.o device.o \ + domain.o doxygen.o ethernet.o fmmu_config.o foe_request.o fsm_change.o \ + fsm_coe.o fsm_foe.o fsm_master.o fsm_pdo.o fsm_pdo_entry.o fsm_sii.o \ + fsm_slave.o fsm_slave_config.o fsm_slave_scan.o fsm_soe.o ioctl.o \ + mailbox.o master.o module.o pdo.o pdo_entry.o pdo_list.o reg_request.o \ + sdo.o sdo_entry.o sdo_request.o slave.o slave_config.o \ + soe_errors.o soe_request.o sync.o sync_config.o voe_handler.o +ethercat_master-$(CONFIG_ETHERCAT_RTDM) += rtdm.o rtdm-ioctl.o diff --git a/net/ethercat/master/cdev.c b/net/ethercat/master/cdev.c new file mode 100644 index 000000000000..5bba2be061f7 --- /dev/null +++ b/net/ethercat/master/cdev.c @@ -0,0 +1,333 @@ +/****************************************************************************** + * + * Copyright (C) 2006-2020 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT master character device. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> + +#include "cdev.h" +#include "master.h" +#include "slave_config.h" +#include "voe_handler.h" +#include "ethernet.h" +#include "ioctl.h" + +/** Set to 1 to enable device operations debugging. + */ +#define DEBUG 0 + +/*****************************************************************************/ + +static int eccdev_open(struct inode *, struct file *); +static int eccdev_release(struct inode *, struct file *); +static long eccdev_ioctl(struct file *, unsigned int, unsigned long); +static int eccdev_mmap(struct file *, struct vm_area_struct *); + +/** This is the kernel version from which the .fault member of the + * vm_operations_struct is usable. + */ +#define PAGE_FAULT_VERSION KERNEL_VERSION(2, 6, 23) + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0) +# define FAULT_RETURN_TYPE int +#else +# define FAULT_RETURN_TYPE vm_fault_t +#endif + +#if LINUX_VERSION_CODE >= PAGE_FAULT_VERSION +static FAULT_RETURN_TYPE eccdev_vma_fault( +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) + struct vm_area_struct *, +#endif + struct vm_fault *); +#else +static struct page *eccdev_vma_nopage( + struct vm_area_struct *, unsigned long, int *); +#endif + +/*****************************************************************************/ + +/** File operation callbacks for the EtherCAT character device. + */ +static struct file_operations eccdev_fops = { + .owner = THIS_MODULE, + .open = eccdev_open, + .release = eccdev_release, + .unlocked_ioctl = eccdev_ioctl, + .mmap = eccdev_mmap +}; + +/** Callbacks for a virtual memory area retrieved with ecdevc_mmap(). + */ +struct vm_operations_struct eccdev_vm_ops = { +#if LINUX_VERSION_CODE >= PAGE_FAULT_VERSION + .fault = eccdev_vma_fault +#else + .nopage = eccdev_vma_nopage +#endif +}; + +/*****************************************************************************/ + +/** Private data structure for file handles. + */ +typedef struct { + ec_cdev_t *cdev; /**< Character device. */ + ec_ioctl_context_t ctx; /**< Context. */ +} ec_cdev_priv_t; + +/*****************************************************************************/ + +/** Constructor. + * + * \return 0 in case of success, else < 0 + */ +int ec_cdev_init( + ec_cdev_t *cdev, /**< EtherCAT master character device. */ + ec_master_t *master, /**< Parent master. */ + dev_t dev_num /**< Device number. */ + ) +{ + int ret; + + cdev->master = master; + + cdev_init(&cdev->cdev, &eccdev_fops); + cdev->cdev.owner = THIS_MODULE; + + ret = cdev_add(&cdev->cdev, + MKDEV(MAJOR(dev_num), master->index), 1); + if (ret) { + EC_MASTER_ERR(master, "Failed to add character device!\n"); + } + + return ret; +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_cdev_clear(ec_cdev_t *cdev /**< EtherCAT XML device */) +{ + cdev_del(&cdev->cdev); +} + +/****************************************************************************** + * File operations + *****************************************************************************/ + +/** Called when the cdev is opened. + */ +int eccdev_open(struct inode *inode, struct file *filp) +{ + ec_cdev_t *cdev = container_of(inode->i_cdev, ec_cdev_t, cdev); + ec_cdev_priv_t *priv; + + priv = kmalloc(sizeof(ec_cdev_priv_t), GFP_KERNEL); + if (!priv) { + EC_MASTER_ERR(cdev->master, + "Failed to allocate memory for private data structure.\n"); + return -ENOMEM; + } + + priv->cdev = cdev; + priv->ctx.writable = (filp->f_mode & FMODE_WRITE) != 0; + priv->ctx.requested = 0; + priv->ctx.process_data = NULL; + priv->ctx.process_data_size = 0; + + filp->private_data = priv; + +#if DEBUG + EC_MASTER_DBG(cdev->master, 0, "File opened.\n"); +#endif + return 0; +} + +/*****************************************************************************/ + +/** Called when the cdev is closed. + */ +int eccdev_release(struct inode *inode, struct file *filp) +{ + ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data; + ec_master_t *master = priv->cdev->master; + + if (priv->ctx.requested) { + ecrt_release_master(master); + } + + if (priv->ctx.process_data) { + vfree(priv->ctx.process_data); + } + +#if DEBUG + EC_MASTER_DBG(master, 0, "File closed.\n"); +#endif + + kfree(priv); + return 0; +} + +/*****************************************************************************/ + +/** Called when an ioctl() command is issued. + */ +long eccdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data; + +#if DEBUG + EC_MASTER_DBG(priv->cdev->master, 0, + "ioctl(filp = 0x%p, cmd = 0x%08x (0x%02x), arg = 0x%lx)\n", + filp, cmd, _IOC_NR(cmd), arg); +#endif + + return ec_ioctl(priv->cdev->master, &priv->ctx, cmd, (void __user *) arg); +} + +/*****************************************************************************/ + +#ifndef VM_DONTDUMP +/** VM_RESERVED disappeared in 3.7. + */ +#define VM_DONTDUMP VM_RESERVED +#endif + +/** Memory-map callback for the EtherCAT character device. + * + * The actual mapping will be done in the eccdev_vma_nopage() callback of the + * virtual memory area. + * + * \return Always zero (success). + */ +int eccdev_mmap( + struct file *filp, + struct vm_area_struct *vma + ) +{ + ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data; + + EC_MASTER_DBG(priv->cdev->master, 1, "mmap()\n"); + + vma->vm_ops = &eccdev_vm_ops; + vma->vm_flags |= VM_DONTDUMP; /* Pages will not be swapped out */ + vma->vm_private_data = priv; + + return 0; +} + +/*****************************************************************************/ + +#if LINUX_VERSION_CODE >= PAGE_FAULT_VERSION + +/** Page fault callback for a virtual memory area. + * + * Called at the first access on a virtual-memory area retrieved with + * ecdev_mmap(). + * + * \return Zero on success, otherwise a negative error code. + */ +static FAULT_RETURN_TYPE eccdev_vma_fault( +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) + struct vm_area_struct *vma, /**< Virtual memory area. */ +#endif + struct vm_fault *vmf /**< Fault data. */ + ) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + struct vm_area_struct *vma = vmf->vma; +#endif + unsigned long offset = vmf->pgoff << PAGE_SHIFT; + ec_cdev_priv_t *priv = (ec_cdev_priv_t *) vma->vm_private_data; + struct page *page; + + if (offset >= priv->ctx.process_data_size) { + return VM_FAULT_SIGBUS; + } + + page = vmalloc_to_page(priv->ctx.process_data + offset); + if (!page) { + return VM_FAULT_SIGBUS; + } + + get_page(page); + vmf->page = page; + + EC_MASTER_DBG(priv->cdev->master, 1, "Vma fault," + " offset = %lu, page = %p\n", offset, page); + + return 0; +} + +#else + +/** Nopage callback for a virtual memory area. + * + * Called at the first access on a virtual-memory area retrieved with + * ecdev_mmap(). + */ +struct page *eccdev_vma_nopage( + struct vm_area_struct *vma, /**< Virtual memory area initialized by + the kernel. */ + unsigned long address, /**< Requested virtual address. */ + int *type /**< Type output parameter. */ + ) +{ + unsigned long offset; + struct page *page = NOPAGE_SIGBUS; + ec_cdev_priv_t *priv = (ec_cdev_priv_t *) vma->vm_private_data; + ec_master_t *master = priv->cdev->master; + + offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); + + if (offset >= priv->ctx.process_data_size) + return NOPAGE_SIGBUS; + + page = vmalloc_to_page(priv->ctx.process_data + offset); + + EC_MASTER_DBG(master, 1, "Nopage fault vma, address = %#lx," + " offset = %#lx, page = %p\n", address, offset, page); + + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + + return page; +} + +#endif + +/*****************************************************************************/ diff --git a/net/ethercat/master/cdev.h b/net/ethercat/master/cdev.h new file mode 100644 index 000000000000..a06bb1c90562 --- /dev/null +++ b/net/ethercat/master/cdev.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT master character device. +*/ + +/*****************************************************************************/ + +#ifndef __EC_CDEV_H__ +#define __EC_CDEV_H__ + +#include <linux/fs.h> +#include <linux/cdev.h> + +#include "globals.h" + +/*****************************************************************************/ + +/** EtherCAT master character device. +*/ +typedef struct { + ec_master_t *master; /**< Master owning the device. */ + struct cdev cdev; /**< Character device. */ +} ec_cdev_t; + +/*****************************************************************************/ + +int ec_cdev_init(ec_cdev_t *, ec_master_t *, dev_t); +void ec_cdev_clear(ec_cdev_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/coe_emerg_ring.c b/net/ethercat/master/coe_emerg_ring.c new file mode 100644 index 000000000000..82c33db9aeab --- /dev/null +++ b/net/ethercat/master/coe_emerg_ring.c @@ -0,0 +1,177 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + * vim: expandtab + * + *****************************************************************************/ + +/** \file + * EtherCAT CoE emergency ring buffer methods. + */ + +/*****************************************************************************/ + +#include <linux/slab.h> + +#include "coe_emerg_ring.h" + +/*****************************************************************************/ + +/** Emergency ring buffer constructor. + */ +void ec_coe_emerg_ring_init( + ec_coe_emerg_ring_t *ring, /**< Emergency ring. */ + ec_slave_config_t *sc /**< Slave configuration. */ + ) +{ + ring->sc = sc; + ring->msgs = NULL; + ring->size = 0; + ring->read_index = 0; + ring->write_index = 0; + ring->overruns = 0; +} + +/*****************************************************************************/ + +/** Emergency ring buffer destructor. + */ +void ec_coe_emerg_ring_clear( + ec_coe_emerg_ring_t *ring /**< Emergency ring. */ + ) +{ + if (ring->msgs) { + kfree(ring->msgs); + } +} + +/*****************************************************************************/ + +/** Set the ring size. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_coe_emerg_ring_size( + ec_coe_emerg_ring_t *ring, /**< Emergency ring. */ + size_t size /**< Maximum number of messages in the ring. */ + ) +{ + ring->size = 0; + + if (size < 0) { + size = 0; + } + + ring->read_index = ring->write_index = 0; + + if (ring->msgs) { + kfree(ring->msgs); + } + ring->msgs = NULL; + + if (size == 0) { + return 0; + } + + ring->msgs = kmalloc(sizeof(ec_coe_emerg_msg_t) * (size + 1), GFP_KERNEL); + if (!ring->msgs) { + return -ENOMEM; + } + + ring->size = size; + return 0; +} + +/*****************************************************************************/ + +/** Add a new emergency message. + */ +void ec_coe_emerg_ring_push( + ec_coe_emerg_ring_t *ring, /**< Emergency ring. */ + const u8 *msg /**< Emergency message. */ + ) +{ + if (!ring->size || + (ring->write_index + 1) % (ring->size + 1) == ring->read_index) { + ring->overruns++; + return; + } + + memcpy(ring->msgs[ring->write_index].data, msg, + EC_COE_EMERGENCY_MSG_SIZE); + ring->write_index = (ring->write_index + 1) % (ring->size + 1); +} + +/*****************************************************************************/ + +/** Remove an emergency message from the ring. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_coe_emerg_ring_pop( + ec_coe_emerg_ring_t *ring, /**< Emergency ring. */ + u8 *msg /**< Memory to store the emergency message. */ + ) +{ + if (ring->read_index == ring->write_index) { + return -ENOENT; + } + + memcpy(msg, ring->msgs[ring->read_index].data, EC_COE_EMERGENCY_MSG_SIZE); + ring->read_index = (ring->read_index + 1) % (ring->size + 1); + return 0; +} + +/*****************************************************************************/ + +/** Clear the ring. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_coe_emerg_ring_clear_ring( + ec_coe_emerg_ring_t *ring /**< Emergency ring. */ + ) +{ + ring->read_index = ring->write_index; + ring->overruns = 0; + return 0; +} + +/*****************************************************************************/ + +/** Read the number of overruns. + * + * \return Number of overruns. + */ +int ec_coe_emerg_ring_overruns( + ec_coe_emerg_ring_t *ring /**< Emergency ring. */ + ) +{ + return ring->overruns; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/coe_emerg_ring.h b/net/ethercat/master/coe_emerg_ring.h new file mode 100644 index 000000000000..c2f6438ca495 --- /dev/null +++ b/net/ethercat/master/coe_emerg_ring.h @@ -0,0 +1,78 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT CoE emergency ring buffer structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_COE_EMERG_RING_H__ +#define __EC_COE_EMERG_RING_H__ + +#include "globals.h" + +/*****************************************************************************/ + +/** EtherCAT CoE emergency message record. + */ +typedef struct { + u8 data[EC_COE_EMERGENCY_MSG_SIZE]; /**< Message data. */ +} ec_coe_emerg_msg_t; + +/*****************************************************************************/ + +/** EtherCAT CoE emergency ring buffer. + */ +typedef struct { + ec_slave_config_t *sc; /**< Slave configuration owning the ring. */ + + ec_coe_emerg_msg_t *msgs; /**< Message ring. */ + size_t size; /**< Ring size. */ + + unsigned int read_index; /**< Read index. */ + unsigned int write_index; /**< Write index. */ + unsigned int overruns; /**< Number of overruns since last reset. */ +} ec_coe_emerg_ring_t; + +/*****************************************************************************/ + +void ec_coe_emerg_ring_init(ec_coe_emerg_ring_t *, ec_slave_config_t *); +void ec_coe_emerg_ring_clear(ec_coe_emerg_ring_t *); + +int ec_coe_emerg_ring_size(ec_coe_emerg_ring_t *, size_t); +void ec_coe_emerg_ring_push(ec_coe_emerg_ring_t *, const u8 *); +int ec_coe_emerg_ring_pop(ec_coe_emerg_ring_t *, u8 *); +int ec_coe_emerg_ring_clear_ring(ec_coe_emerg_ring_t *); +int ec_coe_emerg_ring_overruns(ec_coe_emerg_ring_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/datagram.c b/net/ethercat/master/datagram.c new file mode 100644 index 000000000000..f6163274dd7d --- /dev/null +++ b/net/ethercat/master/datagram.c @@ -0,0 +1,649 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Methods of an EtherCAT datagram. +*/ + +/*****************************************************************************/ + +#include <linux/slab.h> + +#include "datagram.h" +#include "master.h" + +/*****************************************************************************/ + +/** \cond */ + +#define EC_FUNC_HEADER \ + ret = ec_datagram_prealloc(datagram, data_size); \ + if (unlikely(ret)) \ + return ret; \ + datagram->index = 0; \ + datagram->working_counter = 0; \ + datagram->state = EC_DATAGRAM_INIT; + +#define EC_FUNC_FOOTER \ + datagram->data_size = data_size; \ + return 0; + +/** \endcond */ + +/*****************************************************************************/ + +/** Array of datagram type strings used in ec_datagram_type_string(). + * + * \attention This is indexed by ec_datagram_type_t. + */ +static const char *type_strings[] = { + "?", + "APRD", + "APWR", + "APRW", + "FPRD", + "FPWR", + "FPRW", + "BRD", + "BWR", + "BRW", + "LRD", + "LWR", + "LRW", + "ARMW", + "FRMW" +}; + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_datagram_init(ec_datagram_t *datagram /**< EtherCAT datagram. */) +{ + INIT_LIST_HEAD(&datagram->queue); // mark as unqueued + datagram->device_index = EC_DEVICE_MAIN; + datagram->type = EC_DATAGRAM_NONE; + memset(datagram->address, 0x00, EC_ADDR_LEN); + datagram->data = NULL; + datagram->data_origin = EC_ORIG_INTERNAL; + datagram->mem_size = 0; + datagram->data_size = 0; + datagram->index = 0x00; + datagram->working_counter = 0x0000; + datagram->state = EC_DATAGRAM_INIT; +#ifdef EC_HAVE_CYCLES + datagram->cycles_sent = 0; +#endif + datagram->jiffies_sent = 0; +#ifdef EC_HAVE_CYCLES + datagram->cycles_received = 0; +#endif + datagram->jiffies_received = 0; + datagram->skip_count = 0; + datagram->stats_output_jiffies = 0; + memset(datagram->name, 0x00, EC_DATAGRAM_NAME_SIZE); +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_datagram_clear(ec_datagram_t *datagram /**< EtherCAT datagram. */) +{ + ec_datagram_unqueue(datagram); + + if (datagram->data_origin == EC_ORIG_INTERNAL && datagram->data) { + kfree(datagram->data); + datagram->data = NULL; + } +} + +/*****************************************************************************/ + +/** Unqueue datagram. + */ +void ec_datagram_unqueue(ec_datagram_t *datagram /**< EtherCAT datagram. */) +{ + if (!list_empty(&datagram->queue)) { + list_del_init(&datagram->queue); + } +} + +/*****************************************************************************/ + +/** Allocates internal payload memory. + * + * If the allocated memory is already larger than requested, nothing ist done. + * + * \attention If external payload memory has been provided, no range checking + * is done! + * + * \return 0 in case of success, otherwise \a -ENOMEM. + */ +int ec_datagram_prealloc( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + size_t size /**< New payload size in bytes. */ + ) +{ + if (datagram->data_origin == EC_ORIG_EXTERNAL + || size <= datagram->mem_size) + return 0; + + if (datagram->data) { + kfree(datagram->data); + datagram->data = NULL; + datagram->mem_size = 0; + } + + if (!(datagram->data = kmalloc(size, GFP_KERNEL))) { + EC_ERR("Failed to allocate %zu bytes of datagram memory!\n", size); + return -ENOMEM; + } + + datagram->mem_size = size; + return 0; +} + +/*****************************************************************************/ + +/** Fills the datagram payload memory with zeros. + */ +void ec_datagram_zero(ec_datagram_t *datagram /**< EtherCAT datagram. */) +{ + memset(datagram->data, 0x00, datagram->data_size); +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT APRD datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_aprd( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t ring_position, /**< Auto-increment address. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to read. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_APRD; + EC_WRITE_S16(datagram->address, (int16_t) ring_position * (-1)); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT APWR datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_apwr( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t ring_position, /**< Auto-increment address. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to write. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_APWR; + EC_WRITE_S16(datagram->address, (int16_t) ring_position * (-1)); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT APRW datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_aprw( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t ring_position, /**< Auto-increment address. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to write. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_APRW; + EC_WRITE_S16(datagram->address, (int16_t) ring_position * (-1)); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT ARMW datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_armw( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t ring_position, /**< Auto-increment address. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to read. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_ARMW; + EC_WRITE_S16(datagram->address, (int16_t) ring_position * (-1)); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT FPRD datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_fprd( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t configured_address, /**< Configured station address. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to read. */ + ) +{ + int ret; + + if (unlikely(configured_address == 0x0000)) + EC_WARN("Using configured station address 0x0000!\n"); + + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_FPRD; + EC_WRITE_U16(datagram->address, configured_address); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT FPWR datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_fpwr( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t configured_address, /**< Configured station address. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to write. */ + ) +{ + int ret; + + if (unlikely(configured_address == 0x0000)) + EC_WARN("Using configured station address 0x0000!\n"); + + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_FPWR; + EC_WRITE_U16(datagram->address, configured_address); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT FPRW datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_fprw( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t configured_address, /**< Configured station address. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to write. */ + ) +{ + int ret; + + if (unlikely(configured_address == 0x0000)) + EC_WARN("Using configured station address 0x0000!\n"); + + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_FPRW; + EC_WRITE_U16(datagram->address, configured_address); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT FRMW datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_frmw( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t configured_address, /**< Configured station address. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to write. */ + ) +{ + int ret; + + if (unlikely(configured_address == 0x0000)) + EC_WARN("Using configured station address 0x0000!\n"); + + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_FRMW; + EC_WRITE_U16(datagram->address, configured_address); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT BRD datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_brd( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to read. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_BRD; + EC_WRITE_U16(datagram->address, 0x0000); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT BWR datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_bwr( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to write. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_BWR; + EC_WRITE_U16(datagram->address, 0x0000); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT BRW datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_brw( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint16_t mem_address, /**< Physical memory address. */ + size_t data_size /**< Number of bytes to write. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_BRW; + EC_WRITE_U16(datagram->address, 0x0000); + EC_WRITE_U16(datagram->address + 2, mem_address); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT LRD datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_lrd( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint32_t offset, /**< Logical address. */ + size_t data_size /**< Number of bytes to read/write. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_LRD; + EC_WRITE_U32(datagram->address, offset); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT LWR datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_lwr( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint32_t offset, /**< Logical address. */ + size_t data_size /**< Number of bytes to read/write. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_LWR; + EC_WRITE_U32(datagram->address, offset); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT LRW datagram. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_lrw( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint32_t offset, /**< Logical address. */ + size_t data_size /**< Number of bytes to read/write. */ + ) +{ + int ret; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_LRW; + EC_WRITE_U32(datagram->address, offset); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT LRD datagram with external memory. + * + * \attention It is assumed, that the external memory is at least \a data_size + * bytes large. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_lrd_ext( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint32_t offset, /**< Logical address. */ + size_t data_size, /**< Number of bytes to read/write. */ + uint8_t *external_memory /**< Pointer to the memory to use. */ + ) +{ + int ret; + datagram->data = external_memory; + datagram->data_origin = EC_ORIG_EXTERNAL; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_LRD; + EC_WRITE_U32(datagram->address, offset); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT LWR datagram with external memory. + * + * \attention It is assumed, that the external memory is at least \a data_size + * bytes large. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_lwr_ext( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint32_t offset, /**< Logical address. */ + size_t data_size, /**< Number of bytes to read/write. */ + uint8_t *external_memory /**< Pointer to the memory to use. */ + ) +{ + int ret; + datagram->data = external_memory; + datagram->data_origin = EC_ORIG_EXTERNAL; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_LWR; + EC_WRITE_U32(datagram->address, offset); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Initializes an EtherCAT LRW datagram with external memory. + * + * \attention It is assumed, that the external memory is at least \a data_size + * bytes large. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_datagram_lrw_ext( + ec_datagram_t *datagram, /**< EtherCAT datagram. */ + uint32_t offset, /**< Logical address. */ + size_t data_size, /**< Number of bytes to read/write. */ + uint8_t *external_memory /**< Pointer to the memory to use. */ + ) +{ + int ret; + datagram->data = external_memory; + datagram->data_origin = EC_ORIG_EXTERNAL; + EC_FUNC_HEADER; + datagram->type = EC_DATAGRAM_LRW; + EC_WRITE_U32(datagram->address, offset); + EC_FUNC_FOOTER; +} + +/*****************************************************************************/ + +/** Prints the state of a datagram. + * + * Outputs a text message. + */ +void ec_datagram_print_state( + const ec_datagram_t *datagram /**< EtherCAT datagram */ + ) +{ + printk("Datagram "); + switch (datagram->state) { + case EC_DATAGRAM_INIT: + printk("initialized"); + break; + case EC_DATAGRAM_QUEUED: + printk("queued"); + break; + case EC_DATAGRAM_SENT: + printk("sent"); + break; + case EC_DATAGRAM_RECEIVED: + printk("received"); + break; + case EC_DATAGRAM_TIMED_OUT: + printk("timed out"); + break; + case EC_DATAGRAM_ERROR: + printk("error"); + break; + default: + printk("???"); + } + + printk(".\n"); +} + +/*****************************************************************************/ + +/** Evaluates the working counter of a single-cast datagram. + * + * Outputs an error message. + */ +void ec_datagram_print_wc_error( + const ec_datagram_t *datagram /**< EtherCAT datagram */ + ) +{ + if (datagram->working_counter == 0) + printk("No response."); + else if (datagram->working_counter > 1) + printk("%u slaves responded!", datagram->working_counter); + else + printk("Success."); + printk("\n"); +} + +/*****************************************************************************/ + +/** Outputs datagram statistics at most every second. + */ +void ec_datagram_output_stats( + ec_datagram_t *datagram + ) +{ + if (jiffies - datagram->stats_output_jiffies > HZ) { + datagram->stats_output_jiffies = jiffies; + + if (unlikely(datagram->skip_count)) { + EC_WARN("Datagram %p (%s) was SKIPPED %u time%s.\n", + datagram, datagram->name, + datagram->skip_count, + datagram->skip_count == 1 ? "" : "s"); + datagram->skip_count = 0; + } + } +} + +/*****************************************************************************/ + +/** Returns a string describing the datagram type. + * + * \return Pointer on a static memory containing the requested string. + */ +const char *ec_datagram_type_string( + const ec_datagram_t *datagram /**< EtherCAT datagram. */ + ) +{ + return type_strings[datagram->type]; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/datagram.h b/net/ethercat/master/datagram.h new file mode 100644 index 000000000000..e902fbd43789 --- /dev/null +++ b/net/ethercat/master/datagram.h @@ -0,0 +1,148 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT datagram structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_DATAGRAM_H__ +#define __EC_DATAGRAM_H__ + +#include <linux/list.h> +#include <linux/time.h> +#include <linux/timex.h> + +#include "globals.h" + +/*****************************************************************************/ + +/** EtherCAT datagram type. + */ +typedef enum { + EC_DATAGRAM_NONE = 0x00, /**< Dummy. */ + EC_DATAGRAM_APRD = 0x01, /**< Auto Increment Physical Read. */ + EC_DATAGRAM_APWR = 0x02, /**< Auto Increment Physical Write. */ + EC_DATAGRAM_APRW = 0x03, /**< Auto Increment Physical ReadWrite. */ + EC_DATAGRAM_FPRD = 0x04, /**< Configured Address Physical Read. */ + EC_DATAGRAM_FPWR = 0x05, /**< Configured Address Physical Write. */ + EC_DATAGRAM_FPRW = 0x06, /**< Configured Address Physical ReadWrite. */ + EC_DATAGRAM_BRD = 0x07, /**< Broadcast Read. */ + EC_DATAGRAM_BWR = 0x08, /**< Broadcast Write. */ + EC_DATAGRAM_BRW = 0x09, /**< Broadcast ReadWrite. */ + EC_DATAGRAM_LRD = 0x0A, /**< Logical Read. */ + EC_DATAGRAM_LWR = 0x0B, /**< Logical Write. */ + EC_DATAGRAM_LRW = 0x0C, /**< Logical ReadWrite. */ + EC_DATAGRAM_ARMW = 0x0D, /**< Auto Increment Physical Read Multiple + Write. */ + EC_DATAGRAM_FRMW = 0x0E, /**< Configured Address Physical Read Multiple + Write. */ +} ec_datagram_type_t; + +/*****************************************************************************/ + +/** EtherCAT datagram state. + */ +typedef enum { + EC_DATAGRAM_INIT, /**< Initial state of a new datagram. */ + EC_DATAGRAM_QUEUED, /**< Queued for sending. */ + EC_DATAGRAM_SENT, /**< Sent (still in the queue). */ + EC_DATAGRAM_RECEIVED, /**< Received (dequeued). */ + EC_DATAGRAM_TIMED_OUT, /**< Timed out (dequeued). */ + EC_DATAGRAM_ERROR /**< Error while sending/receiving (dequeued). */ +} ec_datagram_state_t; + +/*****************************************************************************/ + +/** EtherCAT datagram. + */ +typedef struct { + struct list_head queue; /**< Master datagram queue item. */ + struct list_head sent; /**< Master list item for sent datagrams. */ + ec_device_index_t device_index; /**< Device via which the datagram shall + be / was sent. */ + ec_datagram_type_t type; /**< Datagram type (APRD, BWR, etc.). */ + uint8_t address[EC_ADDR_LEN]; /**< Recipient address. */ + uint8_t *data; /**< Datagram payload. */ + ec_origin_t data_origin; /**< Origin of the \a data memory. */ + size_t mem_size; /**< Datagram \a data memory size. */ + size_t data_size; /**< Size of the data in \a data. */ + uint8_t index; /**< Index (set by master). */ + uint16_t working_counter; /**< Working counter. */ + ec_datagram_state_t state; /**< State. */ +#ifdef EC_HAVE_CYCLES + cycles_t cycles_sent; /**< Time, when the datagram was sent. */ +#endif + unsigned long jiffies_sent; /**< Jiffies, when the datagram was sent. */ +#ifdef EC_HAVE_CYCLES + cycles_t cycles_received; /**< Time, when the datagram was received. */ +#endif + unsigned long jiffies_received; /**< Jiffies, when the datagram was + received. */ + unsigned int skip_count; /**< Number of requeues when not yet received. */ + unsigned long stats_output_jiffies; /**< Last statistics output. */ + char name[EC_DATAGRAM_NAME_SIZE]; /**< Description of the datagram. */ +} ec_datagram_t; + +/*****************************************************************************/ + +void ec_datagram_init(ec_datagram_t *); +void ec_datagram_clear(ec_datagram_t *); +void ec_datagram_unqueue(ec_datagram_t *); +int ec_datagram_prealloc(ec_datagram_t *, size_t); +void ec_datagram_zero(ec_datagram_t *); + +int ec_datagram_aprd(ec_datagram_t *, uint16_t, uint16_t, size_t); +int ec_datagram_apwr(ec_datagram_t *, uint16_t, uint16_t, size_t); +int ec_datagram_aprw(ec_datagram_t *, uint16_t, uint16_t, size_t); +int ec_datagram_armw(ec_datagram_t *, uint16_t, uint16_t, size_t); +int ec_datagram_fprd(ec_datagram_t *, uint16_t, uint16_t, size_t); +int ec_datagram_fpwr(ec_datagram_t *, uint16_t, uint16_t, size_t); +int ec_datagram_fprw(ec_datagram_t *, uint16_t, uint16_t, size_t); +int ec_datagram_frmw(ec_datagram_t *, uint16_t, uint16_t, size_t); +int ec_datagram_brd(ec_datagram_t *, uint16_t, size_t); +int ec_datagram_bwr(ec_datagram_t *, uint16_t, size_t); +int ec_datagram_brw(ec_datagram_t *, uint16_t, size_t); +int ec_datagram_lrd(ec_datagram_t *, uint32_t, size_t); +int ec_datagram_lwr(ec_datagram_t *, uint32_t, size_t); +int ec_datagram_lrw(ec_datagram_t *, uint32_t, size_t); +int ec_datagram_lrd_ext(ec_datagram_t *, uint32_t, size_t, uint8_t *); +int ec_datagram_lwr_ext(ec_datagram_t *, uint32_t, size_t, uint8_t *); +int ec_datagram_lrw_ext(ec_datagram_t *, uint32_t, size_t, uint8_t *); + +void ec_datagram_print_state(const ec_datagram_t *); +void ec_datagram_print_wc_error(const ec_datagram_t *); +void ec_datagram_output_stats(ec_datagram_t *); +const char *ec_datagram_type_string(const ec_datagram_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/datagram_pair.c b/net/ethercat/master/datagram_pair.c new file mode 100644 index 000000000000..656c9e9ebdf4 --- /dev/null +++ b/net/ethercat/master/datagram_pair.c @@ -0,0 +1,201 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT datagram pair methods. +*/ + +/*****************************************************************************/ + +#include <linux/slab.h> + +#include "master.h" +#include "datagram_pair.h" + +/*****************************************************************************/ + +/** Datagram pair constructor. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_datagram_pair_init( + ec_datagram_pair_t *pair, /**< Datagram pair. */ + ec_domain_t *domain, /**< Parent domain. */ + uint32_t logical_offset, /**< Logical offset. */ + uint8_t *data, /**< Data pointer. */ + size_t data_size, /**< Data size. */ + const unsigned int used[] /**< input/output use count. */ + ) +{ + ec_device_index_t dev_idx; + int ret; + + INIT_LIST_HEAD(&pair->list); + pair->domain = domain; + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + ec_datagram_init(&pair->datagrams[dev_idx]); + snprintf(pair->datagrams[dev_idx].name, + EC_DATAGRAM_NAME_SIZE, "domain%u-%u-%s", domain->index, + logical_offset, ec_device_names[dev_idx != 0]); + pair->datagrams[dev_idx].device_index = dev_idx; + } + + pair->expected_working_counter = 0U; + + for (dev_idx = EC_DEVICE_BACKUP; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + /* backup datagrams have their own memory */ + ret = ec_datagram_prealloc(&pair->datagrams[dev_idx], data_size); + if (ret) { + goto out_datagrams; + } + } + +#if EC_MAX_NUM_DEVICES > 1 + if (!(pair->send_buffer = kmalloc(data_size, GFP_KERNEL))) { + EC_MASTER_ERR(domain->master, + "Failed to allocate domain send buffer!\n"); + ret = -ENOMEM; + goto out_datagrams; + } +#endif + + /* The ec_datagram_lxx() calls below can not fail, because either the + * datagram has external memory or it is preallocated. */ + + if (used[EC_DIR_OUTPUT] && used[EC_DIR_INPUT]) { // inputs and outputs + ec_datagram_lrw_ext(&pair->datagrams[EC_DEVICE_MAIN], + logical_offset, data_size, data); + + for (dev_idx = EC_DEVICE_BACKUP; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + ec_datagram_lrw(&pair->datagrams[dev_idx], + logical_offset, data_size); + } + + // If LRW is used, output FMMUs increment the working counter by 2, + // while input FMMUs increment it by 1. + pair->expected_working_counter = + used[EC_DIR_OUTPUT] * 2 + used[EC_DIR_INPUT]; + } else if (used[EC_DIR_OUTPUT]) { // outputs only + ec_datagram_lwr_ext(&pair->datagrams[EC_DEVICE_MAIN], + logical_offset, data_size, data); + for (dev_idx = EC_DEVICE_BACKUP; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + ec_datagram_lwr(&pair->datagrams[dev_idx], + logical_offset, data_size); + } + + pair->expected_working_counter = used[EC_DIR_OUTPUT]; + } else { // inputs only (or nothing) + ec_datagram_lrd_ext(&pair->datagrams[EC_DEVICE_MAIN], + logical_offset, data_size, data); + for (dev_idx = EC_DEVICE_BACKUP; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + ec_datagram_lrd(&pair->datagrams[dev_idx], logical_offset, + data_size); + } + + pair->expected_working_counter = used[EC_DIR_INPUT]; + } + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + ec_datagram_zero(&pair->datagrams[dev_idx]); + } + + return 0; + +out_datagrams: + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + ec_datagram_clear(&pair->datagrams[dev_idx]); + } + + return ret; +} + +/*****************************************************************************/ + +/** Datagram pair destructor. + */ +void ec_datagram_pair_clear( + ec_datagram_pair_t *pair /**< Datagram pair. */ + ) +{ + unsigned int dev_idx; + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(pair->domain->master); + dev_idx++) { + ec_datagram_clear(&pair->datagrams[dev_idx]); + } + +#if EC_MAX_NUM_DEVICES > 1 + if (pair->send_buffer) { + kfree(pair->send_buffer); + } +#endif +} + +/*****************************************************************************/ + +/** Process received data. + * + * \return Working counter sum over all devices. + */ +uint16_t ec_datagram_pair_process( + ec_datagram_pair_t *pair, /**< Datagram pair. */ + uint16_t wc_sum[] /**< Working counter sums. */ + ) +{ + unsigned int dev_idx; + uint16_t pair_wc = 0; + + for (dev_idx = 0; dev_idx < ec_master_num_devices(pair->domain->master); + dev_idx++) { + ec_datagram_t *datagram = &pair->datagrams[dev_idx]; + +#ifdef EC_RT_SYSLOG + ec_datagram_output_stats(datagram); +#endif + + if (datagram->state == EC_DATAGRAM_RECEIVED) { + pair_wc += datagram->working_counter; + wc_sum[dev_idx] += datagram->working_counter; + } + } + + return pair_wc; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/datagram_pair.h b/net/ethercat/master/datagram_pair.h new file mode 100644 index 000000000000..c6da2b318529 --- /dev/null +++ b/net/ethercat/master/datagram_pair.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT datagram pair structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_DATAGRAM_PAIR_H__ +#define __EC_DATAGRAM_PAIR_H__ + +#include <linux/list.h> + +#include "globals.h" +#include "datagram.h" + +/*****************************************************************************/ + +/** Domain datagram pair. + */ +typedef struct { + struct list_head list; /**< List header. */ + ec_domain_t *domain; /**< Parent domain. */ + ec_datagram_t datagrams[EC_MAX_NUM_DEVICES]; /**< Datagrams. */ +#if EC_MAX_NUM_DEVICES > 1 + uint8_t *send_buffer; +#endif + unsigned int expected_working_counter; /**< Expectord working conter. */ +} ec_datagram_pair_t; + +/*****************************************************************************/ + +int ec_datagram_pair_init(ec_datagram_pair_t *, ec_domain_t *, uint32_t, + uint8_t *, size_t, const unsigned int []); +void ec_datagram_pair_clear(ec_datagram_pair_t *); + +uint16_t ec_datagram_pair_process(ec_datagram_pair_t *, uint16_t[]); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/debug.c b/net/ethercat/master/debug.c new file mode 100644 index 000000000000..4246f2d07869 --- /dev/null +++ b/net/ethercat/master/debug.c @@ -0,0 +1,266 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Ethernet interface for debugging purposes. +*/ + +/*****************************************************************************/ + +#include <linux/version.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include "globals.h" +#include "master.h" +#include "debug.h" + +/*****************************************************************************/ + +// net_device functions +int ec_dbgdev_open(struct net_device *); +int ec_dbgdev_stop(struct net_device *); +int ec_dbgdev_tx(struct sk_buff *, struct net_device *); +struct net_device_stats *ec_dbgdev_stats(struct net_device *); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 31) +/** Device operations for debug interfaces. + */ +static const struct net_device_ops ec_dbg_netdev_ops = +{ + .ndo_open = ec_dbgdev_open, + .ndo_stop = ec_dbgdev_stop, + .ndo_start_xmit = ec_dbgdev_tx, + .ndo_get_stats = ec_dbgdev_stats, +}; +#endif + +/*****************************************************************************/ + +/** Debug interface constructor. + * + * Initializes the debug object, creates a net_device and registeres it. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_debug_init( + ec_debug_t *dbg, /**< Debug object. */ + ec_device_t *device, /**< EtherCAT device. */ + const char *name /**< Interface name. */ + ) +{ + dbg->device = device; + dbg->registered = 0; + dbg->opened = 0; + + memset(&dbg->stats, 0, sizeof(struct net_device_stats)); + + if (!(dbg->dev = + alloc_netdev(sizeof(ec_debug_t *), name, NET_NAME_UNKNOWN, ether_setup))) { + EC_MASTER_ERR(device->master, "Unable to allocate net_device" + " for debug object!\n"); + return -ENODEV; + } + + // initialize net_device +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 31) + dbg->dev->netdev_ops = &ec_dbg_netdev_ops; +#else + dbg->dev->open = ec_dbgdev_open; + dbg->dev->stop = ec_dbgdev_stop; + dbg->dev->hard_start_xmit = ec_dbgdev_tx; + dbg->dev->get_stats = ec_dbgdev_stats; +#endif + + // initialize private data + *((ec_debug_t **) netdev_priv(dbg->dev)) = dbg; + + return 0; +} + +/*****************************************************************************/ + +/** Debug interface destructor. + * + * Unregisters the net_device and frees allocated memory. + */ +void ec_debug_clear( + ec_debug_t *dbg /**< debug object */ + ) +{ + ec_debug_unregister(dbg); + free_netdev(dbg->dev); +} + +/*****************************************************************************/ + +/** Register debug interface. + */ +void ec_debug_register( + ec_debug_t *dbg, /**< debug object */ + const struct net_device *net_dev /**< 'Real' Ethernet device. */ + ) +{ + int result; + + ec_debug_unregister(dbg); + + // use the Ethernet address of the physical device for the debug device + memcpy(dbg->dev->dev_addr, net_dev->dev_addr, ETH_ALEN); + + // connect the net_device to the kernel + if ((result = register_netdev(dbg->dev))) { + EC_MASTER_WARN(dbg->device->master, "Unable to register net_device:" + " error %i\n", result); + } else { + dbg->registered = 1; + } +} + +/*****************************************************************************/ + +/** Unregister debug interface. + */ +void ec_debug_unregister( + ec_debug_t *dbg /**< debug object */ + ) +{ + if (dbg->registered) { + dbg->opened = 0; + dbg->registered = 0; + unregister_netdev(dbg->dev); + } +} + +/*****************************************************************************/ + +/** Sends frame data to the interface. + */ +void ec_debug_send( + ec_debug_t *dbg, /**< debug object */ + const uint8_t *data, /**< frame data */ + size_t size /**< size of the frame data */ + ) +{ + struct sk_buff *skb; + + if (!dbg->opened) + return; + + // allocate socket buffer + if (!(skb = dev_alloc_skb(size))) { + dbg->stats.rx_dropped++; + return; + } + + // copy frame contents into socket buffer + memcpy(skb_put(skb, size), data, size); + + // update device statistics + dbg->stats.rx_packets++; + dbg->stats.rx_bytes += size; + + // pass socket buffer to network stack + skb->dev = dbg->dev; + skb->protocol = eth_type_trans(skb, dbg->dev); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); +} + +/****************************************************************************** + * NET_DEVICE functions + *****************************************************************************/ + +/** Opens the virtual network device. + * + * \return Always zero (success). + */ +int ec_dbgdev_open( + struct net_device *dev /**< debug net_device */ + ) +{ + ec_debug_t *dbg = *((ec_debug_t **) netdev_priv(dev)); + dbg->opened = 1; + EC_MASTER_INFO(dbg->device->master, "Debug interface %s opened.\n", + dev->name); + return 0; +} + +/*****************************************************************************/ + +/** Stops the virtual network device. + * + * \return Always zero (success). + */ +int ec_dbgdev_stop( + struct net_device *dev /**< debug net_device */ + ) +{ + ec_debug_t *dbg = *((ec_debug_t **) netdev_priv(dev)); + dbg->opened = 0; + EC_MASTER_INFO(dbg->device->master, "Debug interface %s stopped.\n", + dev->name); + return 0; +} + +/*****************************************************************************/ + +/** Transmits data via the virtual network device. + * + * \return Always zero (success). + */ +int ec_dbgdev_tx( + struct sk_buff *skb, /**< transmit socket buffer */ + struct net_device *dev /**< EoE net_device */ + ) +{ + ec_debug_t *dbg = *((ec_debug_t **) netdev_priv(dev)); + + dev_kfree_skb(skb); + dbg->stats.tx_dropped++; + return 0; +} + +/*****************************************************************************/ + +/** Gets statistics about the virtual network device. + * + * \return Statistics. + */ +struct net_device_stats *ec_dbgdev_stats( + struct net_device *dev /**< debug net_device */ + ) +{ + ec_debug_t *dbg = *((ec_debug_t **) netdev_priv(dev)); + return &dbg->stats; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/debug.h b/net/ethercat/master/debug.h new file mode 100644 index 000000000000..3439f9f03feb --- /dev/null +++ b/net/ethercat/master/debug.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Network interface for debugging purposes. +*/ + +/*****************************************************************************/ + +#ifndef __EC_DEBUG_H__ +#define __EC_DEBUG_H__ + +#include <net/ecdev.h> + +/*****************************************************************************/ + +/** Debugging network interface. + */ +typedef struct +{ + ec_device_t *device; /**< Parent device. */ + struct net_device *dev; /**< net_device for virtual ethernet device */ + struct net_device_stats stats; /**< device statistics */ + uint8_t registered; /**< net_device is opened */ + uint8_t opened; /**< net_device is opened */ +} +ec_debug_t; + +/*****************************************************************************/ + +int ec_debug_init(ec_debug_t *, ec_device_t *, const char *); +void ec_debug_clear(ec_debug_t *); +void ec_debug_register(ec_debug_t *, const struct net_device *); +void ec_debug_unregister(ec_debug_t *); +void ec_debug_send(ec_debug_t *, const uint8_t *, size_t); + +#endif + +/*****************************************************************************/ diff --git a/net/ethercat/master/device.c b/net/ethercat/master/device.c new file mode 100644 index 000000000000..7edb359dcada --- /dev/null +++ b/net/ethercat/master/device.c @@ -0,0 +1,737 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT device methods. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/skbuff.h> +#include <linux/if_ether.h> +#include <linux/netdevice.h> +#include <linux/rtnetlink.h> + +#include "device.h" +#include "master.h" + +#ifdef EC_DEBUG_RING +#define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +/*****************************************************************************/ + +/** Constructor. + * + * \return 0 in case of success, else < 0 + */ +int ec_device_init( + ec_device_t *device, /**< EtherCAT device */ + ec_master_t *master /**< master owning the device */ + ) +{ + int ret; + unsigned int i; + struct ethhdr *eth; +#ifdef EC_DEBUG_IF + char ifname[10]; + char mb = 'x'; +#endif + + device->master = master; + device->dev = NULL; + device->poll = NULL; + device->module = NULL; + device->open = 0; + device->link_state = 0; + for (i = 0; i < EC_TX_RING_SIZE; i++) { + device->tx_skb[i] = NULL; + } + device->tx_ring_index = 0; +#ifdef EC_HAVE_CYCLES + device->cycles_poll = 0; +#endif +#ifdef EC_DEBUG_RING + device->timeval_poll.tv_sec = 0; + device->timeval_poll.tv_usec = 0; +#endif + device->jiffies_poll = 0; + + ec_device_clear_stats(device); + +#ifdef EC_DEBUG_RING + for (i = 0; i < EC_DEBUG_RING_SIZE; i++) { + ec_debug_frame_t *df = &device->debug_frames[i]; + df->dir = TX; + df->t.tv_sec = 0; + df->t.tv_usec = 0; + memset(df->data, 0, EC_MAX_DATA_SIZE); + df->data_size = 0; + } +#endif +#ifdef EC_DEBUG_RING + device->debug_frame_index = 0; + device->debug_frame_count = 0; +#endif + +#ifdef EC_DEBUG_IF + if (device == &master->devices[EC_DEVICE_MAIN]) { + mb = 'm'; + } + else { + mb = 'b'; + } + + sprintf(ifname, "ecdbg%c%u", mb, master->index); + + ret = ec_debug_init(&device->dbg, device, ifname); + if (ret < 0) { + EC_MASTER_ERR(master, "Failed to init debug device!\n"); + goto out_return; + } +#endif + + for (i = 0; i < EC_TX_RING_SIZE; i++) { + if (!(device->tx_skb[i] = dev_alloc_skb(ETH_FRAME_LEN))) { + EC_MASTER_ERR(master, "Error allocating device socket buffer!\n"); + ret = -ENOMEM; + goto out_tx_ring; + } + + // add Ethernet-II-header + skb_reserve(device->tx_skb[i], ETH_HLEN); + eth = (struct ethhdr *) skb_push(device->tx_skb[i], ETH_HLEN); + eth->h_proto = htons(0x88A4); + memset(eth->h_dest, 0xFF, ETH_ALEN); + } + + return 0; + +out_tx_ring: + for (i = 0; i < EC_TX_RING_SIZE; i++) { + if (device->tx_skb[i]) { + dev_kfree_skb(device->tx_skb[i]); + } + } +#ifdef EC_DEBUG_IF + ec_debug_clear(&device->dbg); +out_return: +#endif + return ret; +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_device_clear( + ec_device_t *device /**< EtherCAT device */ + ) +{ + unsigned int i; + + if (device->open) { + ec_device_close(device); + } + for (i = 0; i < EC_TX_RING_SIZE; i++) + dev_kfree_skb(device->tx_skb[i]); +#ifdef EC_DEBUG_IF + ec_debug_clear(&device->dbg); +#endif +} + +/*****************************************************************************/ + +/** Associate with net_device. + */ +void ec_device_attach( + ec_device_t *device, /**< EtherCAT device */ + struct net_device *net_dev, /**< net_device structure */ + ec_pollfunc_t poll, /**< pointer to device's poll function */ + struct module *module /**< the device's module */ + ) +{ + unsigned int i; + struct ethhdr *eth; + + ec_device_detach(device); // resets fields + + device->dev = net_dev; + device->poll = poll; + device->module = module; + + for (i = 0; i < EC_TX_RING_SIZE; i++) { + device->tx_skb[i]->dev = net_dev; + eth = (struct ethhdr *) (device->tx_skb[i]->data); + memcpy(eth->h_source, net_dev->dev_addr, ETH_ALEN); + } + +#ifdef EC_DEBUG_IF + ec_debug_register(&device->dbg, net_dev); +#endif +} + +/*****************************************************************************/ + +/** Disconnect from net_device. + */ +void ec_device_detach( + ec_device_t *device /**< EtherCAT device */ + ) +{ + unsigned int i; + +#ifdef EC_DEBUG_IF + ec_debug_unregister(&device->dbg); +#endif + + device->dev = NULL; + device->poll = NULL; + device->module = NULL; + device->open = 0; + device->link_state = 0; // down + + ec_device_clear_stats(device); + + for (i = 0; i < EC_TX_RING_SIZE; i++) { + device->tx_skb[i]->dev = NULL; + } +} + +/*****************************************************************************/ + +/** Opens the EtherCAT device. + * + * \return 0 in case of success, else < 0 + */ +int ec_device_open( + ec_device_t *device /**< EtherCAT device */ + ) +{ + int ret; + + if (!device->dev) { + EC_MASTER_ERR(device->master, "No net_device to open!\n"); + return -ENODEV; + } + + if (device->open) { + EC_MASTER_WARN(device->master, "Device already opened!\n"); + return 0; + } + + device->link_state = 0; + + ec_device_clear_stats(device); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + rtnl_lock(); + ret = device->dev->netdev_ops->ndo_open(device->dev); + rtnl_unlock(); +#else + ret = device->dev->open(device->dev); +#endif + if (!ret) + device->open = 1; + + return ret; +} + +/*****************************************************************************/ + +/** Stops the EtherCAT device. + * + * \return 0 in case of success, else < 0 + */ +int ec_device_close( + ec_device_t *device /**< EtherCAT device */ + ) +{ + int ret; + + if (!device->dev) { + EC_MASTER_ERR(device->master, "No device to close!\n"); + return -ENODEV; + } + + if (!device->open) { + EC_MASTER_WARN(device->master, "Device already closed!\n"); + return 0; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + ret = device->dev->netdev_ops->ndo_stop(device->dev); +#else + ret = device->dev->stop(device->dev); +#endif + if (!ret) + device->open = 0; + + return ret; +} + +/*****************************************************************************/ + +/** Returns a pointer to the device's transmit memory. + * + * \return pointer to the TX socket buffer + */ +uint8_t *ec_device_tx_data( + ec_device_t *device /**< EtherCAT device */ + ) +{ + /* cycle through socket buffers, because otherwise there is a race + * condition, if multiple frames are sent and the DMA is not scheduled in + * between. */ + device->tx_ring_index++; + device->tx_ring_index %= EC_TX_RING_SIZE; + return device->tx_skb[device->tx_ring_index]->data + ETH_HLEN; +} + +/*****************************************************************************/ + +/** Sends the content of the transmit socket buffer. + * + * Cuts the socket buffer content to the (now known) size, and calls the + * start_xmit() function of the assigned net_device. + */ +void ec_device_send( + ec_device_t *device, /**< EtherCAT device */ + size_t size /**< number of bytes to send */ + ) +{ + struct sk_buff *skb = device->tx_skb[device->tx_ring_index]; + + // set the right length for the data + skb->len = ETH_HLEN + size; + + if (unlikely(device->master->debug_level > 1)) { + EC_MASTER_DBG(device->master, 2, "Sending frame:\n"); + ec_print_data(skb->data, ETH_HLEN + size); + } + + // start sending +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + if (device->dev->netdev_ops->ndo_start_xmit(skb, device->dev) == + NETDEV_TX_OK) +#else + if (device->dev->hard_start_xmit(skb, device->dev) == NETDEV_TX_OK) +#endif + { + device->tx_count++; + device->master->device_stats.tx_count++; + device->tx_bytes += ETH_HLEN + size; + device->master->device_stats.tx_bytes += ETH_HLEN + size; +#ifdef EC_DEBUG_IF + ec_debug_send(&device->dbg, skb->data, ETH_HLEN + size); +#endif +#ifdef EC_DEBUG_RING + ec_device_debug_ring_append( + device, TX, skb->data + ETH_HLEN, size); +#endif + } else { + device->tx_errors++; + } +} + +/*****************************************************************************/ + +/** Clears the frame statistics. + */ +void ec_device_clear_stats( + ec_device_t *device /**< EtherCAT device */ + ) +{ + unsigned int i; + + // zero frame statistics + device->tx_count = 0; + device->last_tx_count = 0; + device->rx_count = 0; + device->last_rx_count = 0; + device->tx_bytes = 0; + device->last_tx_bytes = 0; + device->rx_bytes = 0; + device->last_rx_bytes = 0; + device->tx_errors = 0; + + for (i = 0; i < EC_RATE_COUNT; i++) { + device->tx_frame_rates[i] = 0; + device->rx_frame_rates[i] = 0; + device->tx_byte_rates[i] = 0; + device->rx_byte_rates[i] = 0; + } +} + +/*****************************************************************************/ + +#ifdef EC_DEBUG_RING +/** Appends frame data to the debug ring. + */ +void ec_device_debug_ring_append( + ec_device_t *device, /**< EtherCAT device */ + ec_debug_frame_dir_t dir, /**< direction */ + const void *data, /**< frame data */ + size_t size /**< data size */ + ) +{ + ec_debug_frame_t *df = &device->debug_frames[device->debug_frame_index]; + + df->dir = dir; + if (dir == TX) { + do_gettimeofday(&df->t); + } + else { + df->t = device->timeval_poll; + } + memcpy(df->data, data, size); + df->data_size = size; + + device->debug_frame_index++; + device->debug_frame_index %= EC_DEBUG_RING_SIZE; + if (unlikely(device->debug_frame_count < EC_DEBUG_RING_SIZE)) + device->debug_frame_count++; +} + +/*****************************************************************************/ + +/** Outputs the debug ring. + */ +void ec_device_debug_ring_print( + const ec_device_t *device /**< EtherCAT device */ + ) +{ + int i; + unsigned int ring_index; + const ec_debug_frame_t *df; + struct timeval t0, diff; + + // calculate index of the newest frame in the ring to get its time + ring_index = (device->debug_frame_index + EC_DEBUG_RING_SIZE - 1) + % EC_DEBUG_RING_SIZE; + t0 = device->debug_frames[ring_index].t; + + EC_MASTER_DBG(device->master, 1, "Debug ring %u:\n", ring_index); + + // calculate index of the oldest frame in the ring + ring_index = (device->debug_frame_index + EC_DEBUG_RING_SIZE + - device->debug_frame_count) % EC_DEBUG_RING_SIZE; + + for (i = 0; i < device->debug_frame_count; i++) { + df = &device->debug_frames[ring_index]; + timersub(&t0, &df->t, &diff); + + EC_MASTER_DBG(device->master, 1, "Frame %u, dt=%u.%06u s, %s:\n", + i + 1 - device->debug_frame_count, + (unsigned int) diff.tv_sec, + (unsigned int) diff.tv_usec, + (df->dir == TX) ? "TX" : "RX"); + ec_print_data(df->data, df->data_size); + + ring_index++; + ring_index %= EC_DEBUG_RING_SIZE; + } +} +#endif + +/*****************************************************************************/ + +/** Calls the poll function of the assigned net_device. + * + * The master itself works without using interrupts. Therefore the processing + * of received data and status changes of the network device has to be + * done by the master calling the ISR "manually". + */ +void ec_device_poll( + ec_device_t *device /**< EtherCAT device */ + ) +{ +#ifdef EC_HAVE_CYCLES + device->cycles_poll = get_cycles(); +#endif + device->jiffies_poll = jiffies; +#ifdef EC_DEBUG_RING + do_gettimeofday(&device->timeval_poll); +#endif + device->poll(device->dev); +} + +/*****************************************************************************/ + +/** Update device statistics. + */ +void ec_device_update_stats( + ec_device_t *device /**< EtherCAT device */ + ) +{ + unsigned int i; + + s32 tx_frame_rate = (device->tx_count - device->last_tx_count) * 1000; + s32 rx_frame_rate = (device->rx_count - device->last_rx_count) * 1000; + s32 tx_byte_rate = (device->tx_bytes - device->last_tx_bytes); + s32 rx_byte_rate = (device->rx_bytes - device->last_rx_bytes); + + /* Low-pass filter: + * Y_n = y_(n - 1) + T / tau * (x - y_(n - 1)) | T = 1 + * -> Y_n += (x - y_(n - 1)) / tau + */ + for (i = 0; i < EC_RATE_COUNT; i++) { + s32 n = rate_intervals[i]; + device->tx_frame_rates[i] += + (tx_frame_rate - device->tx_frame_rates[i]) / n; + device->rx_frame_rates[i] += + (rx_frame_rate - device->rx_frame_rates[i]) / n; + device->tx_byte_rates[i] += + (tx_byte_rate - device->tx_byte_rates[i]) / n; + device->rx_byte_rates[i] += + (rx_byte_rate - device->rx_byte_rates[i]) / n; + } + + device->last_tx_count = device->tx_count; + device->last_rx_count = device->rx_count; + device->last_tx_bytes = device->tx_bytes; + device->last_rx_bytes = device->rx_bytes; +} + +/****************************************************************************** + * Device interface + *****************************************************************************/ + +/** Withdraws an EtherCAT device from the master. + * + * The device is disconnected from the master and all device ressources + * are freed. + * + * \attention Before calling this function, the ecdev_stop() function has + * to be called, to be sure that the master does not use the device + * any more. + * \ingroup DeviceInterface + */ +void ecdev_withdraw(ec_device_t *device /**< EtherCAT device */) +{ + ec_master_t *master = device->master; + char dev_str[20], mac_str[20]; + + ec_mac_print(device->dev->dev_addr, mac_str); + + if (device == &master->devices[EC_DEVICE_MAIN]) { + sprintf(dev_str, "main"); + } else if (device == &master->devices[EC_DEVICE_BACKUP]) { + sprintf(dev_str, "backup"); + } else { + EC_MASTER_WARN(master, "%s() called with unknown device %s!\n", + __func__, mac_str); + sprintf(dev_str, "UNKNOWN"); + } + + EC_MASTER_INFO(master, "Releasing %s device %s.\n", dev_str, mac_str); + + down(&master->device_sem); + ec_device_detach(device); + up(&master->device_sem); +} + +/*****************************************************************************/ + +/** Opens the network device and makes the master enter IDLE phase. + * + * \return 0 on success, else < 0 + * \ingroup DeviceInterface + */ +int ecdev_open(ec_device_t *device /**< EtherCAT device */) +{ + int ret; + ec_master_t *master = device->master; + unsigned int all_open = 1, dev_idx; + + ret = ec_device_open(device); + if (ret) { + EC_MASTER_ERR(master, "Failed to open device!\n"); + return ret; + } + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(device->master); dev_idx++) { + if (!master->devices[dev_idx].open) { + all_open = 0; + break; + } + } + + if (all_open) { + ret = ec_master_enter_idle_phase(device->master); + if (ret) { + EC_MASTER_ERR(device->master, "Failed to enter IDLE phase!\n"); + return ret; + } + } + + return 0; +} + +/*****************************************************************************/ + +/** Makes the master leave IDLE phase and closes the network device. + * + * \return 0 on success, else < 0 + * \ingroup DeviceInterface + */ +void ecdev_close(ec_device_t *device /**< EtherCAT device */) +{ + ec_master_t *master = device->master; + + if (master->phase == EC_IDLE) { + ec_master_leave_idle_phase(master); + } + + if (ec_device_close(device)) { + EC_MASTER_WARN(master, "Failed to close device!\n"); + } +} + +/*****************************************************************************/ + +/** Accepts a received frame. + * + * Forwards the received data to the master. The master will analyze the frame + * and dispatch the received commands to the sending instances. + * + * The data have to begin with the Ethernet header (target MAC address). + * + * \ingroup DeviceInterface + */ +void ecdev_receive( + ec_device_t *device, /**< EtherCAT device */ + const void *data, /**< pointer to received data */ + size_t size /**< number of bytes received */ + ) +{ + const void *ec_data = data + ETH_HLEN; + size_t ec_size = size - ETH_HLEN; + + if (unlikely(!data)) { + EC_MASTER_WARN(device->master, "%s() called with NULL data.\n", + __func__); + return; + } + + device->rx_count++; + device->master->device_stats.rx_count++; + device->rx_bytes += size; + device->master->device_stats.rx_bytes += size; + + if (unlikely(device->master->debug_level > 1)) { + EC_MASTER_DBG(device->master, 2, "Received frame:\n"); + ec_print_data(data, size); + } + //u8 *pointer = ec_data; + //pr_info("ec data %02x", pointer[0]); + //pr_info("ec data %02x %02x %02x %02x", pointer[0], pointer[1], pointer[2], pointer[3]); + +#ifdef EC_DEBUG_IF + ec_debug_send(&device->dbg, data, size); +#endif +#ifdef EC_DEBUG_RING + ec_device_debug_ring_append(device, RX, ec_data, ec_size); +#endif + + ec_master_receive_datagrams(device->master, device, ec_data, ec_size); +} + +/*****************************************************************************/ + +/** Sets a new link state. + * + * If the device notifies the master about the link being down, the master + * will not try to send frames using this device. + * + * \ingroup DeviceInterface + */ +void ecdev_set_link( + ec_device_t *device, /**< EtherCAT device */ + uint8_t state /**< new link state */ + ) +{ + if (unlikely(!device)) { + EC_WARN("ecdev_set_link() called with null device!\n"); + return; + } + + if (likely(state != device->link_state)) { + device->link_state = state; + EC_MASTER_INFO(device->master, + "Link state of %s changed to %s.\n", + device->dev->name, (state ? "UP" : "DOWN")); + } +} + +/*****************************************************************************/ + +/** Reads the link state. + * + * \ingroup DeviceInterface + * + * \return Link state. + */ +uint8_t ecdev_get_link( + const ec_device_t *device /**< EtherCAT device */ + ) +{ + if (unlikely(!device)) { + EC_WARN("ecdev_get_link() called with null device!\n"); + return 0; + } + + return device->link_state; +} + +/*****************************************************************************/ + +/** \cond */ + +EXPORT_SYMBOL(ecdev_withdraw); +EXPORT_SYMBOL(ecdev_open); +EXPORT_SYMBOL(ecdev_close); +EXPORT_SYMBOL(ecdev_receive); +EXPORT_SYMBOL(ecdev_get_link); +EXPORT_SYMBOL(ecdev_set_link); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/master/device.h b/net/ethercat/master/device.h new file mode 100644 index 000000000000..2c26e2b2656c --- /dev/null +++ b/net/ethercat/master/device.h @@ -0,0 +1,158 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT device structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_DEVICE_H__ +#define __EC_DEVICE_H__ + +#include <linux/interrupt.h> + +#include <net/ecdev.h> +#include "globals.h" + +/** + * Size of the transmit ring. + * This memory ring is used to transmit frames. It is necessary to use + * different memory regions, because otherwise the network device DMA could + * send the same data twice, if it is called twice. + */ +#define EC_TX_RING_SIZE 2 + +#ifdef EC_DEBUG_IF +#include "debug.h" +#endif + +#ifdef EC_DEBUG_RING +#define EC_DEBUG_RING_SIZE 10 + +typedef enum { + TX, RX +} ec_debug_frame_dir_t; + +typedef struct { + ec_debug_frame_dir_t dir; + struct timeval t; + uint8_t data[EC_MAX_DATA_SIZE]; + unsigned int data_size; +} ec_debug_frame_t; + +#endif + +/*****************************************************************************/ + +/** + EtherCAT device. + An EtherCAT device is a network interface card, that is owned by an + EtherCAT master to send and receive EtherCAT frames with. +*/ + +struct ec_device +{ + ec_master_t *master; /**< EtherCAT master */ + struct net_device *dev; /**< pointer to the assigned net_device */ + ec_pollfunc_t poll; /**< pointer to the device's poll function */ + struct module *module; /**< pointer to the device's owning module */ + uint8_t open; /**< true, if the net_device has been opened */ + uint8_t link_state; /**< device link state */ + struct sk_buff *tx_skb[EC_TX_RING_SIZE]; /**< transmit skb ring */ + unsigned int tx_ring_index; /**< last ring entry used to transmit */ +#ifdef EC_HAVE_CYCLES + cycles_t cycles_poll; /**< cycles of last poll */ +#endif +#ifdef EC_DEBUG_RING + struct timeval timeval_poll; +#endif + unsigned long jiffies_poll; /**< jiffies of last poll */ + + // Frame statistics + u64 tx_count; /**< Number of frames sent. */ + u64 last_tx_count; /**< Number of frames sent of last statistics cycle. */ + u64 rx_count; /**< Number of frames received. */ + u64 last_rx_count; /**< Number of frames received of last statistics + cycle. */ + u64 tx_bytes; /**< Number of bytes sent. */ + u64 last_tx_bytes; /**< Number of bytes sent of last statistics cycle. */ + u64 rx_bytes; /**< Number of bytes received. */ + u64 last_rx_bytes; /**< Number of bytes received of last statistics cycle. + */ + u64 tx_errors; /**< Number of transmit errors. */ + s32 tx_frame_rates[EC_RATE_COUNT]; /**< Transmit rates in frames/s for + different statistics cycle periods. + */ + s32 rx_frame_rates[EC_RATE_COUNT]; /**< Receive rates in frames/s for + different statistics cycle periods. + */ + s32 tx_byte_rates[EC_RATE_COUNT]; /**< Transmit rates in byte/s for + different statistics cycle periods. */ + s32 rx_byte_rates[EC_RATE_COUNT]; /**< Receive rates in byte/s for + different statistics cycle periods. */ + +#ifdef EC_DEBUG_IF + ec_debug_t dbg; /**< debug device */ +#endif +#ifdef EC_DEBUG_RING + ec_debug_frame_t debug_frames[EC_DEBUG_RING_SIZE]; + unsigned int debug_frame_index; + unsigned int debug_frame_count; +#endif +}; + +/*****************************************************************************/ + +int ec_device_init(ec_device_t *, ec_master_t *); +void ec_device_clear(ec_device_t *); + +void ec_device_attach(ec_device_t *, struct net_device *, ec_pollfunc_t, + struct module *); +void ec_device_detach(ec_device_t *); + +int ec_device_open(ec_device_t *); +int ec_device_close(ec_device_t *); + +void ec_device_poll(ec_device_t *); +uint8_t *ec_device_tx_data(ec_device_t *); +void ec_device_send(ec_device_t *, size_t); +void ec_device_clear_stats(ec_device_t *); +void ec_device_update_stats(ec_device_t *); + +#ifdef EC_DEBUG_RING +void ec_device_debug_ring_append(ec_device_t *, ec_debug_frame_dir_t, + const void *, size_t); +void ec_device_debug_ring_print(const ec_device_t *); +#endif + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/domain.c b/net/ethercat/master/domain.c new file mode 100644 index 000000000000..a1ec9b8cf370 --- /dev/null +++ b/net/ethercat/master/domain.c @@ -0,0 +1,717 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT domain methods. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> + +#include "globals.h" +#include "master.h" +#include "slave_config.h" + +#include "domain.h" +#include "datagram_pair.h" + +/** Extra debug output for redundancy functions. + */ +#define DEBUG_REDUNDANCY 0 + +/*****************************************************************************/ + +void ec_domain_clear_data(ec_domain_t *); + +/*****************************************************************************/ + +/** Domain constructor. + */ +void ec_domain_init( + ec_domain_t *domain, /**< EtherCAT domain. */ + ec_master_t *master, /**< Parent master. */ + unsigned int index /**< Index. */ + ) +{ + unsigned int dev_idx; + + domain->master = master; + domain->index = index; + INIT_LIST_HEAD(&domain->fmmu_configs); + domain->data_size = 0; + domain->data = NULL; + domain->data_origin = EC_ORIG_INTERNAL; + domain->logical_base_address = 0x00000000; + INIT_LIST_HEAD(&domain->datagram_pairs); + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + domain->working_counter[dev_idx] = 0x0000; + } + domain->expected_working_counter = 0x0000; + domain->working_counter_changes = 0; + domain->redundancy_active = 0; + domain->notify_jiffies = 0; +} + +/*****************************************************************************/ + +/** Domain destructor. + */ +void ec_domain_clear(ec_domain_t *domain /**< EtherCAT domain */) +{ + ec_datagram_pair_t *datagram_pair, *next_pair; + + // dequeue and free datagrams + list_for_each_entry_safe(datagram_pair, next_pair, + &domain->datagram_pairs, list) { + ec_datagram_pair_clear(datagram_pair); + kfree(datagram_pair); + } + + ec_domain_clear_data(domain); +} + +/*****************************************************************************/ + +/** Frees internally allocated memory. + */ +void ec_domain_clear_data( + ec_domain_t *domain /**< EtherCAT domain. */ + ) +{ + if (domain->data_origin == EC_ORIG_INTERNAL && domain->data) { + kfree(domain->data); + } + + domain->data = NULL; + domain->data_origin = EC_ORIG_INTERNAL; +} + +/*****************************************************************************/ + +/** Adds an FMMU configuration to the domain. + */ +void ec_domain_add_fmmu_config( + ec_domain_t *domain, /**< EtherCAT domain. */ + ec_fmmu_config_t *fmmu /**< FMMU configuration. */ + ) +{ + fmmu->domain = domain; + + domain->data_size += fmmu->data_size; + list_add_tail(&fmmu->list, &domain->fmmu_configs); + + EC_MASTER_DBG(domain->master, 1, "Domain %u:" + " Added %u bytes, total %zu.\n", + domain->index, fmmu->data_size, domain->data_size); +} + +/*****************************************************************************/ + +/** Allocates a domain datagram pair and appends it to the list. + * + * The datagrams' types and expected working counters are determined by the + * number of input and output fmmus that share the datagrams. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_domain_add_datagram_pair( + ec_domain_t *domain, /**< EtherCAT domain. */ + uint32_t logical_offset, /**< Logical offset. */ + size_t data_size, /**< Size of the data. */ + uint8_t *data, /**< Process data. */ + const unsigned int used[] /**< Slave config counter for in/out. */ + ) +{ + ec_datagram_pair_t *datagram_pair; + int ret; + + if (!(datagram_pair = kmalloc(sizeof(ec_datagram_pair_t), GFP_KERNEL))) { + EC_MASTER_ERR(domain->master, + "Failed to allocate domain datagram pair!\n"); + return -ENOMEM; + } + + ret = ec_datagram_pair_init(datagram_pair, domain, logical_offset, data, + data_size, used); + if (ret) { + kfree(datagram_pair); + return ret; + } + + domain->expected_working_counter += + datagram_pair->expected_working_counter; + + EC_MASTER_DBG(domain->master, 1, + "Adding datagram pair with expected WC %u.\n", + datagram_pair->expected_working_counter); + + + list_add_tail(&datagram_pair->list, &domain->datagram_pairs); + return 0; +} + +/*****************************************************************************/ + +/** Domain finish helper function. + * + * Detects, if a slave configuration has already been taken into account for + * a datagram's expected working counter calculation. + * + * Walks through the list of all FMMU configurations for the current datagram + * and ends before the current datagram. + * + * \return Non-zero if slave connfig was already counted. + */ +int shall_count( + const ec_fmmu_config_t *cur_fmmu, /**< Current FMMU with direction to + search for. */ + const ec_fmmu_config_t *first_fmmu /**< Datagram's first FMMU. */ + ) +{ + for (; first_fmmu != cur_fmmu; + first_fmmu = list_entry(first_fmmu->list.next, + ec_fmmu_config_t, list)) { + + if (first_fmmu->sc == cur_fmmu->sc + && first_fmmu->dir == cur_fmmu->dir) { + return 0; // was already counted + } + } + + return 1; +} + +/*****************************************************************************/ + +/** Finishes a domain. + * + * This allocates the necessary datagrams and writes the correct logical + * addresses to every configured FMMU. + * + * \todo Check for FMMUs that do not fit into any datagram. + * + * \retval 0 Success + * \retval <0 Error code. + */ +int ec_domain_finish( + ec_domain_t *domain, /**< EtherCAT domain. */ + uint32_t base_address /**< Logical base address. */ + ) +{ + uint32_t datagram_offset; + size_t datagram_size; + unsigned int datagram_count; + unsigned int datagram_used[EC_DIR_COUNT]; + ec_fmmu_config_t *fmmu; + const ec_fmmu_config_t *datagram_first_fmmu = NULL; + const ec_datagram_pair_t *datagram_pair; + int ret; + + domain->logical_base_address = base_address; + + if (domain->data_size && domain->data_origin == EC_ORIG_INTERNAL) { + if (!(domain->data = + (uint8_t *) kmalloc(domain->data_size, GFP_KERNEL))) { + EC_MASTER_ERR(domain->master, "Failed to allocate %zu bytes" + " internal memory for domain %u!\n", + domain->data_size, domain->index); + return -ENOMEM; + } + } + + // Cycle through all domain FMMUs and + // - correct the logical base addresses + // - set up the datagrams to carry the process data + // - calculate the datagrams' expected working counters + datagram_offset = 0; + datagram_size = 0; + datagram_count = 0; + datagram_used[EC_DIR_OUTPUT] = 0; + datagram_used[EC_DIR_INPUT] = 0; + + if (!list_empty(&domain->fmmu_configs)) { + datagram_first_fmmu = + list_entry(domain->fmmu_configs.next, ec_fmmu_config_t, list); + } + + list_for_each_entry(fmmu, &domain->fmmu_configs, list) { + + // Correct logical FMMU address + fmmu->logical_start_address += base_address; + + // If the current FMMU's data do not fit in the current datagram, + // allocate a new one. + if (datagram_size + fmmu->data_size > EC_MAX_DATA_SIZE) { + ret = ec_domain_add_datagram_pair(domain, + domain->logical_base_address + datagram_offset, + datagram_size, domain->data + datagram_offset, + datagram_used); + if (ret < 0) + return ret; + + datagram_offset += datagram_size; + datagram_size = 0; + datagram_count++; + datagram_used[EC_DIR_OUTPUT] = 0; + datagram_used[EC_DIR_INPUT] = 0; + datagram_first_fmmu = fmmu; + } + + // Increment Input/Output counter to determine datagram types + // and calculate expected working counters + if (shall_count(fmmu, datagram_first_fmmu)) { + datagram_used[fmmu->dir]++; + } + + datagram_size += fmmu->data_size; + } + + /* Allocate last datagram pair, if data are left (this is also the case if + * the process data fit into a single datagram) */ + if (datagram_size) { + ret = ec_domain_add_datagram_pair(domain, + domain->logical_base_address + datagram_offset, + datagram_size, domain->data + datagram_offset, + datagram_used); + if (ret < 0) + return ret; + datagram_count++; + } + + EC_MASTER_INFO(domain->master, "Domain%u: Logical address 0x%08x," + " %zu byte, expected working counter %u.\n", domain->index, + domain->logical_base_address, domain->data_size, + domain->expected_working_counter); + + list_for_each_entry(datagram_pair, &domain->datagram_pairs, list) { + const ec_datagram_t *datagram = + &datagram_pair->datagrams[EC_DEVICE_MAIN]; + EC_MASTER_INFO(domain->master, " Datagram %s: Logical offset 0x%08x," + " %zu byte, type %s.\n", datagram->name, + EC_READ_U32(datagram->address), datagram->data_size, + ec_datagram_type_string(datagram)); + } + + return 0; +} + +/*****************************************************************************/ + +/** Get the number of FMMU configurations of the domain. + */ +unsigned int ec_domain_fmmu_count(const ec_domain_t *domain) +{ + const ec_fmmu_config_t *fmmu; + unsigned int num = 0; + + list_for_each_entry(fmmu, &domain->fmmu_configs, list) { + num++; + } + + return num; +} + +/*****************************************************************************/ + +/** Get a certain FMMU configuration via its position in the list. + * + * \return FMMU at position \a pos, or NULL. + */ +const ec_fmmu_config_t *ec_domain_find_fmmu( + const ec_domain_t *domain, /**< EtherCAT domain. */ + unsigned int pos /**< List position. */ + ) +{ + const ec_fmmu_config_t *fmmu; + + list_for_each_entry(fmmu, &domain->fmmu_configs, list) { + if (pos--) + continue; + return fmmu; + } + + return NULL; +} + +/*****************************************************************************/ + +#if EC_MAX_NUM_DEVICES > 1 + +/** Process received data. + */ +int data_changed( + uint8_t *send_buffer, + const ec_datagram_t *datagram, + size_t offset, + size_t size + ) +{ + uint8_t *sent = send_buffer + offset; + uint8_t *recv = datagram->data + offset; + size_t i; + + for (i = 0; i < size; i++) { + if (recv[i] != sent[i]) { + return 1; + } + } + + return 0; +} + +#endif + +/****************************************************************************** + * Application interface + *****************************************************************************/ + +int ecrt_domain_reg_pdo_entry_list(ec_domain_t *domain, + const ec_pdo_entry_reg_t *regs) +{ + const ec_pdo_entry_reg_t *reg; + ec_slave_config_t *sc; + int ret; + + EC_MASTER_DBG(domain->master, 1, "ecrt_domain_reg_pdo_entry_list(" + "domain = 0x%p, regs = 0x%p)\n", domain, regs); + + for (reg = regs; reg->index; reg++) { + sc = ecrt_master_slave_config_err(domain->master, reg->alias, + reg->position, reg->vendor_id, reg->product_code); + if (IS_ERR(sc)) + return PTR_ERR(sc); + + ret = ecrt_slave_config_reg_pdo_entry(sc, reg->index, + reg->subindex, domain, reg->bit_position); + if (ret < 0) + return ret; + + *reg->offset = ret; + } + + return 0; +} + +/*****************************************************************************/ + +size_t ecrt_domain_size(const ec_domain_t *domain) +{ + return domain->data_size; +} + +/*****************************************************************************/ + +void ecrt_domain_external_memory(ec_domain_t *domain, uint8_t *mem) +{ + EC_MASTER_DBG(domain->master, 1, "ecrt_domain_external_memory(" + "domain = 0x%p, mem = 0x%p)\n", domain, mem); + + down(&domain->master->master_sem); + + ec_domain_clear_data(domain); + + domain->data = mem; + domain->data_origin = EC_ORIG_EXTERNAL; + + up(&domain->master->master_sem); +} + +/*****************************************************************************/ + +uint8_t *ecrt_domain_data(ec_domain_t *domain) +{ + return domain->data; +} + +/*****************************************************************************/ + +void ecrt_domain_process(ec_domain_t *domain) +{ + uint16_t wc_sum[EC_MAX_NUM_DEVICES] = {}, wc_total; + ec_datagram_pair_t *pair; +#if EC_MAX_NUM_DEVICES > 1 + uint16_t datagram_pair_wc, redundant_wc; + unsigned int datagram_offset; + ec_fmmu_config_t *fmmu = list_first_entry(&domain->fmmu_configs, + ec_fmmu_config_t, list); + unsigned int redundancy; +#endif + unsigned int dev_idx; +#ifdef EC_RT_SYSLOG + unsigned int wc_change; +#endif + +#if DEBUG_REDUNDANCY + EC_MASTER_DBG(domain->master, 1, "domain %u process\n", domain->index); +#endif + + list_for_each_entry(pair, &domain->datagram_pairs, list) { +#if EC_MAX_NUM_DEVICES > 1 + datagram_pair_wc = ec_datagram_pair_process(pair, wc_sum); +#else + ec_datagram_pair_process(pair, wc_sum); +#endif + +#if EC_MAX_NUM_DEVICES > 1 + if (ec_master_num_devices(domain->master) > 1) { + ec_datagram_t *main_datagram = &pair->datagrams[EC_DEVICE_MAIN]; + uint32_t logical_datagram_address = + EC_READ_U32(main_datagram->address); + size_t datagram_size = main_datagram->data_size; + +#if DEBUG_REDUNDANCY + EC_MASTER_DBG(domain->master, 1, "dgram %s log=%u\n", + main_datagram->name, logical_datagram_address); +#endif + + /* Redundancy: Go through FMMU configs to detect data changes. */ + list_for_each_entry_from(fmmu, &domain->fmmu_configs, list) { + ec_datagram_t *backup_datagram = + &pair->datagrams[EC_DEVICE_BACKUP]; + + if (fmmu->dir != EC_DIR_INPUT) { + continue; + } + + if (fmmu->logical_start_address >= + logical_datagram_address + datagram_size) { + // fmmu data contained in next datagram pair + break; + } + + datagram_offset = + fmmu->logical_start_address - logical_datagram_address; + +#if DEBUG_REDUNDANCY + EC_MASTER_DBG(domain->master, 1, + "input fmmu log=%u size=%u offset=%u\n", + fmmu->logical_start_address, fmmu->data_size, + datagram_offset); + if (domain->master->debug_level > 0) { + ec_print_data(pair->send_buffer + datagram_offset, + fmmu->data_size); + ec_print_data(main_datagram->data + datagram_offset, + fmmu->data_size); + ec_print_data(backup_datagram->data + datagram_offset, + fmmu->data_size); + } +#endif + + if (data_changed(pair->send_buffer, main_datagram, + datagram_offset, fmmu->data_size)) { + /* data changed on main link: no copying necessary. */ +#if DEBUG_REDUNDANCY + EC_MASTER_DBG(domain->master, 1, "main changed\n"); +#endif + } else if (data_changed(pair->send_buffer, backup_datagram, + datagram_offset, fmmu->data_size)) { + /* data changed on backup link: copy to main memory. */ +#if DEBUG_REDUNDANCY + EC_MASTER_DBG(domain->master, 1, "backup changed\n"); +#endif + memcpy(main_datagram->data + datagram_offset, + backup_datagram->data + datagram_offset, + fmmu->data_size); + } else if (datagram_pair_wc == + pair->expected_working_counter) { + /* no change, but WC complete: use main data. */ +#if DEBUG_REDUNDANCY + EC_MASTER_DBG(domain->master, 1, + "no change but complete\n"); +#endif + } else { + /* no change and WC incomplete: mark WC as zero to avoid + * data.dependent WC flickering. */ + datagram_pair_wc = 0; +#if DEBUG_REDUNDANCY + EC_MASTER_DBG(domain->master, 1, + "no change and incomplete\n"); +#endif + } + } + } +#endif // EC_MAX_NUM_DEVICES > 1 + } + +#if EC_MAX_NUM_DEVICES > 1 + redundant_wc = 0; + for (dev_idx = EC_DEVICE_BACKUP; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + redundant_wc += wc_sum[dev_idx]; + } + + redundancy = redundant_wc > 0; + if (redundancy != domain->redundancy_active) { +#ifdef EC_RT_SYSLOG + if (redundancy) { + EC_MASTER_WARN(domain->master, + "Domain %u: Redundant link in use!\n", + domain->index); + } else { + EC_MASTER_INFO(domain->master, + "Domain %u: Redundant link unused again.\n", + domain->index); + } +#endif + domain->redundancy_active = redundancy; + } +#else + domain->redundancy_active = 0; +#endif + +#ifdef EC_RT_SYSLOG + wc_change = 0; +#endif + wc_total = 0; + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + if (wc_sum[dev_idx] != domain->working_counter[dev_idx]) { +#ifdef EC_RT_SYSLOG + wc_change = 1; +#endif + domain->working_counter[dev_idx] = wc_sum[dev_idx]; + } + wc_total += wc_sum[dev_idx]; + } + +#ifdef EC_RT_SYSLOG + if (wc_change) { + domain->working_counter_changes++; + } + + if (domain->working_counter_changes && + jiffies - domain->notify_jiffies > HZ) { + domain->notify_jiffies = jiffies; + if (domain->working_counter_changes == 1) { + EC_MASTER_INFO(domain->master, "Domain %u: Working counter" + " changed to %u/%u", domain->index, + wc_total, domain->expected_working_counter); + } else { + EC_MASTER_INFO(domain->master, "Domain %u: %u working counter" + " changes - now %u/%u", domain->index, + domain->working_counter_changes, + wc_total, domain->expected_working_counter); + } +#if EC_MAX_NUM_DEVICES > 1 + if (ec_master_num_devices(domain->master) > 1) { + printk(" ("); + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(domain->master); + dev_idx++) { + printk("%u", domain->working_counter[dev_idx]); + if (dev_idx + 1 < ec_master_num_devices(domain->master)) { + printk("+"); + } + } + printk(")"); + } +#endif + printk(".\n"); + + domain->working_counter_changes = 0; + } +#endif +} + +/*****************************************************************************/ + +void ecrt_domain_queue(ec_domain_t *domain) +{ + ec_datagram_pair_t *datagram_pair; + ec_device_index_t dev_idx; + + list_for_each_entry(datagram_pair, &domain->datagram_pairs, list) { + +#if EC_MAX_NUM_DEVICES > 1 + /* copy main data to send buffer */ + memcpy(datagram_pair->send_buffer, + datagram_pair->datagrams[EC_DEVICE_MAIN].data, + datagram_pair->datagrams[EC_DEVICE_MAIN].data_size); +#endif + ec_master_queue_datagram(domain->master, + &datagram_pair->datagrams[EC_DEVICE_MAIN]); + + /* copy main data to backup datagram */ + for (dev_idx = EC_DEVICE_BACKUP; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + memcpy(datagram_pair->datagrams[dev_idx].data, + datagram_pair->datagrams[EC_DEVICE_MAIN].data, + datagram_pair->datagrams[EC_DEVICE_MAIN].data_size); + ec_master_queue_datagram(domain->master, + &datagram_pair->datagrams[dev_idx]); + } + } +} + +/*****************************************************************************/ + +void ecrt_domain_state(const ec_domain_t *domain, ec_domain_state_t *state) +{ + unsigned int dev_idx; + uint16_t wc = 0; + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + wc += domain->working_counter[dev_idx]; + } + + state->working_counter = wc; + + if (wc) { + if (wc == domain->expected_working_counter) { + state->wc_state = EC_WC_COMPLETE; + } else { + state->wc_state = EC_WC_INCOMPLETE; + } + } else { + state->wc_state = EC_WC_ZERO; + } + + state->redundancy_active = domain->redundancy_active; +} + +/*****************************************************************************/ + +/** \cond */ + +EXPORT_SYMBOL(ecrt_domain_reg_pdo_entry_list); +EXPORT_SYMBOL(ecrt_domain_size); +EXPORT_SYMBOL(ecrt_domain_external_memory); +EXPORT_SYMBOL(ecrt_domain_data); +EXPORT_SYMBOL(ecrt_domain_process); +EXPORT_SYMBOL(ecrt_domain_queue); +EXPORT_SYMBOL(ecrt_domain_state); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/master/domain.h b/net/ethercat/master/domain.h new file mode 100644 index 000000000000..1d15aaf2bb43 --- /dev/null +++ b/net/ethercat/master/domain.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT domain structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_DOMAIN_H__ +#define __EC_DOMAIN_H__ + +#include <linux/list.h> + +#include "globals.h" +#include "datagram.h" +#include "master.h" +#include "fmmu_config.h" + +/*****************************************************************************/ + +/** EtherCAT domain. + * + * Handles the process data and the therefore needed datagrams of a certain + * group of slaves. + */ +struct ec_domain +{ + struct list_head list; /**< List item. */ + ec_master_t *master; /**< EtherCAT master owning the domain. */ + unsigned int index; /**< Index (just a number). */ + + struct list_head fmmu_configs; /**< FMMU configurations contained. */ + size_t data_size; /**< Size of the process data. */ + uint8_t *data; /**< Memory for the process data. */ + ec_origin_t data_origin; /**< Origin of the \a data memory. */ + uint32_t logical_base_address; /**< Logical offset address of the + process data. */ + struct list_head datagram_pairs; /**< Datagrams pairs (main/backup) for + process data exchange. */ + uint16_t working_counter[EC_MAX_NUM_DEVICES]; /**< Last working counter + values. */ + uint16_t expected_working_counter; /**< Expected working counter. */ + unsigned int working_counter_changes; /**< Working counter changes + since last notification. */ + unsigned int redundancy_active; /**< Non-zero, if redundancy is in use. */ + unsigned long notify_jiffies; /**< Time of last notification. */ +}; + +/*****************************************************************************/ + +void ec_domain_init(ec_domain_t *, ec_master_t *, unsigned int); +void ec_domain_clear(ec_domain_t *); + +void ec_domain_add_fmmu_config(ec_domain_t *, ec_fmmu_config_t *); +int ec_domain_finish(ec_domain_t *, uint32_t); + +unsigned int ec_domain_fmmu_count(const ec_domain_t *); +const ec_fmmu_config_t *ec_domain_find_fmmu(const ec_domain_t *, unsigned int); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/doxygen.c b/net/ethercat/master/doxygen.c new file mode 100644 index 000000000000..06832de90bd1 --- /dev/null +++ b/net/ethercat/master/doxygen.c @@ -0,0 +1,89 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +// This file only contains the doxygen mainpage. + +/** \file + * Doxygen mainpage only. + */ + +/*****************************************************************************/ + +/** + \mainpage + + \section sec_general General information + + This HTML contains the complete code documentation. + + The API documentations are in the <a href="modules.html">modules + section</a>. + + For information how to build and install, see the INSTALL file in the source + root. + + \section sec_contact Contact + + \verbatim + Florian Pose <fp@igh-essen.com> + Ingenieurgemeinschaft IgH + Heinz-Baecker-Str. 34 + D-45356 Essen + http://igh-essen.com + \endverbatim + + \section sec_license License + + \verbatim + Copyright (C) 2006-2013 Florian Pose, Ingenieurgemeinschaft IgH + + This file is part of the IgH EtherCAT Master. + + The IgH EtherCAT Master is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License version 2, as + published by the Free Software Foundation. + + The IgH EtherCAT Master is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + Public License for more details. + + You should have received a copy of the GNU General Public License along + with the IgH EtherCAT Master; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + --- + + The license mentioned above concerns the source code only. Using the + EtherCAT technology and brand is only permitted in compliance with the + industrial property and similar rights of Beckhoff Automation GmbH. + \endverbatim +*/ + +/*****************************************************************************/ diff --git a/net/ethercat/master/ethernet.c b/net/ethercat/master/ethernet.c new file mode 100644 index 000000000000..30de578ba926 --- /dev/null +++ b/net/ethercat/master/ethernet.c @@ -0,0 +1,874 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Ethernet over EtherCAT (EoE). +*/ + +/*****************************************************************************/ + +#include <linux/version.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include "globals.h" +#include "master.h" +#include "slave.h" +#include "mailbox.h" +#include "ethernet.h" + +/*****************************************************************************/ + +/** Defines the debug level of EoE processing. + * + * 0 = No debug messages. + * 1 = Output warnings. + * 2 = Output actions. + * 3 = Output actions and frame data. + */ +#define EOE_DEBUG_LEVEL 1 + +/** Size of the EoE tx queue. + */ +#define EC_EOE_TX_QUEUE_SIZE 100 + +/** Number of tries. + */ +#define EC_EOE_TRIES 100 + +/*****************************************************************************/ + +void ec_eoe_flush(ec_eoe_t *); + +// state functions +void ec_eoe_state_rx_start(ec_eoe_t *); +void ec_eoe_state_rx_check(ec_eoe_t *); +void ec_eoe_state_rx_fetch(ec_eoe_t *); +void ec_eoe_state_tx_start(ec_eoe_t *); +void ec_eoe_state_tx_sent(ec_eoe_t *); + +// net_device functions +int ec_eoedev_open(struct net_device *); +int ec_eoedev_stop(struct net_device *); +int ec_eoedev_tx(struct sk_buff *, struct net_device *); +struct net_device_stats *ec_eoedev_stats(struct net_device *); + +/*****************************************************************************/ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) +/** Device operations for EoE interfaces. + */ +static const struct net_device_ops ec_eoedev_ops = { + .ndo_open = ec_eoedev_open, + .ndo_stop = ec_eoedev_stop, + .ndo_start_xmit = ec_eoedev_tx, + .ndo_get_stats = ec_eoedev_stats, +}; +#endif + +/*****************************************************************************/ + +/** EoE constructor. + * + * Initializes the EoE handler, creates a net_device and registers it. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_eoe_init( + ec_eoe_t *eoe, /**< EoE handler */ + ec_slave_t *slave /**< EtherCAT slave */ + ) +{ + ec_eoe_t **priv; + int i, ret = 0; + char name[EC_DATAGRAM_NAME_SIZE]; + + eoe->slave = slave; + + ec_datagram_init(&eoe->datagram); + eoe->queue_datagram = 0; + eoe->state = ec_eoe_state_rx_start; + eoe->opened = 0; + eoe->rx_skb = NULL; + eoe->rx_expected_fragment = 0; + INIT_LIST_HEAD(&eoe->tx_queue); + eoe->tx_frame = NULL; + eoe->tx_queue_active = 0; + eoe->tx_queue_size = EC_EOE_TX_QUEUE_SIZE; + eoe->tx_queued_frames = 0; + + sema_init(&eoe->tx_queue_sem, 1); + eoe->tx_frame_number = 0xFF; + memset(&eoe->stats, 0, sizeof(struct net_device_stats)); + + eoe->rx_counter = 0; + eoe->tx_counter = 0; + eoe->rx_rate = 0; + eoe->tx_rate = 0; + eoe->rate_jiffies = 0; + eoe->rx_idle = 1; + eoe->tx_idle = 1; + + /* device name eoe<MASTER>[as]<SLAVE>, because networking scripts don't + * like hyphens etc. in interface names. */ + if (slave->effective_alias) { + snprintf(name, EC_DATAGRAM_NAME_SIZE, + "eoe%ua%u", slave->master->index, slave->effective_alias); + } else { + snprintf(name, EC_DATAGRAM_NAME_SIZE, + "eoe%us%u", slave->master->index, slave->ring_position); + } + + snprintf(eoe->datagram.name, EC_DATAGRAM_NAME_SIZE, name); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) + eoe->dev = alloc_netdev(sizeof(ec_eoe_t *), name, NET_NAME_UNKNOWN, + ether_setup); +#else + eoe->dev = alloc_netdev(sizeof(ec_eoe_t *), name, ether_setup); +#endif + if (!eoe->dev) { + EC_SLAVE_ERR(slave, "Unable to allocate net_device %s" + " for EoE handler!\n", name); + ret = -ENODEV; + goto out_return; + } + + // initialize net_device +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) + eoe->dev->netdev_ops = &ec_eoedev_ops; +#else + eoe->dev->open = ec_eoedev_open; + eoe->dev->stop = ec_eoedev_stop; + eoe->dev->hard_start_xmit = ec_eoedev_tx; + eoe->dev->get_stats = ec_eoedev_stats; +#endif + + for (i = 0; i < ETH_ALEN; i++) + eoe->dev->dev_addr[i] = i | (i << 4); + + // initialize private data + priv = netdev_priv(eoe->dev); + *priv = eoe; + + // Usually setting the MTU appropriately makes the upper layers + // do the frame fragmenting. In some cases this doesn't work + // so the MTU is left on the Ethernet standard value and fragmenting + // is done "manually". +#if 0 + eoe->dev->mtu = slave->configured_rx_mailbox_size - ETH_HLEN - 10; +#endif + + // connect the net_device to the kernel + ret = register_netdev(eoe->dev); + if (ret) { + EC_SLAVE_ERR(slave, "Unable to register net_device:" + " error %i\n", ret); + goto out_free; + } + + // make the last address octet unique + eoe->dev->dev_addr[ETH_ALEN - 1] = (uint8_t) eoe->dev->ifindex; + return 0; + + out_free: + free_netdev(eoe->dev); + eoe->dev = NULL; + out_return: + return ret; +} + +/*****************************************************************************/ + +/** EoE destructor. + * + * Unregisteres the net_device and frees allocated memory. + */ +void ec_eoe_clear(ec_eoe_t *eoe /**< EoE handler */) +{ + unregister_netdev(eoe->dev); // possibly calls close callback + + // empty transmit queue + ec_eoe_flush(eoe); + + if (eoe->tx_frame) { + dev_kfree_skb(eoe->tx_frame->skb); + kfree(eoe->tx_frame); + } + + if (eoe->rx_skb) + dev_kfree_skb(eoe->rx_skb); + + free_netdev(eoe->dev); + + ec_datagram_clear(&eoe->datagram); +} + +/*****************************************************************************/ + +/** Empties the transmit queue. + */ +void ec_eoe_flush(ec_eoe_t *eoe /**< EoE handler */) +{ + ec_eoe_frame_t *frame, *next; + + down(&eoe->tx_queue_sem); + + list_for_each_entry_safe(frame, next, &eoe->tx_queue, queue) { + list_del(&frame->queue); + dev_kfree_skb(frame->skb); + kfree(frame); + } + eoe->tx_queued_frames = 0; + + up(&eoe->tx_queue_sem); +} + +/*****************************************************************************/ + +/** Sends a frame or the next fragment. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_eoe_send(ec_eoe_t *eoe /**< EoE handler */) +{ + size_t remaining_size, current_size, complete_offset; + unsigned int last_fragment; + uint8_t *data; +#if EOE_DEBUG_LEVEL >= 3 + unsigned int i; +#endif + + remaining_size = eoe->tx_frame->skb->len - eoe->tx_offset; + + if (remaining_size <= eoe->slave->configured_tx_mailbox_size - 10) { + current_size = remaining_size; + last_fragment = 1; + } else { + current_size = ((eoe->slave->configured_tx_mailbox_size - 10) / 32) * 32; + last_fragment = 0; + } + + if (eoe->tx_fragment_number) { + complete_offset = eoe->tx_offset / 32; + } + else { + // complete size in 32 bit blocks, rounded up. + complete_offset = remaining_size / 32 + 1; + } + +#if EOE_DEBUG_LEVEL >= 2 + EC_SLAVE_DBG(slave, 0, "EoE %s TX sending fragment %u%s" + " with %u octets (%u). %u frames queued.\n", + eoe->dev->name, eoe->tx_fragment_number, + last_fragment ? "" : "+", current_size, complete_offset, + eoe->tx_queued_frames); +#endif + +#if EOE_DEBUG_LEVEL >= 3 + EC_SLAVE_DBG(master, 0, ""); + for (i = 0; i < current_size; i++) { + printk("%02X ", eoe->tx_frame->skb->data[eoe->tx_offset + i]); + if ((i + 1) % 16 == 0) { + printk("\n"); + EC_SLAVE_DBG(master, 0, ""); + } + } + printk("\n"); +#endif + + data = ec_slave_mbox_prepare_send(eoe->slave, &eoe->datagram, + 0x02, current_size + 4); + if (IS_ERR(data)) + return PTR_ERR(data); + + EC_WRITE_U8 (data, 0x00); // eoe fragment req. + EC_WRITE_U8 (data + 1, last_fragment); + EC_WRITE_U16(data + 2, ((eoe->tx_fragment_number & 0x3F) | + (complete_offset & 0x3F) << 6 | + (eoe->tx_frame_number & 0x0F) << 12)); + + memcpy(data + 4, eoe->tx_frame->skb->data + eoe->tx_offset, current_size); + eoe->queue_datagram = 1; + + eoe->tx_offset += current_size; + eoe->tx_fragment_number++; + return 0; +} + +/*****************************************************************************/ + +/** Runs the EoE state machine. + */ +void ec_eoe_run(ec_eoe_t *eoe /**< EoE handler */) +{ + if (!eoe->opened) + return; + + // if the datagram was not sent, or is not yet received, skip this cycle + if (eoe->queue_datagram || eoe->datagram.state == EC_DATAGRAM_SENT) + return; + + // call state function + eoe->state(eoe); + + // update statistics + if (jiffies - eoe->rate_jiffies > HZ) { + eoe->rx_rate = eoe->rx_counter; + eoe->tx_rate = eoe->tx_counter; + eoe->rx_counter = 0; + eoe->tx_counter = 0; + eoe->rate_jiffies = jiffies; + } + + ec_datagram_output_stats(&eoe->datagram); +} + +/*****************************************************************************/ + +/** Queues the datagram, if necessary. + */ +void ec_eoe_queue(ec_eoe_t *eoe /**< EoE handler */) +{ + if (eoe->queue_datagram) { + ec_master_queue_datagram_ext(eoe->slave->master, &eoe->datagram); + eoe->queue_datagram = 0; + } +} + +/*****************************************************************************/ + +/** Returns the state of the device. + * + * \return 1 if the device is "up", 0 if it is "down" + */ +int ec_eoe_is_open(const ec_eoe_t *eoe /**< EoE handler */) +{ + return eoe->opened; +} + +/*****************************************************************************/ + +/** Returns the idle state. + * + * \retval 1 The device is idle. + * \retval 0 The device is busy. + */ +int ec_eoe_is_idle(const ec_eoe_t *eoe /**< EoE handler */) +{ + return eoe->rx_idle && eoe->tx_idle; +} + +/****************************************************************************** + * STATE PROCESSING FUNCTIONS + *****************************************************************************/ + +/** State: RX_START. + * + * Starts a new receiving sequence by queueing a datagram that checks the + * slave's mailbox for a new EoE datagram. + * + * \todo Use both devices. + */ +void ec_eoe_state_rx_start(ec_eoe_t *eoe /**< EoE handler */) +{ + if (eoe->slave->error_flag || + !eoe->slave->master->devices[EC_DEVICE_MAIN].link_state) { + eoe->rx_idle = 1; + eoe->tx_idle = 1; + return; + } + + ec_slave_mbox_prepare_check(eoe->slave, &eoe->datagram); + eoe->queue_datagram = 1; + eoe->state = ec_eoe_state_rx_check; +} + +/*****************************************************************************/ + +/** State: RX_CHECK. + * + * Processes the checking datagram sent in RX_START and issues a receive + * datagram, if new data is available. + */ +void ec_eoe_state_rx_check(ec_eoe_t *eoe /**< EoE handler */) +{ + if (eoe->datagram.state != EC_DATAGRAM_RECEIVED) { + eoe->stats.rx_errors++; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "Failed to receive mbox" + " check datagram for %s.\n", eoe->dev->name); +#endif + eoe->state = ec_eoe_state_tx_start; + return; + } + + if (!ec_slave_mbox_check(&eoe->datagram)) { + eoe->rx_idle = 1; + eoe->state = ec_eoe_state_tx_start; + return; + } + + eoe->rx_idle = 0; + ec_slave_mbox_prepare_fetch(eoe->slave, &eoe->datagram); + eoe->queue_datagram = 1; + eoe->state = ec_eoe_state_rx_fetch; +} + +/*****************************************************************************/ + +/** State: RX_FETCH. + * + * Checks if the requested data of RX_CHECK was received and processes the EoE + * datagram. + */ +void ec_eoe_state_rx_fetch(ec_eoe_t *eoe /**< EoE handler */) +{ + size_t rec_size, data_size; + uint8_t *data, frame_type, last_fragment, time_appended, mbox_prot; + uint8_t fragment_offset, fragment_number; +#if EOE_DEBUG_LEVEL >= 2 + uint8_t frame_number; +#endif + off_t offset; +#if EOE_DEBUG_LEVEL >= 3 + unsigned int i; +#endif + + if (eoe->datagram.state != EC_DATAGRAM_RECEIVED) { + eoe->stats.rx_errors++; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "Failed to receive mbox" + " fetch datagram for %s.\n", eoe->dev->name); +#endif + eoe->state = ec_eoe_state_tx_start; + return; + } + + data = ec_slave_mbox_fetch(eoe->slave, &eoe->datagram, + &mbox_prot, &rec_size); + if (IS_ERR(data)) { + eoe->stats.rx_errors++; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "Invalid mailbox response for %s.\n", + eoe->dev->name); +#endif + eoe->state = ec_eoe_state_tx_start; + return; + } + + if (mbox_prot != 0x02) { // EoE FIXME mailbox handler necessary + eoe->stats.rx_errors++; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "Other mailbox protocol response for %s.\n", + eoe->dev->name); +#endif + eoe->state = ec_eoe_state_tx_start; + return; + } + + frame_type = EC_READ_U16(data) & 0x000F; + + if (frame_type != 0x00) { +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "%s: Other frame received." + " Dropping.\n", eoe->dev->name); +#endif + eoe->stats.rx_dropped++; + eoe->state = ec_eoe_state_tx_start; + return; + } + + // EoE Fragment Request received + + last_fragment = (EC_READ_U16(data) >> 8) & 0x0001; + time_appended = (EC_READ_U16(data) >> 9) & 0x0001; + fragment_number = EC_READ_U16(data + 2) & 0x003F; + fragment_offset = (EC_READ_U16(data + 2) >> 6) & 0x003F; +#if EOE_DEBUG_LEVEL >= 2 + frame_number = (EC_READ_U16(data + 2) >> 12) & 0x000F; +#endif + +#if EOE_DEBUG_LEVEL >= 2 + EC_SLAVE_DBG(eoe->slave, 0, "EoE %s RX fragment %u%s, offset %u," + " frame %u%s, %u octets\n", eoe->dev->name, fragment_number, + last_fragment ? "" : "+", fragment_offset, frame_number, + time_appended ? ", + timestamp" : "", + time_appended ? rec_size - 8 : rec_size - 4); +#endif + +#if EOE_DEBUG_LEVEL >= 3 + EC_SLAVE_DBG(eoe->slave, 0, ""); + for (i = 0; i < rec_size - 4; i++) { + printk("%02X ", data[i + 4]); + if ((i + 1) % 16 == 0) { + printk("\n"); + EC_SLAVE_DBG(eoe->slave, 0, ""); + } + } + printk("\n"); +#endif + + data_size = time_appended ? rec_size - 8 : rec_size - 4; + + if (!fragment_number) { + if (eoe->rx_skb) { + EC_SLAVE_WARN(eoe->slave, "EoE RX freeing old socket buffer.\n"); + dev_kfree_skb(eoe->rx_skb); + } + + // new socket buffer + if (!(eoe->rx_skb = dev_alloc_skb(fragment_offset * 32))) { + if (printk_ratelimit()) + EC_SLAVE_WARN(eoe->slave, "EoE RX low on mem," + " frame dropped.\n"); + eoe->stats.rx_dropped++; + eoe->state = ec_eoe_state_tx_start; + return; + } + + eoe->rx_skb_offset = 0; + eoe->rx_skb_size = fragment_offset * 32; + eoe->rx_expected_fragment = 0; + } + else { + if (!eoe->rx_skb) { + eoe->stats.rx_dropped++; + eoe->state = ec_eoe_state_tx_start; + return; + } + + offset = fragment_offset * 32; + if (offset != eoe->rx_skb_offset || + offset + data_size > eoe->rx_skb_size || + fragment_number != eoe->rx_expected_fragment) { + dev_kfree_skb(eoe->rx_skb); + eoe->rx_skb = NULL; + eoe->stats.rx_errors++; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "Fragmenting error at %s.\n", + eoe->dev->name); +#endif + eoe->state = ec_eoe_state_tx_start; + return; + } + } + + // copy fragment into socket buffer + memcpy(skb_put(eoe->rx_skb, data_size), data + 4, data_size); + eoe->rx_skb_offset += data_size; + + if (last_fragment) { + // update statistics + eoe->stats.rx_packets++; + eoe->stats.rx_bytes += eoe->rx_skb->len; + eoe->rx_counter += eoe->rx_skb->len; + +#if EOE_DEBUG_LEVEL >= 2 + EC_SLAVE_DBG(eoe->slave, 0, "EoE %s RX frame completed" + " with %u octets.\n", eoe->dev->name, eoe->rx_skb->len); +#endif + + // pass socket buffer to network stack + eoe->rx_skb->dev = eoe->dev; + eoe->rx_skb->protocol = eth_type_trans(eoe->rx_skb, eoe->dev); + eoe->rx_skb->ip_summed = CHECKSUM_UNNECESSARY; + if (netif_rx(eoe->rx_skb)) { + EC_SLAVE_WARN(eoe->slave, "EoE RX netif_rx failed.\n"); + } + eoe->rx_skb = NULL; + + eoe->state = ec_eoe_state_tx_start; + } + else { + eoe->rx_expected_fragment++; +#if EOE_DEBUG_LEVEL >= 2 + EC_SLAVE_DBG(eoe->slave, 0, "EoE %s RX expecting fragment %u\n", + eoe->dev->name, eoe->rx_expected_fragment); +#endif + eoe->state = ec_eoe_state_rx_start; + } +} + +/*****************************************************************************/ + +/** State: TX START. + * + * Starts a new transmit sequence. If no data is available, a new receive + * sequence is started instead. + * + * \todo Use both devices. + */ +void ec_eoe_state_tx_start(ec_eoe_t *eoe /**< EoE handler */) +{ +#if EOE_DEBUG_LEVEL >= 2 + unsigned int wakeup = 0; +#endif + + if (eoe->slave->error_flag || + !eoe->slave->master->devices[EC_DEVICE_MAIN].link_state) { + eoe->rx_idle = 1; + eoe->tx_idle = 1; + return; + } + + down(&eoe->tx_queue_sem); + + if (!eoe->tx_queued_frames || list_empty(&eoe->tx_queue)) { + up(&eoe->tx_queue_sem); + eoe->tx_idle = 1; + // no data available. + // start a new receive immediately. + ec_eoe_state_rx_start(eoe); + return; + } + + // take the first frame out of the queue + eoe->tx_frame = list_entry(eoe->tx_queue.next, ec_eoe_frame_t, queue); + list_del(&eoe->tx_frame->queue); + if (!eoe->tx_queue_active && + eoe->tx_queued_frames == eoe->tx_queue_size / 2) { + netif_wake_queue(eoe->dev); + eoe->tx_queue_active = 1; +#if EOE_DEBUG_LEVEL >= 2 + wakeup = 1; +#endif + } + + eoe->tx_queued_frames--; + up(&eoe->tx_queue_sem); + + eoe->tx_idle = 0; + + eoe->tx_frame_number++; + eoe->tx_frame_number %= 16; + eoe->tx_fragment_number = 0; + eoe->tx_offset = 0; + + if (ec_eoe_send(eoe)) { + dev_kfree_skb(eoe->tx_frame->skb); + kfree(eoe->tx_frame); + eoe->tx_frame = NULL; + eoe->stats.tx_errors++; + eoe->state = ec_eoe_state_rx_start; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "Send error at %s.\n", eoe->dev->name); +#endif + return; + } + +#if EOE_DEBUG_LEVEL >= 2 + if (wakeup) + EC_SLAVE_DBG(eoe->slave, 0, "EoE %s waking up TX queue...\n", + eoe->dev->name); +#endif + + eoe->tries = EC_EOE_TRIES; + eoe->state = ec_eoe_state_tx_sent; +} + +/*****************************************************************************/ + +/** State: TX SENT. + * + * Checks is the previous transmit datagram succeded and sends the next + * fragment, if necessary. + */ +void ec_eoe_state_tx_sent(ec_eoe_t *eoe /**< EoE handler */) +{ + if (eoe->datagram.state != EC_DATAGRAM_RECEIVED) { + if (eoe->tries) { + eoe->tries--; // try again + eoe->queue_datagram = 1; + } else { + eoe->stats.tx_errors++; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "Failed to receive send" + " datagram for %s after %u tries.\n", + eoe->dev->name, EC_EOE_TRIES); +#endif + eoe->state = ec_eoe_state_rx_start; + } + return; + } + + if (eoe->datagram.working_counter != 1) { + if (eoe->tries) { + eoe->tries--; // try again + eoe->queue_datagram = 1; + } else { + eoe->stats.tx_errors++; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "No sending response" + " for %s after %u tries.\n", + eoe->dev->name, EC_EOE_TRIES); +#endif + eoe->state = ec_eoe_state_rx_start; + } + return; + } + + // frame completely sent + if (eoe->tx_offset >= eoe->tx_frame->skb->len) { + eoe->stats.tx_packets++; + eoe->stats.tx_bytes += eoe->tx_frame->skb->len; + eoe->tx_counter += eoe->tx_frame->skb->len; + dev_kfree_skb(eoe->tx_frame->skb); + kfree(eoe->tx_frame); + eoe->tx_frame = NULL; + eoe->state = ec_eoe_state_rx_start; + } + else { // send next fragment + if (ec_eoe_send(eoe)) { + dev_kfree_skb(eoe->tx_frame->skb); + kfree(eoe->tx_frame); + eoe->tx_frame = NULL; + eoe->stats.tx_errors++; +#if EOE_DEBUG_LEVEL >= 1 + EC_SLAVE_WARN(eoe->slave, "Send error at %s.\n", eoe->dev->name); +#endif + eoe->state = ec_eoe_state_rx_start; + } + } +} + +/****************************************************************************** + * NET_DEVICE functions + *****************************************************************************/ + +/** Opens the virtual network device. + * + * \return Always zero (success). + */ +int ec_eoedev_open(struct net_device *dev /**< EoE net_device */) +{ + ec_eoe_t *eoe = *((ec_eoe_t **) netdev_priv(dev)); + ec_eoe_flush(eoe); + eoe->opened = 1; + eoe->rx_idle = 0; + eoe->tx_idle = 0; + netif_start_queue(dev); + eoe->tx_queue_active = 1; +#if EOE_DEBUG_LEVEL >= 2 + EC_SLAVE_DBG(eoe->slave, 0, "%s opened.\n", dev->name); +#endif + ec_slave_request_state(eoe->slave, EC_SLAVE_STATE_OP); + return 0; +} + +/*****************************************************************************/ + +/** Stops the virtual network device. + * + * \return Always zero (success). + */ +int ec_eoedev_stop(struct net_device *dev /**< EoE net_device */) +{ + ec_eoe_t *eoe = *((ec_eoe_t **) netdev_priv(dev)); + netif_stop_queue(dev); + eoe->rx_idle = 1; + eoe->tx_idle = 1; + eoe->tx_queue_active = 0; + eoe->opened = 0; + ec_eoe_flush(eoe); +#if EOE_DEBUG_LEVEL >= 2 + EC_SLAVE_DBG(eoe->slave, 0, "%s stopped.\n", dev->name); +#endif + ec_slave_request_state(eoe->slave, EC_SLAVE_STATE_PREOP); + return 0; +} + +/*****************************************************************************/ + +/** Transmits data via the virtual network device. + * + * \return Zero on success, non-zero on failure. + */ +int ec_eoedev_tx(struct sk_buff *skb, /**< transmit socket buffer */ + struct net_device *dev /**< EoE net_device */ + ) +{ + ec_eoe_t *eoe = *((ec_eoe_t **) netdev_priv(dev)); + ec_eoe_frame_t *frame; + +#if 0 + if (skb->len > eoe->slave->configured_tx_mailbox_size - 10) { + EC_SLAVE_WARN(eoe->slave, "EoE TX frame (%u octets)" + " exceeds MTU. dropping.\n", skb->len); + dev_kfree_skb(skb); + eoe->stats.tx_dropped++; + return 0; + } +#endif + + if (!(frame = + (ec_eoe_frame_t *) kmalloc(sizeof(ec_eoe_frame_t), GFP_ATOMIC))) { + if (printk_ratelimit()) + EC_SLAVE_WARN(eoe->slave, "EoE TX: low on mem. frame dropped.\n"); + return 1; + } + + frame->skb = skb; + + down(&eoe->tx_queue_sem); + list_add_tail(&frame->queue, &eoe->tx_queue); + eoe->tx_queued_frames++; + if (eoe->tx_queued_frames == eoe->tx_queue_size) { + netif_stop_queue(dev); + eoe->tx_queue_active = 0; + } + up(&eoe->tx_queue_sem); + +#if EOE_DEBUG_LEVEL >= 2 + EC_SLAVE_DBG(eoe->slave, 0, "EoE %s TX queued frame" + " with %u octets (%u frames queued).\n", + eoe->dev->name, skb->len, eoe->tx_queued_frames); + if (!eoe->tx_queue_active) + EC_SLAVE_WARN(eoe->slave, "EoE TX queue is now full.\n"); +#endif + + return 0; +} + +/*****************************************************************************/ + +/** Gets statistics about the virtual network device. + * + * \return Statistics. + */ +struct net_device_stats *ec_eoedev_stats( + struct net_device *dev /**< EoE net_device */ + ) +{ + ec_eoe_t *eoe = *((ec_eoe_t **) netdev_priv(dev)); + return &eoe->stats; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/ethernet.h b/net/ethercat/master/ethernet.h new file mode 100644 index 000000000000..b0b3860ce1d5 --- /dev/null +++ b/net/ethercat/master/ethernet.h @@ -0,0 +1,125 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Ethernet over EtherCAT (EoE) +*/ + +/*****************************************************************************/ + +#ifndef __EC_ETHERNET_H__ +#define __EC_ETHERNET_H__ + +#include <linux/list.h> +#include <linux/netdevice.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27) +#include <linux/semaphore.h> +#else +#include <asm/semaphore.h> +#endif + +#include "globals.h" +#include "slave.h" +#include "datagram.h" + +/*****************************************************************************/ + +/** + Queued frame structure. +*/ + +typedef struct +{ + struct list_head queue; /**< list item */ + struct sk_buff *skb; /**< socket buffer */ +} +ec_eoe_frame_t; + +/*****************************************************************************/ + +typedef struct ec_eoe ec_eoe_t; /**< \see ec_eoe */ + +/** + Ethernet over EtherCAT (EoE) handler. + The master creates one of these objects for each slave that supports the + EoE protocol. +*/ + +struct ec_eoe +{ + struct list_head list; /**< list item */ + ec_slave_t *slave; /**< pointer to the corresponding slave */ + ec_datagram_t datagram; /**< datagram */ + unsigned int queue_datagram; /**< the datagram is ready for queuing */ + void (*state)(ec_eoe_t *); /**< state function for the state machine */ + struct net_device *dev; /**< net_device for virtual ethernet device */ + struct net_device_stats stats; /**< device statistics */ + unsigned int opened; /**< net_device is opened */ + unsigned long rate_jiffies; /**< time of last rate output */ + + struct sk_buff *rx_skb; /**< current rx socket buffer */ + off_t rx_skb_offset; /**< current write pointer in the socket buffer */ + size_t rx_skb_size; /**< size of the allocated socket buffer memory */ + uint8_t rx_expected_fragment; /**< next expected fragment number */ + uint32_t rx_counter; /**< octets received during last second */ + uint32_t rx_rate; /**< receive rate (bps) */ + unsigned int rx_idle; /**< Idle flag. */ + + struct list_head tx_queue; /**< queue for frames to send */ + unsigned int tx_queue_size; /**< Transmit queue size. */ + unsigned int tx_queue_active; /**< kernel netif queue started */ + unsigned int tx_queued_frames; /**< number of frames in the queue */ + struct semaphore tx_queue_sem; /**< Semaphore for the send queue. */ + ec_eoe_frame_t *tx_frame; /**< current TX frame */ + uint8_t tx_frame_number; /**< number of the transmitted frame */ + uint8_t tx_fragment_number; /**< number of the fragment */ + size_t tx_offset; /**< number of octets sent */ + uint32_t tx_counter; /**< octets transmitted during last second */ + uint32_t tx_rate; /**< transmit rate (bps) */ + unsigned int tx_idle; /**< Idle flag. */ + + unsigned int tries; /**< Tries. */ +}; + +/*****************************************************************************/ + +int ec_eoe_init(ec_eoe_t *, ec_slave_t *); +void ec_eoe_clear(ec_eoe_t *); +void ec_eoe_run(ec_eoe_t *); +void ec_eoe_queue(ec_eoe_t *); +int ec_eoe_is_open(const ec_eoe_t *); +int ec_eoe_is_idle(const ec_eoe_t *); + +/*****************************************************************************/ + +#endif + +/*****************************************************************************/ diff --git a/net/ethercat/master/fmmu_config.c b/net/ethercat/master/fmmu_config.c new file mode 100644 index 000000000000..957100f2ef3c --- /dev/null +++ b/net/ethercat/master/fmmu_config.c @@ -0,0 +1,99 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT FMMU configuration methods. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "slave_config.h" +#include "master.h" + +#include "fmmu_config.h" + +/*****************************************************************************/ + +/** FMMU configuration constructor. + * + * Inits an FMMU configuration, sets the logical start address and adds the + * process data size for the mapped PDOs of the given direction to the domain + * data size. + */ +void ec_fmmu_config_init( + ec_fmmu_config_t *fmmu, /**< EtherCAT FMMU configuration. */ + ec_slave_config_t *sc, /**< EtherCAT slave configuration. */ + ec_domain_t *domain, /**< EtherCAT domain. */ + uint8_t sync_index, /**< Sync manager index to use. */ + ec_direction_t dir /**< PDO direction. */ + ) +{ + INIT_LIST_HEAD(&fmmu->list); + fmmu->sc = sc; + fmmu->sync_index = sync_index; + fmmu->dir = dir; + + fmmu->logical_start_address = domain->data_size; + fmmu->data_size = ec_pdo_list_total_size( + &sc->sync_configs[sync_index].pdos); + + ec_domain_add_fmmu_config(domain, fmmu); +} + +/*****************************************************************************/ + +/** Initializes an FMMU configuration page. + * + * The referenced memory (\a data) must be at least EC_FMMU_PAGE_SIZE bytes. + */ +void ec_fmmu_config_page( + const ec_fmmu_config_t *fmmu, /**< EtherCAT FMMU configuration. */ + const ec_sync_t *sync, /**< Sync manager. */ + uint8_t *data /**> Configuration page memory. */ + ) +{ + EC_CONFIG_DBG(fmmu->sc, 1, "FMMU: LogAddr 0x%08X, Size %3u," + " PhysAddr 0x%04X, SM%u, Dir %s\n", + fmmu->logical_start_address, fmmu->data_size, + sync->physical_start_address, fmmu->sync_index, + fmmu->dir == EC_DIR_INPUT ? "in" : "out"); + + EC_WRITE_U32(data, fmmu->logical_start_address); + EC_WRITE_U16(data + 4, fmmu->data_size); // size of fmmu + EC_WRITE_U8 (data + 6, 0x00); // logical start bit + EC_WRITE_U8 (data + 7, 0x07); // logical end bit + EC_WRITE_U16(data + 8, sync->physical_start_address); + EC_WRITE_U8 (data + 10, 0x00); // physical start bit + EC_WRITE_U8 (data + 11, fmmu->dir == EC_DIR_INPUT ? 0x01 : 0x02); + EC_WRITE_U16(data + 12, 0x0001); // enable + EC_WRITE_U16(data + 14, 0x0000); // reserved +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fmmu_config.h b/net/ethercat/master/fmmu_config.h new file mode 100644 index 000000000000..4803f3fc9175 --- /dev/null +++ b/net/ethercat/master/fmmu_config.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT FMMU configuration structure. + */ + +/*****************************************************************************/ + +#ifndef __EC_FMMU_CONFIG_H__ +#define __EC_FMMU_CONFIG_H__ + +#include "globals.h" +#include "sync.h" + +/*****************************************************************************/ + +/** FMMU configuration. + */ +typedef struct { + struct list_head list; /**< List node used by domain. */ + const ec_slave_config_t *sc; /**< EtherCAT slave config. */ + const ec_domain_t *domain; /**< Domain. */ + uint8_t sync_index; /**< Index of sync manager to use. */ + ec_direction_t dir; /**< FMMU direction. */ + uint32_t logical_start_address; /**< Logical start address. */ + unsigned int data_size; /**< Covered PDO size. */ +} ec_fmmu_config_t; + +/*****************************************************************************/ + +void ec_fmmu_config_init(ec_fmmu_config_t *, ec_slave_config_t *, + ec_domain_t *, uint8_t, ec_direction_t); + +void ec_fmmu_config_page(const ec_fmmu_config_t *, const ec_sync_t *, + uint8_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/foe.h b/net/ethercat/master/foe.h new file mode 100644 index 000000000000..44090b0b7435 --- /dev/null +++ b/net/ethercat/master/foe.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * FoE defines. + */ + +#ifndef __FOE_H__ +#define __FOE_H__ + +/*****************************************************************************/ + +/** FoE error enumeration type. + */ +typedef enum { + FOE_BUSY = 0, /**< Busy. */ + FOE_READY = 1, /**< Ready. */ + FOE_IDLE = 2, /**< Idle. */ + FOE_WC_ERROR = 3, /**< Working counter error. */ + FOE_RECEIVE_ERROR = 4, /**< Receive error. */ + FOE_PROT_ERROR = 5, /**< Protocol error. */ + FOE_NODATA_ERROR = 6, /**< No data error. */ + FOE_PACKETNO_ERROR = 7, /**< Packet number error. */ + FOE_OPCODE_ERROR = 8, /**< OpCode error. */ + FOE_TIMEOUT_ERROR = 9, /**< Timeout error. */ + FOE_SEND_RX_DATA_ERROR = 10, /**< Error sending received data. */ + FOE_RX_DATA_ACK_ERROR = 11, /**< Error acknowledging received data. */ + FOE_ACK_ERROR = 12, /**< Acknowledge error. */ + FOE_MBOX_FETCH_ERROR = 13, /**< Error fetching data from mailbox. */ + FOE_READ_NODATA_ERROR = 14, /**< No data while reading. */ + FOE_MBOX_PROT_ERROR = 15, /**< Mailbox protocol error. */ +} ec_foe_error_t; + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/foe_request.c b/net/ethercat/master/foe_request.c new file mode 100644 index 000000000000..66a65e8f9c17 --- /dev/null +++ b/net/ethercat/master/foe_request.c @@ -0,0 +1,238 @@ +/****************************************************************************** + * + * Copyright (C) 2008 Olav Zarges, imc Messsysteme GmbH + * Copyright (C) 2020 Florian Pose, IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * File-over-EtherCAT request functions. + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> + +#include "foe_request.h" +#include "foe.h" + +/*****************************************************************************/ + +/** Default timeout in ms to wait for FoE transfer responses. + */ +#define EC_FOE_REQUEST_RESPONSE_TIMEOUT 3000 + +/*****************************************************************************/ + +void ec_foe_request_clear_data(ec_foe_request_t *); + +/*****************************************************************************/ + +/** FoE request constructor. + */ +void ec_foe_request_init( + ec_foe_request_t *req, /**< FoE request. */ + uint8_t* file_name /** filename */) +{ + INIT_LIST_HEAD(&req->list); + req->buffer = NULL; + req->file_name = file_name; + req->buffer_size = 0; + req->data_size = 0; + req->dir = EC_DIR_INVALID; + req->issue_timeout = 0; // no timeout + req->response_timeout = EC_FOE_REQUEST_RESPONSE_TIMEOUT; + req->state = EC_INT_REQUEST_INIT; + req->result = FOE_BUSY; + req->error_code = 0x00000000; +} + +/*****************************************************************************/ + +/** FoE request destructor. + */ +void ec_foe_request_clear( + ec_foe_request_t *req /**< FoE request. */ + ) +{ + ec_foe_request_clear_data(req); +} + +/*****************************************************************************/ + +/** FoE request destructor. + */ +void ec_foe_request_clear_data( + ec_foe_request_t *req /**< FoE request. */ + ) +{ + if (req->buffer) { + vfree(req->buffer); + req->buffer = NULL; + } + + req->buffer_size = 0; + req->data_size = 0; +} + +/*****************************************************************************/ + +/** Pre-allocates the data memory. + * + * If the internal \a buffer_size is already bigger than \a size, nothing is + * done. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_foe_request_alloc( + ec_foe_request_t *req, /**< FoE request. */ + size_t size /**< Data size to allocate. */ + ) +{ + if (size <= req->buffer_size) { + return 0; + } + + ec_foe_request_clear_data(req); + + if (!(req->buffer = (uint8_t *) vmalloc(size))) { + EC_ERR("Failed to allocate %zu bytes of FoE memory.\n", size); + return -ENOMEM; + } + + req->buffer_size = size; + req->data_size = 0; + return 0; +} + +/*****************************************************************************/ + +/** Copies FoE data from an external source. + * + * If the \a buffer_size is to small, new memory is allocated. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_foe_request_copy_data( + ec_foe_request_t *req, /**< FoE request. */ + const uint8_t *source, /**< Source data. */ + size_t size /**< Number of bytes in \a source. */ + ) +{ + int ret; + + ret = ec_foe_request_alloc(req, size); + if (ret) { + return ret; + } + + memcpy(req->buffer, source, size); + req->data_size = size; + return 0; +} + +/*****************************************************************************/ + +/** Checks, if the timeout was exceeded. + * + * \return non-zero if the timeout was exceeded, else zero. + */ +int ec_foe_request_timed_out( + const ec_foe_request_t *req /**< FoE request. */ + ) +{ + return req->issue_timeout + && jiffies - req->jiffies_start > HZ * req->issue_timeout / 1000; +} + +/*****************************************************************************/ + +/** Set the request timeout. + */ +void ec_foe_request_timeout( + ec_foe_request_t *req, /**< FoE request. */ + uint32_t timeout /**< Timeout in ms. */ + ) +{ + req->issue_timeout = timeout; +} + +/*****************************************************************************/ + +/** Returns a pointer to the request's data. + * + * \return Data pointer. + */ +uint8_t *ec_foe_request_data( + ec_foe_request_t *req /**< FoE request. */ + ) +{ + return req->buffer; +} + +/*****************************************************************************/ + +/** Returns the data size. + * + * \return Data size. + */ +size_t ec_foe_request_data_size( + const ec_foe_request_t *req /**< FoE request. */ + ) +{ + return req->data_size; +} + +/*****************************************************************************/ + +/** Prepares a read request (slave to master). + */ +void ec_foe_request_read( + ec_foe_request_t *req /**< FoE request. */ + ) +{ + req->dir = EC_DIR_INPUT; + req->state = EC_INT_REQUEST_QUEUED; + req->result = FOE_BUSY; + req->jiffies_start = jiffies; +} + +/*****************************************************************************/ + +/** Prepares a write request (master to slave). + */ +void ec_foe_request_write( + ec_foe_request_t *req /**< FoE request. */ + ) +{ + req->dir = EC_DIR_OUTPUT; + req->state = EC_INT_REQUEST_QUEUED; + req->result = FOE_BUSY; + req->jiffies_start = jiffies; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/foe_request.h b/net/ethercat/master/foe_request.h new file mode 100644 index 000000000000..25b9e63821ff --- /dev/null +++ b/net/ethercat/master/foe_request.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2008 Olav Zarges, imc Messsysteme GmbH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT FoE request structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FOE_REQUEST_H__ +#define __EC_FOE_REQUEST_H__ + +#include <linux/list.h> + +#include "../include/ecrt.h" + +#include "globals.h" + +/*****************************************************************************/ + +/** FoE request. + */ +typedef struct { + struct list_head list; /**< List item. */ + uint8_t *buffer; /**< Pointer to FoE data. */ + size_t buffer_size; /**< Size of FoE data memory. */ + size_t data_size; /**< Size of FoE data. */ + + uint32_t issue_timeout; /**< Maximum time in ms, the processing of the + request may take. */ + uint32_t response_timeout; /**< Maximum time in ms, the transfer is + retried, if the slave does not respond. */ + ec_direction_t dir; /**< Direction. EC_DIR_OUTPUT means downloading to + the slave, EC_DIR_INPUT means uploading from the + slave. */ + ec_internal_request_state_t state; /**< FoE request state. */ + unsigned long jiffies_start; /**< Jiffies, when the request was issued. */ + unsigned long jiffies_sent; /**< Jiffies, when the upload/download + request was sent. */ + uint8_t *file_name; /**< Pointer to the filename. */ + uint32_t result; /**< FoE request abort code. Zero on success. */ + uint32_t error_code; /**< Error code from an FoE Error Request. */ +} ec_foe_request_t; + +/*****************************************************************************/ + +void ec_foe_request_init(ec_foe_request_t *, uint8_t *file_name); +void ec_foe_request_clear(ec_foe_request_t *); + +int ec_foe_request_alloc(ec_foe_request_t *, size_t); +int ec_foe_request_copy_data(ec_foe_request_t *, const uint8_t *, size_t); +int ec_foe_request_timed_out(const ec_foe_request_t *); + +void ec_foe_request_write(ec_foe_request_t *); +void ec_foe_request_read(ec_foe_request_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_change.c b/net/ethercat/master/fsm_change.c new file mode 100644 index 000000000000..584a20270d55 --- /dev/null +++ b/net/ethercat/master/fsm_change.c @@ -0,0 +1,577 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT state change FSM. +*/ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "fsm_change.h" + +/*****************************************************************************/ + +/** Timeout while waiting for AL state change [s]. + */ +#define EC_AL_STATE_CHANGE_TIMEOUT 5 + +/*****************************************************************************/ + +void ec_fsm_change_state_start(ec_fsm_change_t *); +void ec_fsm_change_state_check(ec_fsm_change_t *); +void ec_fsm_change_state_status(ec_fsm_change_t *); +void ec_fsm_change_state_start_code(ec_fsm_change_t *); +void ec_fsm_change_state_code(ec_fsm_change_t *); +void ec_fsm_change_state_ack(ec_fsm_change_t *); +void ec_fsm_change_state_check_ack(ec_fsm_change_t *); +void ec_fsm_change_state_end(ec_fsm_change_t *); +void ec_fsm_change_state_error(ec_fsm_change_t *); + +/*****************************************************************************/ + +/** + Constructor. +*/ + +void ec_fsm_change_init(ec_fsm_change_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< datagram */ + ) +{ + fsm->state = NULL; + fsm->datagram = datagram; + fsm->spontaneous_change = 0; +} + +/*****************************************************************************/ + +/** + Destructor. +*/ + +void ec_fsm_change_clear(ec_fsm_change_t *fsm /**< finite state machine */) +{ +} + +/*****************************************************************************/ + +/** + Starts the change state machine. +*/ + +void ec_fsm_change_start(ec_fsm_change_t *fsm, /**< finite state machine */ + ec_slave_t *slave, /**< EtherCAT slave */ + ec_slave_state_t state /**< requested state */ + ) +{ + fsm->mode = EC_FSM_CHANGE_MODE_FULL; + fsm->slave = slave; + fsm->requested_state = state; + fsm->state = ec_fsm_change_state_start; +} + +/*****************************************************************************/ + +/** + Starts the change state machine to only acknowlegde a slave's state. +*/ + +void ec_fsm_change_ack(ec_fsm_change_t *fsm, /**< finite state machine */ + ec_slave_t *slave /**< EtherCAT slave */ + ) +{ + fsm->mode = EC_FSM_CHANGE_MODE_ACK_ONLY; + fsm->slave = slave; + fsm->requested_state = EC_SLAVE_STATE_UNKNOWN; + fsm->state = ec_fsm_change_state_start_code; +} + +/*****************************************************************************/ + +/** + Executes the current state of the state machine. + \return false, if the state machine has terminated +*/ + +int ec_fsm_change_exec(ec_fsm_change_t *fsm /**< finite state machine */) +{ + fsm->state(fsm); + + return fsm->state != ec_fsm_change_state_end + && fsm->state != ec_fsm_change_state_error; +} + +/*****************************************************************************/ + +/** + Returns, if the state machine terminated with success. + \return non-zero if successful. +*/ + +int ec_fsm_change_success(ec_fsm_change_t *fsm /**< Finite state machine */) +{ + return fsm->state == ec_fsm_change_state_end; +} + +/****************************************************************************** + * state change state machine + *****************************************************************************/ + +/** + Change state: START. +*/ + +void ec_fsm_change_state_start(ec_fsm_change_t *fsm + /**< finite state machine */) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + fsm->take_time = 1; + fsm->old_state = fsm->slave->current_state; + + // write new state to slave + ec_datagram_fpwr(datagram, slave->station_address, 0x0120, 2); + EC_WRITE_U16(datagram->data, fsm->requested_state); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_change_state_check; +} + +/*****************************************************************************/ + +/** + Change state: CHECK. +*/ + +void ec_fsm_change_state_check(ec_fsm_change_t *fsm + /**< finite state machine */) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Failed to receive state datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (fsm->take_time) { + fsm->take_time = 0; + fsm->jiffies_start = datagram->jiffies_sent; + } + + if (datagram->working_counter == 0) { + if (datagram->jiffies_received - fsm->jiffies_start >= 3 * HZ) { + char state_str[EC_STATE_STRING_SIZE]; + ec_state_string(fsm->requested_state, state_str, 0); + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Failed to set state %s: ", state_str); + ec_datagram_print_wc_error(datagram); + return; + } + + // repeat writing new state to slave + ec_datagram_fpwr(datagram, slave->station_address, 0x0120, 2); + EC_WRITE_U16(datagram->data, fsm->requested_state); + fsm->retries = EC_FSM_RETRIES; + return; + } + + if (unlikely(datagram->working_counter > 1)) { + char state_str[EC_STATE_STRING_SIZE]; + ec_state_string(fsm->requested_state, state_str, 0); + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Failed to set state %s: ", state_str); + ec_datagram_print_wc_error(datagram); + return; + } + + fsm->take_time = 1; + + // read AL status from slave + ec_datagram_fprd(datagram, slave->station_address, 0x0130, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->spontaneous_change = 0; + fsm->state = ec_fsm_change_state_status; +} + +/*****************************************************************************/ + +/** + Change state: STATUS. +*/ + +void ec_fsm_change_state_status(ec_fsm_change_t *fsm + /**< finite state machine */) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Failed to receive state checking datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + char req_state[EC_STATE_STRING_SIZE]; + ec_state_string(fsm->requested_state, req_state, 0); + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Failed to check state %s: ", req_state); + ec_datagram_print_wc_error(datagram); + return; + } + + if (fsm->take_time) { + fsm->take_time = 0; + fsm->jiffies_start = datagram->jiffies_sent; + } + + slave->current_state = EC_READ_U8(datagram->data); + + if (slave->current_state == fsm->requested_state) { + // state has been set successfully + fsm->state = ec_fsm_change_state_end; + return; + } + + if (slave->current_state != fsm->old_state) { // state changed + char req_state[EC_STATE_STRING_SIZE], cur_state[EC_STATE_STRING_SIZE]; + + ec_state_string(slave->current_state, cur_state, 0); + + if ((slave->current_state & 0x0F) != (fsm->old_state & 0x0F)) { + // Slave spontaneously changed its state just before the new state + // was written. Accept current state as old state and wait for + // state change + fsm->spontaneous_change = 1; + fsm->old_state = slave->current_state; + EC_SLAVE_WARN(slave, "Changed to %s in the meantime.\n", + cur_state); + goto check_again; + } + + // state change error + + slave->error_flag = 1; + ec_state_string(fsm->requested_state, req_state, 0); + + EC_SLAVE_ERR(slave, "Failed to set %s state, slave refused state" + " change (%s).\n", req_state, cur_state); + + ec_fsm_change_state_start_code(fsm); + return; + } + + // still old state + + if (datagram->jiffies_received - fsm->jiffies_start >= + EC_AL_STATE_CHANGE_TIMEOUT * HZ) { + // timeout while checking + char state_str[EC_STATE_STRING_SIZE]; + ec_state_string(fsm->requested_state, state_str, 0); + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Timeout while setting state %s.\n", state_str); + return; + } + + check_again: + // no timeout yet. check again + ec_datagram_fprd(datagram, slave->station_address, 0x0130, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; +} + +/*****************************************************************************/ + +/** Enter reading AL status code. + */ +void ec_fsm_change_state_start_code( + ec_fsm_change_t *fsm /**< finite state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + + // fetch AL status error code + ec_datagram_fprd(datagram, slave->station_address, 0x0134, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_change_state_code; +} + +/*****************************************************************************/ + +/** + Application layer status messages. +*/ + +const ec_code_msg_t al_status_messages[] = { + {0x0000, "No error"}, + {0x0001, "Unspecified error"}, + {0x0002, "No Memory"}, + {0x0011, "Invalid requested state change"}, + {0x0012, "Unknown requested state"}, + {0x0013, "Bootstrap not supported"}, + {0x0014, "No valid firmware"}, + {0x0015, "Invalid mailbox configuration"}, + {0x0016, "Invalid mailbox configuration"}, + {0x0017, "Invalid sync manager configuration"}, + {0x0018, "No valid inputs available"}, + {0x0019, "No valid outputs"}, + {0x001A, "Synchronization error"}, + {0x001B, "Sync manager watchdog"}, + {0x001C, "Invalid sync manager types"}, + {0x001D, "Invalid output configuration"}, + {0x001E, "Invalid input configuration"}, + {0x001F, "Invalid watchdog configuration"}, + {0x0020, "Slave needs cold start"}, + {0x0021, "Slave needs INIT"}, + {0x0022, "Slave needs PREOP"}, + {0x0023, "Slave needs SAFEOP"}, + {0x0024, "Invalid Input Mapping"}, + {0x0025, "Invalid Output Mapping"}, + {0x0026, "Inconsistent Settings"}, + {0x0027, "Freerun not supported"}, + {0x0028, "Synchronization not supported"}, + {0x0029, "Freerun needs 3 Buffer Mode"}, + {0x002A, "Background Watchdog"}, + {0x002B, "No Valid Inputs and Outputs"}, + {0x002C, "Fatal Sync Error"}, + {0x002D, "No Sync Error"}, + {0x0030, "Invalid DC SYNCH configuration"}, + {0x0031, "Invalid DC latch configuration"}, + {0x0032, "PLL error"}, + {0x0033, "DC Sync IO Error"}, + {0x0034, "DC Sync Timeout Error"}, + {0x0035, "DC Invalid Sync Cycle Time"}, + {0x0036, "DC Sync0 Cycle Time"}, + {0x0037, "DC Sync1 Cycle Time"}, + {0x0041, "MBX_AOE"}, + {0x0042, "MBX_EOE"}, + {0x0043, "MBX_COE"}, + {0x0044, "MBX_FOE"}, + {0x0045, "MBX_SOE"}, + {0x004F, "MBX_VOE"}, + {0x0050, "EEPROM No Access"}, + {0x0051, "EEPROM Error"}, + {0x0060, "Slave Restarted Locally"}, + {0xffff} +}; + + +/*****************************************************************************/ + +/** + Change state: CODE. +*/ + +void ec_fsm_change_state_code(ec_fsm_change_t *fsm + /**< finite state machine */) +{ + ec_datagram_t *datagram = fsm->datagram; + uint32_t code; + const ec_code_msg_t *al_msg; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(fsm->slave, "Failed to receive" + " AL status code datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + EC_SLAVE_WARN(fsm->slave, "Reception of AL status code" + " datagram failed: "); + ec_datagram_print_wc_error(datagram); + } else { + code = EC_READ_U16(datagram->data); + for (al_msg = al_status_messages; al_msg->code != 0xffff; al_msg++) { + if (al_msg->code != code) { + continue; + } + + EC_SLAVE_ERR(fsm->slave, "AL status message 0x%04X: \"%s\".\n", + al_msg->code, al_msg->message); + break; + } + if (al_msg->code == 0xffff) { /* not found in our list. */ + EC_SLAVE_ERR(fsm->slave, "Unknown AL status code 0x%04X.\n", + code); + } + } + + // acknowledge "old" slave state + ec_datagram_fpwr(datagram, fsm->slave->station_address, 0x0120, 2); + EC_WRITE_U16(datagram->data, fsm->slave->current_state); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_change_state_ack; +} + +/*****************************************************************************/ + +/** + Change state: ACK. +*/ + +void ec_fsm_change_state_ack(ec_fsm_change_t *fsm /**< finite state machine */) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Failed to receive state ack datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Reception of state ack datagram failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + + fsm->take_time = 1; + + // read new AL status + ec_datagram_fprd(datagram, slave->station_address, 0x0130, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_change_state_check_ack; +} + +/*****************************************************************************/ + +/** + Change state: CHECK ACK. +*/ + +void ec_fsm_change_state_check_ack(ec_fsm_change_t *fsm + /**< finite state machine */) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Failed to receive state ack check datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Reception of state ack check datagram failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + + if (fsm->take_time) { + fsm->take_time = 0; + fsm->jiffies_start = datagram->jiffies_sent; + } + + slave->current_state = EC_READ_U8(datagram->data); + + if (!(slave->current_state & EC_SLAVE_STATE_ACK_ERR)) { + char state_str[EC_STATE_STRING_SIZE]; + ec_state_string(slave->current_state, state_str, 0); + if (fsm->mode == EC_FSM_CHANGE_MODE_FULL) { + fsm->state = ec_fsm_change_state_error; + } + else { // EC_FSM_CHANGE_MODE_ACK_ONLY + fsm->state = ec_fsm_change_state_end; + } + EC_SLAVE_INFO(slave, "Acknowledged state %s.\n", state_str); + return; + } + + if (datagram->jiffies_received - fsm->jiffies_start >= + EC_AL_STATE_CHANGE_TIMEOUT * HZ) { + // timeout while checking + char state_str[EC_STATE_STRING_SIZE]; + ec_state_string(slave->current_state, state_str, 0); + fsm->state = ec_fsm_change_state_error; + EC_SLAVE_ERR(slave, "Timeout while acknowledging state %s.\n", + state_str); + return; + } + + // reread new AL status + ec_datagram_fprd(datagram, slave->station_address, 0x0130, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; +} + +/*****************************************************************************/ + +/** + State: ERROR. +*/ + +void ec_fsm_change_state_error(ec_fsm_change_t *fsm + /**< finite state machine */) +{ +} + +/*****************************************************************************/ + +/** + State: END. +*/ + +void ec_fsm_change_state_end(ec_fsm_change_t *fsm + /**< finite state machine */) +{ +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_change.h b/net/ethercat/master/fsm_change.h new file mode 100644 index 000000000000..59a4d66a7594 --- /dev/null +++ b/net/ethercat/master/fsm_change.h @@ -0,0 +1,92 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT state change FSM. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_CHANGE_H__ +#define __EC_FSM_CHANGE_H__ + +#include "globals.h" +#include "datagram.h" +#include "slave.h" + +/*****************************************************************************/ + +/** + Mode of the change state machine. +*/ + +typedef enum { + EC_FSM_CHANGE_MODE_FULL, /**< full state change */ + EC_FSM_CHANGE_MODE_ACK_ONLY /**< only state acknowledgement */ +} +ec_fsm_change_mode_t; + +/*****************************************************************************/ + +typedef struct ec_fsm_change ec_fsm_change_t; /**< \see ec_fsm_change */ + +/** + EtherCAT state change FSM. +*/ + +struct ec_fsm_change +{ + ec_slave_t *slave; /**< slave the FSM runs on */ + ec_datagram_t *datagram; /**< datagram used in the state machine */ + unsigned int retries; /**< retries upon datagram timeout */ + + void (*state)(ec_fsm_change_t *); /**< slave state change state function */ + ec_fsm_change_mode_t mode; /**< full state change, or ack only. */ + ec_slave_state_t requested_state; /**< input: state */ + ec_slave_state_t old_state; /**< prior slave state */ + unsigned long jiffies_start; /**< change timer */ + uint8_t take_time; /**< take sending timestamp */ + uint8_t spontaneous_change; /**< spontaneous state change detected */ +}; + +/*****************************************************************************/ + +void ec_fsm_change_init(ec_fsm_change_t *, ec_datagram_t *); +void ec_fsm_change_clear(ec_fsm_change_t *); + +void ec_fsm_change_start(ec_fsm_change_t *, ec_slave_t *, ec_slave_state_t); +void ec_fsm_change_ack(ec_fsm_change_t *, ec_slave_t *); + +int ec_fsm_change_exec(ec_fsm_change_t *); +int ec_fsm_change_success(ec_fsm_change_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_coe.c b/net/ethercat/master/fsm_coe.c new file mode 100644 index 000000000000..cea0b0239df3 --- /dev/null +++ b/net/ethercat/master/fsm_coe.c @@ -0,0 +1,2534 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT CoE state machines. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "fsm_coe.h" +#include "slave_config.h" + +/*****************************************************************************/ + +/** Maximum time in ms to wait for responses when reading out the dictionary. + */ +#define EC_FSM_COE_DICT_TIMEOUT 1000 + +/** CoE download request header size. + */ +#define EC_COE_DOWN_REQ_HEADER_SIZE 10 + +/** CoE download segment request header size. + */ +#define EC_COE_DOWN_SEG_REQ_HEADER_SIZE 3 + +/** Minimum size of download segment. + */ +#define EC_COE_DOWN_SEG_MIN_DATA_SIZE 7 + +/** Enable debug output for CoE retries. + */ +#define DEBUG_RETRIES 0 + +/** Enable warning output if transfers take too long. + */ +#define DEBUG_LONG 0 + +/*****************************************************************************/ + +void ec_fsm_coe_dict_start(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_request(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_check(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_response(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_desc_request(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_desc_check(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_desc_response(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_entry_request(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_entry_check(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_dict_entry_response(ec_fsm_coe_t *, ec_datagram_t *); + +void ec_fsm_coe_down_start(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_down_request(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_down_check(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_down_response(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_down_seg_check(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_down_seg_response(ec_fsm_coe_t *, ec_datagram_t *); + +void ec_fsm_coe_up_start(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_up_request(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_up_check(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_up_response(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_up_seg_request(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_up_seg_check(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_up_seg_response(ec_fsm_coe_t *, ec_datagram_t *); + +void ec_fsm_coe_end(ec_fsm_coe_t *, ec_datagram_t *); +void ec_fsm_coe_error(ec_fsm_coe_t *, ec_datagram_t *); + +/*****************************************************************************/ + +/** SDO abort messages. + * + * The "abort SDO transfer request" supplies an abort code, which can be + * translated to clear text. This table does the mapping of the codes and + * messages. + */ +const ec_code_msg_t sdo_abort_messages[] = { + {0x05030000, "Toggle bit not changed"}, + {0x05040000, "SDO protocol timeout"}, + {0x05040001, "Client/Server command specifier not valid or unknown"}, + {0x05040005, "Out of memory"}, + {0x06010000, "Unsupported access to an object"}, + {0x06010001, "Attempt to read a write-only object"}, + {0x06010002, "Attempt to write a read-only object"}, + {0x06020000, "This object does not exist in the object directory"}, + {0x06040041, "The object cannot be mapped into the PDO"}, + {0x06040042, "The number and length of the objects to be mapped would" + " exceed the PDO length"}, + {0x06040043, "General parameter incompatibility reason"}, + {0x06040047, "Gerneral internal incompatibility in device"}, + {0x06060000, "Access failure due to a hardware error"}, + {0x06070010, "Data type does not match, length of service parameter does" + " not match"}, + {0x06070012, "Data type does not match, length of service parameter too" + " high"}, + {0x06070013, "Data type does not match, length of service parameter too" + " low"}, + {0x06090011, "Subindex does not exist"}, + {0x06090030, "Value range of parameter exceeded"}, + {0x06090031, "Value of parameter written too high"}, + {0x06090032, "Value of parameter written too low"}, + {0x06090036, "Maximum value is less than minimum value"}, + {0x08000000, "General error"}, + {0x08000020, "Data cannot be transferred or stored to the application"}, + {0x08000021, "Data cannot be transferred or stored to the application" + " because of local control"}, + {0x08000022, "Data cannot be transferred or stored to the application" + " because of the present device state"}, + {0x08000023, "Object dictionary dynamic generation fails or no object" + " dictionary is present"}, + {} +}; + +/*****************************************************************************/ + +/** Outputs an SDO abort message. + */ +void ec_canopen_abort_msg( + const ec_slave_t *slave, /**< Slave. */ + uint32_t abort_code /**< Abort code to search for. */ + ) +{ + const ec_code_msg_t *abort_msg; + + for (abort_msg = sdo_abort_messages; abort_msg->code; abort_msg++) { + if (abort_msg->code == abort_code) { + EC_SLAVE_ERR(slave, "SDO abort message 0x%08X: \"%s\".\n", + abort_msg->code, abort_msg->message); + return; + } + } + + EC_SLAVE_ERR(slave, "Unknown SDO abort code 0x%08X.\n", abort_code); +} + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_coe_init( + ec_fsm_coe_t *fsm /**< Finite state machine */ + ) +{ + fsm->state = NULL; + fsm->datagram = NULL; +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_coe_clear( + ec_fsm_coe_t *fsm /**< Finite state machine */ + ) +{ +} + +/*****************************************************************************/ + +/** Starts reading a slaves' SDO dictionary. + */ +void ec_fsm_coe_dictionary( + ec_fsm_coe_t *fsm, /**< Finite state machine */ + ec_slave_t *slave /**< EtherCAT slave */ + ) +{ + fsm->slave = slave; + fsm->state = ec_fsm_coe_dict_start; +} + +/*****************************************************************************/ + +/** Starts to transfer an SDO to/from a slave. + */ +void ec_fsm_coe_transfer( + ec_fsm_coe_t *fsm, /**< State machine. */ + ec_slave_t *slave, /**< EtherCAT slave. */ + ec_sdo_request_t *request /**< SDO request. */ + ) +{ + fsm->slave = slave; + fsm->request = request; + + if (request->dir == EC_DIR_OUTPUT) { + fsm->state = ec_fsm_coe_down_start; + } + else { + fsm->state = ec_fsm_coe_up_start; + } +} + +/*****************************************************************************/ + +/** Executes the current state of the state machine. + * + * \return 1 if the datagram was used, else 0. + */ +int ec_fsm_coe_exec( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + int datagram_used = 0; + + if (fsm->datagram && + (fsm->datagram->state == EC_DATAGRAM_INIT || + fsm->datagram->state == EC_DATAGRAM_QUEUED || + fsm->datagram->state == EC_DATAGRAM_SENT)) { + // datagram not received yet + return datagram_used; + } + + fsm->state(fsm, datagram); + + datagram_used = + fsm->state != ec_fsm_coe_end && fsm->state != ec_fsm_coe_error; + + if (datagram_used) { + fsm->datagram = datagram; + } else { + fsm->datagram = NULL; + } + + return datagram_used; +} + +/*****************************************************************************/ + +/** Returns, if the state machine terminated with success. + * \return non-zero if successful. + */ +int ec_fsm_coe_success( + const ec_fsm_coe_t *fsm /**< Finite state machine */ + ) +{ + return fsm->state == ec_fsm_coe_end; +} + +/*****************************************************************************/ + +/** Check if the received data are a CoE emergency request. + * + * If the check is positive, the emergency request is output. + * + * \return The data were an emergency request. + */ +int ec_fsm_coe_check_emergency( + ec_fsm_coe_t *fsm, /**< Finite state machine */ + const uint8_t *data, /**< CoE mailbox data. */ + size_t size /**< CoE mailbox data size. */ + ) +{ + if (size < 2 || ((EC_READ_U16(data) >> 12) & 0x0F) != 0x01) + return 0; + + if (size < 10) { + EC_SLAVE_WARN(fsm->slave, "Received incomplete CoE Emergency" + " request:\n"); + ec_print_data(data, size); + return 1; + } + + { + ec_slave_config_t *sc = fsm->slave->config; + if (sc) { + ec_coe_emerg_ring_push(&sc->emerg_ring, data + 2); + } + } + + EC_SLAVE_WARN(fsm->slave, "CoE Emergency Request received:\n" + "Error code 0x%04X, Error register 0x%02X, data:\n", + EC_READ_U16(data + 2), EC_READ_U8(data + 4)); + ec_print_data(data + 5, 5); + return 1; +} + +/****************************************************************************** + * CoE dictionary state machine + *****************************************************************************/ + +/** Prepare a dictionary request. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_fsm_coe_prepare_dict( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + uint8_t *data = ec_slave_mbox_prepare_send(slave, datagram, 0x03, 8); + if (IS_ERR(data)) { + return PTR_ERR(data); + } + + EC_WRITE_U16(data, 0x8 << 12); // SDO information + EC_WRITE_U8 (data + 2, 0x01); // Get OD List Request + EC_WRITE_U8 (data + 3, 0x00); + EC_WRITE_U16(data + 4, 0x0000); + EC_WRITE_U16(data + 6, 0x0001); // deliver all SDOs! + + fsm->state = ec_fsm_coe_dict_request; + return 0; +} + +/*****************************************************************************/ + +/** CoE state: DICT START. + */ +void ec_fsm_coe_dict_start( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (!(slave->sii.mailbox_protocols & EC_MBOX_COE)) { + EC_SLAVE_ERR(slave, "Slave does not support CoE!\n"); + fsm->state = ec_fsm_coe_error; + return; + } + + if (slave->sii.has_general && !slave->sii.coe_details.enable_sdo_info) { + EC_SLAVE_ERR(slave, "Slave does not support" + " SDO information service!\n"); + fsm->state = ec_fsm_coe_error; + return; + } + + fsm->retries = EC_FSM_RETRIES; + + if (ec_fsm_coe_prepare_dict(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } +} + +/*****************************************************************************/ + +/** CoE state: DICT REQUEST. + * \todo Timeout behavior + */ +void ec_fsm_coe_dict_request( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + if (ec_fsm_coe_prepare_dict(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE dictionary" + " request datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE dictionary request failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_check; +} + +/*****************************************************************************/ + +/** CoE state: DICT CHECK. + */ +void ec_fsm_coe_dict_check( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE mailbox check datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave,"Reception of CoE mailbox check" + " datagram failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (fsm->datagram->jiffies_received - fsm->jiffies_start) * + 1000 / HZ; + if (diff_ms >= EC_FSM_COE_DICT_TIMEOUT) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Timeout while waiting for" + " SDO dictionary list response.\n"); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_response; +} + +/*****************************************************************************/ + +/** Prepare an object description request. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_fsm_coe_dict_prepare_desc( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + u8 *data = ec_slave_mbox_prepare_send(slave, datagram, 0x03, 8); + if (IS_ERR(data)) { + return PTR_ERR(data); + } + + EC_WRITE_U16(data, 0x8 << 12); // SDO information + EC_WRITE_U8 (data + 2, 0x03); // Get object description request + EC_WRITE_U8 (data + 3, 0x00); + EC_WRITE_U16(data + 4, 0x0000); + EC_WRITE_U16(data + 6, fsm->sdo->index); // SDO index + + fsm->state = ec_fsm_coe_dict_desc_request; + return 0; +} + +/*****************************************************************************/ + +/** + CoE state: DICT RESPONSE. + \todo Timeout behavior +*/ + +void ec_fsm_coe_dict_response( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + uint8_t *data, mbox_prot; + size_t rec_size; + unsigned int sdo_count, i; + uint16_t sdo_index, fragments_left; + ec_sdo_t *sdo; + bool first_segment; + size_t index_list_offset; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE dictionary" + " response datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE dictionary response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + fsm->state = ec_fsm_coe_error; + return; + } + + if (mbox_prot != 0x03) { // CoE + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + fsm->state = ec_fsm_coe_error; + return; + } + + if (ec_fsm_coe_check_emergency(fsm, data, rec_size)) { + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_check; + return; + } + + if (rec_size < 3) { + EC_SLAVE_ERR(slave, "Received corrupted SDO dictionary response" + " (size %zu).\n", rec_size); + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 == 0x8 && // SDO information + (EC_READ_U8(data + 2) & 0x7F) == 0x07) { // error response + EC_SLAVE_ERR(slave, "SDO information error response!\n"); + if (rec_size < 10) { + EC_SLAVE_ERR(slave, "Incomplete SDO information" + " error response:\n"); + ec_print_data(data, rec_size); + } else { + ec_canopen_abort_msg(slave, EC_READ_U32(data + 6)); + } + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 != 0x8 || // SDO information + (EC_READ_U8 (data + 2) & 0x7F) != 0x02) { // Get OD List response + if (fsm->slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Invalid SDO list response!" + " Retrying...\n"); + ec_print_data(data, rec_size); + } + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_check; + return; + } + + first_segment = list_empty(&slave->sdo_dictionary) ? true : false; + index_list_offset = first_segment ? 8 : 6; + + if (rec_size < index_list_offset || rec_size % 2) { + EC_SLAVE_ERR(slave, "Invalid data size %zu!\n", rec_size); + ec_print_data(data, rec_size); + fsm->state = ec_fsm_coe_error; + return; + } + + sdo_count = (rec_size - index_list_offset) / 2; + + for (i = 0; i < sdo_count; i++) { + sdo_index = EC_READ_U16(data + index_list_offset + i * 2); + if (!sdo_index) { + EC_SLAVE_DBG(slave, 1, "SDO dictionary contains index 0x0000.\n"); + continue; + } + + if (!(sdo = (ec_sdo_t *) kmalloc(sizeof(ec_sdo_t), GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate memory for SDO!\n"); + fsm->state = ec_fsm_coe_error; + return; + } + + ec_sdo_init(sdo, slave, sdo_index); + list_add_tail(&sdo->list, &slave->sdo_dictionary); + } + + fragments_left = EC_READ_U16(data + 4); + if (fragments_left) { + EC_SLAVE_DBG(slave, 1, "SDO list fragments left: %u\n", + fragments_left); + } + + if (EC_READ_U8(data + 2) & 0x80 || fragments_left) { + // more messages waiting. check again. + fsm->jiffies_start = fsm->datagram->jiffies_sent; + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_check; + return; + } + + if (list_empty(&slave->sdo_dictionary)) { + // no SDOs in dictionary. finished. + fsm->state = ec_fsm_coe_end; // success + return; + } + + // fetch SDO descriptions + fsm->sdo = list_entry(slave->sdo_dictionary.next, ec_sdo_t, list); + + fsm->retries = EC_FSM_RETRIES; + if (ec_fsm_coe_dict_prepare_desc(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } +} + +/*****************************************************************************/ + +/** + CoE state: DICT DESC REQUEST. + \todo Timeout behavior +*/ + +void ec_fsm_coe_dict_desc_request( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + if (ec_fsm_coe_dict_prepare_desc(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE SDO" + " description request datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE SDO description" + " request failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_desc_check; +} + +/*****************************************************************************/ + +/** + CoE state: DICT DESC CHECK. +*/ + +void ec_fsm_coe_dict_desc_check( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE mailbox check datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE mailbox check" + " datagram failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (fsm->datagram->jiffies_received - fsm->jiffies_start) * + 1000 / HZ; + if (diff_ms >= EC_FSM_COE_DICT_TIMEOUT) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Timeout while waiting for" + " SDO 0x%04x object description response.\n", + fsm->sdo->index); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_desc_response; +} + +/*****************************************************************************/ + +/** Prepare an entry description request. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_fsm_coe_dict_prepare_entry( + ec_fsm_coe_t *fsm, /**< Finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + u8 *data = ec_slave_mbox_prepare_send(slave, datagram, 0x03, 10); + if (IS_ERR(data)) { + return PTR_ERR(data); + } + + EC_WRITE_U16(data, 0x8 << 12); // SDO information + EC_WRITE_U8 (data + 2, 0x05); // Get entry description request + EC_WRITE_U8 (data + 3, 0x00); + EC_WRITE_U16(data + 4, 0x0000); + EC_WRITE_U16(data + 6, fsm->sdo->index); // SDO index + EC_WRITE_U8 (data + 8, fsm->subindex); // SDO subindex + EC_WRITE_U8 (data + 9, 0x01); // value info (access rights only) + + fsm->state = ec_fsm_coe_dict_entry_request; + return 0; +} + +/*****************************************************************************/ + +/** + CoE state: DICT DESC RESPONSE. + \todo Timeout behavior +*/ + +void ec_fsm_coe_dict_desc_response( + ec_fsm_coe_t *fsm, /**< Finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_sdo_t *sdo = fsm->sdo; + uint8_t *data, mbox_prot; + size_t rec_size, name_size; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE SDO description" + " response datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE SDO description" + " response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + fsm->state = ec_fsm_coe_error; + return; + } + + if (mbox_prot != 0x03) { // CoE + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + fsm->state = ec_fsm_coe_error; + return; + } + + if (ec_fsm_coe_check_emergency(fsm, data, rec_size)) { + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_desc_check; + return; + } + + if (rec_size < 3) { + EC_SLAVE_ERR(slave, "Received corrupted SDO description response" + " (size %zu).\n", rec_size); + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 == 0x8 && // SDO information + (EC_READ_U8 (data + 2) & 0x7F) == 0x07) { // error response + EC_SLAVE_ERR(slave, "SDO information error response while" + " fetching SDO 0x%04X!\n", sdo->index); + ec_canopen_abort_msg(slave, EC_READ_U32(data + 6)); + fsm->state = ec_fsm_coe_error; + return; + } + + if (rec_size < 8) { + EC_SLAVE_ERR(slave, "Received corrupted SDO" + " description response (size %zu).\n", rec_size); + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 != 0x8 || // SDO information + (EC_READ_U8 (data + 2) & 0x7F) != 0x04 || // Object desc. response + EC_READ_U16(data + 6) != sdo->index) { // SDO index + if (fsm->slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Invalid object description response while" + " fetching SDO 0x%04X!\n", sdo->index); + ec_print_data(data, rec_size); + } + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_desc_check; + return; + } + + if (rec_size < 12) { + EC_SLAVE_ERR(slave, "Invalid data size!\n"); + ec_print_data(data, rec_size); + fsm->state = ec_fsm_coe_error; + return; + } + + sdo->max_subindex = EC_READ_U8(data + 10); + sdo->object_code = EC_READ_U8(data + 11); + + name_size = rec_size - 12; + if (name_size) { + if (!(sdo->name = kmalloc(name_size + 1, GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate SDO name!\n"); + fsm->state = ec_fsm_coe_error; + return; + } + + memcpy(sdo->name, data + 12, name_size); + sdo->name[name_size] = 0; + } + + if (EC_READ_U8(data + 2) & 0x80) { + EC_SLAVE_ERR(slave, "Fragment follows (not implemented)!\n"); + fsm->state = ec_fsm_coe_error; + return; + } + + // start fetching entries + + fsm->subindex = 0; + fsm->retries = EC_FSM_RETRIES; + + if (ec_fsm_coe_dict_prepare_entry(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } +} + +/*****************************************************************************/ + +/** + CoE state: DICT ENTRY REQUEST. + \todo Timeout behavior +*/ + +void ec_fsm_coe_dict_entry_request( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + if (ec_fsm_coe_dict_prepare_entry(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE SDO entry" + " request datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE SDO entry request failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_entry_check; +} + +/*****************************************************************************/ + +/** + CoE state: DICT ENTRY CHECK. +*/ + +void ec_fsm_coe_dict_entry_check( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_check(slave, datagram); // can not fail + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE mailbox check datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE mailbox check" + " datagram failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (fsm->datagram->jiffies_received - fsm->jiffies_start) * + 1000 / HZ; + if (diff_ms >= EC_FSM_COE_DICT_TIMEOUT) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Timeout while waiting for" + " SDO entry 0x%04x:%x description response.\n", + fsm->sdo->index, fsm->subindex); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_entry_response; +} + +/*****************************************************************************/ + +/** + CoE state: DICT ENTRY RESPONSE. + \todo Timeout behavior +*/ + +void ec_fsm_coe_dict_entry_response( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_sdo_t *sdo = fsm->sdo; + uint8_t *data, mbox_prot; + size_t rec_size, data_size; + ec_sdo_entry_t *entry; + u16 word; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE SDO" + " description response datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE SDO description" + " response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + fsm->state = ec_fsm_coe_error; + return; + } + + if (mbox_prot != 0x03) { // CoE + EC_SLAVE_ERR(slave, "Received mailbox protocol" + " 0x%02X as response.\n", mbox_prot); + fsm->state = ec_fsm_coe_error; + return; + } + + if (ec_fsm_coe_check_emergency(fsm, data, rec_size)) { + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_entry_check; + return; + } + + if (rec_size < 3) { + EC_SLAVE_ERR(slave, "Received corrupted SDO entry" + " description response (size %zu).\n", rec_size); + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 == 0x8 && // SDO information + (EC_READ_U8 (data + 2) & 0x7F) == 0x07) { // error response + EC_SLAVE_WARN(slave, "SDO information error response while" + " fetching SDO entry 0x%04X:%02X!\n", + sdo->index, fsm->subindex); + ec_canopen_abort_msg(slave, EC_READ_U32(data + 6)); + + /* There may be gaps in the subindices, so try to continue with next + * subindex. */ + + } else { + + if (rec_size < 9) { + EC_SLAVE_ERR(slave, "Received corrupted SDO entry" + " description response (size %zu).\n", rec_size); + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 != 0x8 || // SDO information + (EC_READ_U8(data + 2) & 0x7F) != 0x06 || // Entry desc. response + EC_READ_U16(data + 6) != sdo->index || // SDO index + EC_READ_U8(data + 8) != fsm->subindex) { // SDO subindex + if (fsm->slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Invalid entry description response" + " while fetching SDO entry 0x%04X:%02X!\n", + sdo->index, fsm->subindex); + ec_print_data(data, rec_size); + } + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_dict_entry_check; + return; + } + + if (rec_size < 16) { + EC_SLAVE_ERR(slave, "Invalid data size %zu!\n", rec_size); + ec_print_data(data, rec_size); + fsm->state = ec_fsm_coe_error; + return; + } + + data_size = rec_size - 16; + + if (!(entry = (ec_sdo_entry_t *) + kmalloc(sizeof(ec_sdo_entry_t), GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate entry!\n"); + fsm->state = ec_fsm_coe_error; + return; + } + + ec_sdo_entry_init(entry, sdo, fsm->subindex); + entry->data_type = EC_READ_U16(data + 10); + entry->bit_length = EC_READ_U16(data + 12); + + // read access rights + word = EC_READ_U16(data + 14); + entry->read_access[EC_SDO_ENTRY_ACCESS_PREOP] = word & 0x0001; + entry->read_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = + (word >> 1) & 0x0001; + entry->read_access[EC_SDO_ENTRY_ACCESS_OP] = (word >> 2) & 0x0001; + entry->write_access[EC_SDO_ENTRY_ACCESS_PREOP] = (word >> 3) & 0x0001; + entry->write_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = + (word >> 4) & 0x0001; + entry->write_access[EC_SDO_ENTRY_ACCESS_OP] = (word >> 5) & 0x0001; + + if (data_size) { + uint8_t *desc; + if (!(desc = kmalloc(data_size + 1, GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate SDO entry name!\n"); + fsm->state = ec_fsm_coe_error; + return; + } + memcpy(desc, data + 16, data_size); + desc[data_size] = 0; + entry->description = desc; + } + + list_add_tail(&entry->list, &sdo->entries); + } + + if (fsm->subindex < sdo->max_subindex) { + + fsm->subindex++; + fsm->retries = EC_FSM_RETRIES; + + if (ec_fsm_coe_dict_prepare_entry(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + + return; + } + + // another SDO description to fetch? + if (fsm->sdo->list.next != &slave->sdo_dictionary) { + + fsm->sdo = list_entry(fsm->sdo->list.next, ec_sdo_t, list); + fsm->retries = EC_FSM_RETRIES; + + if (ec_fsm_coe_dict_prepare_desc(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + + return; + } + + fsm->state = ec_fsm_coe_end; +} + +/****************************************************************************** + * CoE state machine + *****************************************************************************/ + +/** Prepare a donwnload request. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_fsm_coe_prepare_down_start( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + u8 *data; + ec_slave_t *slave = fsm->slave; + ec_sdo_request_t *request = fsm->request; + uint8_t data_set_size; + + if (request->data_size <= 4) { // use expedited transfer type + data = ec_slave_mbox_prepare_send(slave, datagram, 0x03, + EC_COE_DOWN_REQ_HEADER_SIZE); + if (IS_ERR(data)) { + request->errno = PTR_ERR(data); + return PTR_ERR(data); + } + + fsm->remaining = 0; + + data_set_size = 4 - request->data_size; + + EC_WRITE_U16(data, 0x2 << 12); // SDO request + EC_WRITE_U8 (data + 2, (0x3 // size specified, expedited + | data_set_size << 2 + | ((request->complete_access ? 1 : 0) << 4) + | 0x1 << 5)); // Download request + EC_WRITE_U16(data + 3, request->index); + EC_WRITE_U8 (data + 5, + request->complete_access ? 0x00 : request->subindex); + memcpy(data + 6, request->data, request->data_size); + memset(data + 6 + request->data_size, 0x00, 4 - request->data_size); + + if (slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Expedited download request:\n"); + ec_print_data(data, EC_COE_DOWN_REQ_HEADER_SIZE); + } + } + else { // request->data_size > 4, use normal transfer type + size_t data_size, + max_data_size = + slave->configured_rx_mailbox_size - EC_MBOX_HEADER_SIZE, + required_data_size = + EC_COE_DOWN_REQ_HEADER_SIZE + request->data_size; + + if (max_data_size < required_data_size) { + // segmenting needed + data_size = max_data_size; + } else { + data_size = required_data_size; + } + + data = ec_slave_mbox_prepare_send(slave, datagram, 0x03, + data_size); + if (IS_ERR(data)) { + request->errno = PTR_ERR(data); + return PTR_ERR(data); + } + + fsm->offset = 0; + fsm->remaining = request->data_size; + + EC_WRITE_U16(data, 0x2 << 12); // SDO request + EC_WRITE_U8(data + 2, + 0x1 // size indicator, normal + | ((request->complete_access ? 1 : 0) << 4) + | 0x1 << 5); // Download request + EC_WRITE_U16(data + 3, request->index); + EC_WRITE_U8 (data + 5, + request->complete_access ? 0x00 : request->subindex); + EC_WRITE_U32(data + 6, request->data_size); + + if (data_size > EC_COE_DOWN_REQ_HEADER_SIZE) { + size_t segment_size = data_size - EC_COE_DOWN_REQ_HEADER_SIZE; + memcpy(data + EC_COE_DOWN_REQ_HEADER_SIZE, + request->data, segment_size); + fsm->offset += segment_size; + fsm->remaining -= segment_size; + } + + if (slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Normal download request:\n"); + ec_print_data(data, data_size); + } + } + + fsm->state = ec_fsm_coe_down_request; + return 0; +} + +/****************************************************************************/ + +/** CoE state: DOWN START. + */ +void ec_fsm_coe_down_start( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_sdo_request_t *request = fsm->request; + + if (fsm->slave->master->debug_level) { + char subidxstr[10]; + if (request->complete_access) { + subidxstr[0] = 0x00; + } else { + sprintf(subidxstr, ":%02X", request->subindex); + } + EC_SLAVE_DBG(slave, 1, "Downloading SDO 0x%04X%s.\n", + request->index, subidxstr); + ec_print_data(request->data, request->data_size); + } + + if (!(slave->sii.mailbox_protocols & EC_MBOX_COE)) { + EC_SLAVE_ERR(slave, "Slave does not support CoE!\n"); + request->errno = EPROTONOSUPPORT; + fsm->state = ec_fsm_coe_error; + return; + } + + if (slave->configured_rx_mailbox_size < + EC_MBOX_HEADER_SIZE + EC_COE_DOWN_REQ_HEADER_SIZE) { + EC_SLAVE_ERR(slave, "Mailbox too small!\n"); + request->errno = EOVERFLOW; + fsm->state = ec_fsm_coe_error; + return; + } + + + fsm->request->jiffies_sent = jiffies; + fsm->retries = EC_FSM_RETRIES; + + if (ec_fsm_coe_prepare_down_start(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } +} + +/*****************************************************************************/ + +/** + CoE state: DOWN REQUEST. + \todo Timeout behavior +*/ + +void ec_fsm_coe_down_request( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + unsigned long diff_ms; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + if (ec_fsm_coe_prepare_down_start(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE download" + " request datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + diff_ms = (jiffies - fsm->request->jiffies_sent) * 1000 / HZ; + + if (fsm->datagram->working_counter != 1) { + if (!fsm->datagram->working_counter) { + if (diff_ms < fsm->request->response_timeout) { +#if DEBUG_RETRIES + EC_SLAVE_DBG(slave, 1, "Slave did not respond to SDO" + " download request. Retrying after %lu ms...\n", + diff_ms); +#endif + // no response; send request datagram again + if (ec_fsm_coe_prepare_down_start(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + return; + } + } + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE download request" + " for SDO 0x%04x:%x failed with timeout after %lu ms: ", + fsm->request->index, fsm->request->subindex, diff_ms); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + +#if DEBUG_LONG + if (diff_ms > 200) { + EC_SLAVE_WARN(slave, "SDO 0x%04x:%x download took %lu ms.\n", + fsm->request->index, fsm->request->subindex, diff_ms); + } +#endif + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_down_check; +} + +/*****************************************************************************/ + +/** CoE state: DOWN CHECK. + */ +void ec_fsm_coe_down_check( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE mailbox check" + " datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE mailbox check" + " datagram failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (fsm->datagram->jiffies_received - fsm->jiffies_start) * + 1000 / HZ; + if (diff_ms >= fsm->request->response_timeout) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Timeout after %lu ms while waiting" + " for SDO 0x%04x:%x download response.\n", diff_ms, + fsm->request->index, fsm->request->subindex); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_down_response; +} + +/*****************************************************************************/ + +/** Prepare a download segment request. + */ +void ec_fsm_coe_down_prepare_segment_request( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_sdo_request_t *request = fsm->request; + size_t max_segment_size = + slave->configured_rx_mailbox_size + - EC_MBOX_HEADER_SIZE + - EC_COE_DOWN_SEG_REQ_HEADER_SIZE; + size_t data_size; + uint8_t last_segment, seg_data_size, *data; + + if (fsm->remaining > max_segment_size) { + fsm->segment_size = max_segment_size; + last_segment = 0; + } else { + fsm->segment_size = fsm->remaining; + last_segment = 1; + } + + if (fsm->segment_size > EC_COE_DOWN_SEG_MIN_DATA_SIZE) { + seg_data_size = 0x00; + data_size = EC_COE_DOWN_SEG_REQ_HEADER_SIZE + fsm->segment_size; + } else { + seg_data_size = EC_COE_DOWN_SEG_MIN_DATA_SIZE - fsm->segment_size; + data_size = EC_COE_DOWN_SEG_REQ_HEADER_SIZE + + EC_COE_DOWN_SEG_MIN_DATA_SIZE; + } + + data = ec_slave_mbox_prepare_send(slave, datagram, 0x03, + data_size); + if (IS_ERR(data)) { + request->errno = PTR_ERR(data); + fsm->state = ec_fsm_coe_error; + return; + } + + EC_WRITE_U16(data, 0x2 << 12); // SDO request + EC_WRITE_U8(data + 2, (last_segment ? 1 : 0) + | (seg_data_size << 1) + | (fsm->toggle << 4) + | (0x00 << 5)); // Download segment request + memcpy(data + EC_COE_DOWN_SEG_REQ_HEADER_SIZE, + request->data + fsm->offset, fsm->segment_size); + if (fsm->segment_size < EC_COE_DOWN_SEG_MIN_DATA_SIZE) { + memset(data + EC_COE_DOWN_SEG_REQ_HEADER_SIZE + fsm->segment_size, + 0x00, EC_COE_DOWN_SEG_MIN_DATA_SIZE - fsm->segment_size); + } + + if (slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Download segment request:\n"); + ec_print_data(data, data_size); + } + + fsm->state = ec_fsm_coe_down_seg_check; +} + +/*****************************************************************************/ + +/** + CoE state: DOWN RESPONSE. + \todo Timeout behavior +*/ + +void ec_fsm_coe_down_response( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + uint8_t *data, mbox_prot; + size_t rec_size; + ec_sdo_request_t *request = fsm->request; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE download" + " response datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE download response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + request->errno = PTR_ERR(data); + fsm->state = ec_fsm_coe_error; + return; + } + + if (mbox_prot != 0x03) { // CoE + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + return; + } + + if (ec_fsm_coe_check_emergency(fsm, data, rec_size)) { + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_down_check; + return; + } + + if (slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Download response:\n"); + ec_print_data(data, rec_size); + } + + if (rec_size < 6) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Received data are too small (%zu bytes):\n", + rec_size); + ec_print_data(data, rec_size); + return; + } + + if (EC_READ_U16(data) >> 12 == 0x2 && // SDO request + EC_READ_U8 (data + 2) >> 5 == 0x4) { // abort SDO transfer request + char subidxstr[10]; + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + if (request->complete_access) { + subidxstr[0] = 0x00; + } else { + sprintf(subidxstr, ":%02X", request->subindex); + } + EC_SLAVE_ERR(slave, "SDO download 0x%04X%s (%zu bytes) aborted.\n", + request->index, subidxstr, request->data_size); + if (rec_size < 10) { + EC_SLAVE_ERR(slave, "Incomplete abort command:\n"); + ec_print_data(data, rec_size); + } else { + fsm->request->abort_code = EC_READ_U32(data + 6); + ec_canopen_abort_msg(slave, fsm->request->abort_code); + } + return; + } + + if (EC_READ_U16(data) >> 12 != 0x3 || // SDO response + EC_READ_U8 (data + 2) >> 5 != 0x3 || // Download response + EC_READ_U16(data + 3) != request->index || // index + EC_READ_U8 (data + 5) != request->subindex) { // subindex + if (slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Invalid SDO download response!" + " Retrying...\n"); + ec_print_data(data, rec_size); + } + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_down_check; + return; + } + + if (fsm->remaining) { // more segments to download + fsm->toggle = 0; + ec_fsm_coe_down_prepare_segment_request(fsm, datagram); + } else { + fsm->state = ec_fsm_coe_end; // success + } +} + +/*****************************************************************************/ + +/** + CoE state: DOWN SEG CHECK. +*/ + +void ec_fsm_coe_down_seg_check( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE mailbox check datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE mailbox segment check" + " datagram failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (fsm->datagram->jiffies_received - fsm->jiffies_start) * + 1000 / HZ; + if (diff_ms >= fsm->request->response_timeout) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Timeout while waiting for SDO download" + " segment response.\n"); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_down_seg_response; +} + +/*****************************************************************************/ + +/** + CoE state: DOWN SEG RESPONSE. + \todo Timeout behavior +*/ + +void ec_fsm_coe_down_seg_response( + ec_fsm_coe_t *fsm, /**< Finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + uint8_t *data, mbox_prot; + size_t rec_size; + ec_sdo_request_t *request = fsm->request; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE download response" + " datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE download response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + request->errno = PTR_ERR(data); + fsm->state = ec_fsm_coe_error; + return; + } + + if (mbox_prot != 0x03) { // CoE + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + return; + } + + if (ec_fsm_coe_check_emergency(fsm, data, rec_size)) { + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_down_check; + return; + } + + if (slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Download response:\n"); + ec_print_data(data, rec_size); + } + + if (rec_size < 6) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Received data are too small (%zu bytes):\n", + rec_size); + ec_print_data(data, rec_size); + return; + } + + if (EC_READ_U16(data) >> 12 == 0x2 && // SDO request + EC_READ_U8 (data + 2) >> 5 == 0x4) { // abort SDO transfer request + char subidxstr[10]; + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + if (request->complete_access) { + subidxstr[0] = 0x00; + } else { + sprintf(subidxstr, ":%02X", request->subindex); + } + EC_SLAVE_ERR(slave, "SDO download 0x%04X%s (%zu bytes) aborted.\n", + request->index, subidxstr, request->data_size); + if (rec_size < 10) { + EC_SLAVE_ERR(slave, "Incomplete abort command:\n"); + ec_print_data(data, rec_size); + } else { + fsm->request->abort_code = EC_READ_U32(data + 6); + ec_canopen_abort_msg(slave, fsm->request->abort_code); + } + return; + } + + if (EC_READ_U16(data) >> 12 != 0x3 || + ((EC_READ_U8(data + 2) >> 5) != 0x01)) { // segment response + if (slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Invalid SDO download response!" + " Retrying...\n"); + ec_print_data(data, rec_size); + } + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_down_seg_check; + return; + } + + if (((EC_READ_U8(data + 2) >> 4) & 0x01) != fsm->toggle) { + EC_SLAVE_ERR(slave, "Invalid toggle received during" + " segmented download:\n"); + ec_print_data(data, rec_size); + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + return; + } + + fsm->offset += fsm->segment_size; + fsm->remaining -= fsm->segment_size; + + if (fsm->remaining) { // more segments to download + fsm->toggle = !fsm->toggle; + ec_fsm_coe_down_prepare_segment_request(fsm, datagram); + } else { + fsm->state = ec_fsm_coe_end; // success + } +} + +/*****************************************************************************/ + +/** Prepare an upload request. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_fsm_coe_prepare_up( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_sdo_request_t *request = fsm->request; + ec_master_t *master = slave->master; + + u8 *data = ec_slave_mbox_prepare_send(slave, datagram, 0x03, 10); + if (IS_ERR(data)) { + request->errno = PTR_ERR(data); + return PTR_ERR(data); + } + + EC_WRITE_U16(data, 0x2 << 12); // SDO request + EC_WRITE_U8 (data + 2, 0x2 << 5); // initiate upload request + EC_WRITE_U16(data + 3, request->index); + EC_WRITE_U8 (data + 5, request->subindex); + memset(data + 6, 0x00, 4); + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Upload request:\n"); + ec_print_data(data, 10); + } + + fsm->state = ec_fsm_coe_up_request; + return 0; +} + +/*****************************************************************************/ + +/** + CoE state: UP START. +*/ + +void ec_fsm_coe_up_start( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_sdo_request_t *request = fsm->request; + + EC_SLAVE_DBG(slave, 1, "Uploading SDO 0x%04X:%02X.\n", + request->index, request->subindex); + + if (!(slave->sii.mailbox_protocols & EC_MBOX_COE)) { + EC_SLAVE_ERR(slave, "Slave does not support CoE!\n"); + request->errno = EPROTONOSUPPORT; + fsm->state = ec_fsm_coe_error; + return; + } + + fsm->retries = EC_FSM_RETRIES; + fsm->request->jiffies_sent = jiffies; + + if (ec_fsm_coe_prepare_up(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } +} + +/*****************************************************************************/ +/** + CoE state: UP REQUEST. + \todo Timeout behavior +*/ + +void ec_fsm_coe_up_request( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + unsigned long diff_ms; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + if (ec_fsm_coe_prepare_up(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE upload request: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + diff_ms = (jiffies - fsm->request->jiffies_sent) * 1000 / HZ; + + if (fsm->datagram->working_counter != 1) { + if (!fsm->datagram->working_counter) { + if (diff_ms < fsm->request->response_timeout) { +#if DEBUG_RETRIES + EC_SLAVE_DBG(slave, 1, "Slave did not respond to" + " SDO upload request. Retrying after %lu ms...\n", + diff_ms); +#endif + // no response; send request datagram again + if (ec_fsm_coe_prepare_up(fsm, datagram)) { + fsm->state = ec_fsm_coe_error; + } + return; + } + } + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE upload request for" + " SDO 0x%04x:%x failed with timeout after %lu ms: ", + fsm->request->index, fsm->request->subindex, diff_ms); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + +#if DEBUG_LONG + if (diff_ms > 200) { + EC_SLAVE_WARN(slave, "SDO 0x%04x:%x upload took %lu ms.\n", + fsm->request->index, fsm->request->subindex, diff_ms); + } +#endif + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_check; +} + +/*****************************************************************************/ + +/** + CoE state: UP CHECK. +*/ + +void ec_fsm_coe_up_check( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE mailbox check datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE mailbox check" + " datagram failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (fsm->datagram->jiffies_received - fsm->jiffies_start) * + 1000 / HZ; + if (diff_ms >= fsm->request->response_timeout) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Timeout after %lu ms while waiting for" + " SDO 0x%04x:%x upload response.\n", diff_ms, + fsm->request->index, fsm->request->subindex); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_response; +} + +/*****************************************************************************/ + +/** Prepare an SDO upload segment request. + */ +void ec_fsm_coe_up_prepare_segment_request( + ec_fsm_coe_t *fsm, /**< Finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + uint8_t *data = + ec_slave_mbox_prepare_send(fsm->slave, datagram, 0x03, 10); + if (IS_ERR(data)) { + fsm->request->errno = PTR_ERR(data); + fsm->state = ec_fsm_coe_error; + return; + } + + EC_WRITE_U16(data, 0x2 << 12); // SDO request + EC_WRITE_U8 (data + 2, (fsm->toggle << 4 // toggle + | 0x3 << 5)); // upload segment request + memset(data + 3, 0x00, 7); + + if (fsm->slave->master->debug_level) { + EC_SLAVE_DBG(fsm->slave, 1, "Upload segment request:\n"); + ec_print_data(data, 10); + } +} + +/*****************************************************************************/ + +/** + CoE state: UP RESPONSE. + \todo Timeout behavior +*/ + +void ec_fsm_coe_up_response( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_master_t *master = slave->master; + uint16_t rec_index; + uint8_t *data, mbox_prot, rec_subindex; + size_t rec_size, data_size; + ec_sdo_request_t *request = fsm->request; + unsigned int expedited, size_specified; + int ret; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE upload response" + " datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE upload response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + request->errno = PTR_ERR(data); + fsm->state = ec_fsm_coe_error; + return; + } + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Upload response:\n"); + ec_print_data(data, rec_size); + } + + if (mbox_prot != 0x03) { // CoE + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_WARN(slave, "Received mailbox protocol 0x%02X" + " as response.\n", mbox_prot); + return; + } + + if (ec_fsm_coe_check_emergency(fsm, data, rec_size)) { + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_check; + return; + } + + if (rec_size < 6) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Received currupted SDO upload response" + " (%zu bytes)!\n", rec_size); + ec_print_data(data, rec_size); + return; + } + + if (EC_READ_U16(data) >> 12 == 0x2 && // SDO request + EC_READ_U8(data + 2) >> 5 == 0x4) { // abort SDO transfer request + EC_SLAVE_ERR(slave, "SDO upload 0x%04X:%02X aborted.\n", + request->index, request->subindex); + if (rec_size >= 10) { + request->abort_code = EC_READ_U32(data + 6); + ec_canopen_abort_msg(slave, request->abort_code); + } else { + EC_SLAVE_ERR(slave, "No abort message.\n"); + } + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 != 0x3 || // SDO response + EC_READ_U8(data + 2) >> 5 != 0x2) { // upload response + EC_SLAVE_ERR(slave, "Received unknown response while" + " uploading SDO 0x%04X:%02X.\n", + request->index, request->subindex); + ec_print_data(data, rec_size); + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + return; + } + + rec_index = EC_READ_U16(data + 3); + rec_subindex = EC_READ_U8(data + 5); + + if (rec_index != request->index || rec_subindex != request->subindex) { + EC_SLAVE_ERR(slave, "Received upload response for wrong SDO" + " (0x%04X:%02X, requested: 0x%04X:%02X).\n", + rec_index, rec_subindex, request->index, request->subindex); + ec_print_data(data, rec_size); + + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_check; + return; + } + + // normal or expedited? + expedited = EC_READ_U8(data + 2) & 0x02; + + if (expedited) { + size_specified = EC_READ_U8(data + 2) & 0x01; + if (size_specified) { + fsm->complete_size = 4 - ((EC_READ_U8(data + 2) & 0x0C) >> 2); + } else { + fsm->complete_size = 4; + } + + if (rec_size < 6 + fsm->complete_size) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Received corrupted SDO expedited upload" + " response (only %zu bytes)!\n", rec_size); + ec_print_data(data, rec_size); + return; + } + + ret = ec_sdo_request_copy_data(request, data + 6, fsm->complete_size); + if (ret) { + request->errno = -ret; + fsm->state = ec_fsm_coe_error; + return; + } + } else { // normal + if (rec_size < 10) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Received currupted SDO normal upload" + " response (only %zu bytes)!\n", rec_size); + ec_print_data(data, rec_size); + return; + } + + data_size = rec_size - 10; + fsm->complete_size = EC_READ_U32(data + 6); + + if (!fsm->complete_size) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "No complete size supplied!\n"); + ec_print_data(data, rec_size); + return; + } + + ret = ec_sdo_request_alloc(request, fsm->complete_size); + if (ret) { + request->errno = -ret; + fsm->state = ec_fsm_coe_error; + return; + } + + ret = ec_sdo_request_copy_data(request, data + 10, data_size); + if (ret) { + request->errno = -ret; + fsm->state = ec_fsm_coe_error; + return; + } + + fsm->toggle = 0; + + if (data_size < fsm->complete_size) { + EC_SLAVE_DBG(slave, 1, "SDO data incomplete (%zu / %u)." + " Segmenting...\n", data_size, fsm->complete_size); + ec_fsm_coe_up_prepare_segment_request(fsm, datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_seg_request; + return; + } + } + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Uploaded data:\n"); + ec_print_data(request->data, request->data_size); + } + + fsm->state = ec_fsm_coe_end; // success +} + +/*****************************************************************************/ + +/** + CoE state: UP REQUEST. + \todo Timeout behavior +*/ + +void ec_fsm_coe_up_seg_request( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_fsm_coe_up_prepare_segment_request(fsm, datagram); + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE upload segment" + " request datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE upload segment" + " request failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_seg_check; +} + +/*****************************************************************************/ + +/** + CoE state: UP CHECK. +*/ + +void ec_fsm_coe_up_seg_check( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE mailbox check" + " datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE mailbox check datagram" + " failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (fsm->datagram->jiffies_received - fsm->jiffies_start) * + 1000 / HZ; + if (diff_ms >= fsm->request->response_timeout) { + fsm->request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Timeout while waiting for SDO upload" + " segment response.\n"); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_seg_response; +} + +/*****************************************************************************/ + +/** + CoE state: UP RESPONSE. + \todo Timeout behavior +*/ + +void ec_fsm_coe_up_seg_response( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_master_t *master = slave->master; + uint8_t *data, mbox_prot; + size_t rec_size, data_size; + ec_sdo_request_t *request = fsm->request; + unsigned int last_segment; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Failed to receive CoE upload segment" + " response datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + EC_SLAVE_ERR(slave, "Reception of CoE upload segment" + " response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + request->errno = PTR_ERR(data); + fsm->state = ec_fsm_coe_error; + return; + } + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Upload segment response:\n"); + ec_print_data(data, rec_size); + } + + if (mbox_prot != 0x03) { // CoE + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + return; + } + + if (ec_fsm_coe_check_emergency(fsm, data, rec_size)) { + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_seg_check; + return; + } + + if (rec_size < 10) { + EC_SLAVE_ERR(slave, "Received currupted SDO upload" + " segment response!\n"); + ec_print_data(data, rec_size); + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 == 0x2 && // SDO request + EC_READ_U8 (data + 2) >> 5 == 0x4) { // abort SDO transfer request + EC_SLAVE_ERR(slave, "SDO upload 0x%04X:%02X aborted.\n", + request->index, request->subindex); + request->abort_code = EC_READ_U32(data + 6); + ec_canopen_abort_msg(slave, request->abort_code); + request->errno = EIO; + fsm->state = ec_fsm_coe_error; + return; + } + + if (EC_READ_U16(data) >> 12 != 0x3 || // SDO response + EC_READ_U8 (data + 2) >> 5 != 0x0) { // upload segment response + if (fsm->slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Invalid SDO upload segment response!\n"); + ec_print_data(data, rec_size); + } + // check for CoE response again + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_seg_check; + return; + } + + data_size = rec_size - 3; /* Header of segment upload is smaller than + normal upload */ + if (rec_size == 10) { + uint8_t seg_size = (EC_READ_U8(data + 2) & 0xE) >> 1; + data_size -= seg_size; + } + + if (request->data_size + data_size > fsm->complete_size) { + EC_SLAVE_ERR(slave, "SDO upload 0x%04X:%02X failed: Fragment" + " exceeding complete size!\n", + request->index, request->subindex); + request->errno = EOVERFLOW; + fsm->state = ec_fsm_coe_error; + return; + } + + memcpy(request->data + request->data_size, data + 3, data_size); + request->data_size += data_size; + + last_segment = EC_READ_U8(data + 2) & 0x01; + if (!last_segment) { + fsm->toggle = !fsm->toggle; + ec_fsm_coe_up_prepare_segment_request(fsm, datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_coe_up_seg_request; + return; + } + + if (request->data_size != fsm->complete_size) { + EC_SLAVE_WARN(slave, "SDO upload 0x%04X:%02X: Assembled data" + " size (%zu) does not match complete size (%u)!\n", + request->index, request->subindex, + request->data_size, fsm->complete_size); + } + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Uploaded data:\n"); + ec_print_data(request->data, request->data_size); + } + + fsm->state = ec_fsm_coe_end; // success +} + +/*****************************************************************************/ + +/** + State: ERROR. +*/ + +void ec_fsm_coe_error( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +} + +/*****************************************************************************/ + +/** + State: END. +*/ + +void ec_fsm_coe_end( + ec_fsm_coe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_coe.h b/net/ethercat/master/fsm_coe.h new file mode 100644 index 000000000000..7b565040fd16 --- /dev/null +++ b/net/ethercat/master/fsm_coe.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT CoE state machines. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_COE_H__ +#define __EC_FSM_COE_H__ + +#include "globals.h" +#include "datagram.h" +#include "slave.h" +#include "sdo.h" +#include "sdo_request.h" + +/*****************************************************************************/ + +typedef struct ec_fsm_coe ec_fsm_coe_t; /**< \see ec_fsm_coe */ + +/** Finite state machines for the CANopen over EtherCAT protocol. + */ +struct ec_fsm_coe { + ec_slave_t *slave; /**< slave the FSM runs on */ + unsigned int retries; /**< retries upon datagram timeout */ + + void (*state)(ec_fsm_coe_t *, ec_datagram_t *); /**< CoE state function */ + ec_datagram_t *datagram; /**< Datagram used in last step. */ + unsigned long jiffies_start; /**< CoE timestamp. */ + ec_sdo_t *sdo; /**< current SDO */ + uint8_t subindex; /**< current subindex */ + ec_sdo_request_t *request; /**< SDO request */ + uint32_t complete_size; /**< Used when segmenting. */ + uint8_t toggle; /**< toggle bit for segment commands */ + uint32_t offset; /**< Data offset during segmented download. */ + uint32_t remaining; /**< Remaining bytes during segmented download. */ + size_t segment_size; /**< Current segment size. */ +}; + +/*****************************************************************************/ + +void ec_fsm_coe_init(ec_fsm_coe_t *); +void ec_fsm_coe_clear(ec_fsm_coe_t *); + +void ec_fsm_coe_dictionary(ec_fsm_coe_t *, ec_slave_t *); +void ec_fsm_coe_transfer(ec_fsm_coe_t *, ec_slave_t *, ec_sdo_request_t *); + +int ec_fsm_coe_exec(ec_fsm_coe_t *, ec_datagram_t *); +int ec_fsm_coe_success(const ec_fsm_coe_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_foe.c b/net/ethercat/master/fsm_foe.c new file mode 100644 index 000000000000..2e8896998283 --- /dev/null +++ b/net/ethercat/master/fsm_foe.c @@ -0,0 +1,927 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2008 Olav Zarges, imc Messsysteme GmbH + * 2013 Florian Pose <fp@igh-essen.com> + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT FoE state machines. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "fsm_foe.h" +#include "foe.h" + +/*****************************************************************************/ + +/** Maximum time in ms to wait for responses when reading out the dictionary. + */ +#define EC_FSM_FOE_TIMEOUT 3000 + +/** Mailbox type FoE. + */ +#define EC_MBOX_TYPE_FILEACCESS 0x04 + +/** Size of the FoE header. + */ +#define EC_FOE_HEADER_SIZE 6 +// uint8_t OpCode +// uint8_t reserved +// uint32_t PacketNo, Password, ErrorCode + +//#define DEBUG_FOE + +/*****************************************************************************/ + +/** FoE OpCodes. + */ +enum { + EC_FOE_OPCODE_RRQ = 1, /**< Read request. */ + EC_FOE_OPCODE_WRQ = 2, /**< Write request. */ + EC_FOE_OPCODE_DATA = 3, /**< Data. */ + EC_FOE_OPCODE_ACK = 4, /**< Acknowledge. */ + EC_FOE_OPCODE_ERR = 5, /**< Error. */ + EC_FOE_OPCODE_BUSY = 6 /**< Busy. */ +} ec_foe_opcode_t; + +/*****************************************************************************/ + +int ec_foe_prepare_data_send(ec_fsm_foe_t *, ec_datagram_t *); +int ec_foe_prepare_wrq_send(ec_fsm_foe_t *, ec_datagram_t *); +int ec_foe_prepare_rrq_send(ec_fsm_foe_t *, ec_datagram_t *); +int ec_foe_prepare_send_ack(ec_fsm_foe_t *, ec_datagram_t *); + +void ec_foe_set_tx_error(ec_fsm_foe_t *, uint32_t); +void ec_foe_set_rx_error(ec_fsm_foe_t *, uint32_t); + +void ec_fsm_foe_end(ec_fsm_foe_t *, ec_datagram_t *); +void ec_fsm_foe_error(ec_fsm_foe_t *, ec_datagram_t *); + +void ec_fsm_foe_state_wrq_sent(ec_fsm_foe_t *, ec_datagram_t *); +void ec_fsm_foe_state_rrq_sent(ec_fsm_foe_t *, ec_datagram_t *); + +void ec_fsm_foe_state_ack_check(ec_fsm_foe_t *, ec_datagram_t *); +void ec_fsm_foe_state_ack_read(ec_fsm_foe_t *, ec_datagram_t *); + +void ec_fsm_foe_state_data_sent(ec_fsm_foe_t *, ec_datagram_t *); + +void ec_fsm_foe_state_data_check(ec_fsm_foe_t *, ec_datagram_t *); +void ec_fsm_foe_state_data_read(ec_fsm_foe_t *, ec_datagram_t *); +void ec_fsm_foe_state_sent_ack(ec_fsm_foe_t *, ec_datagram_t *); + +void ec_fsm_foe_write_start(ec_fsm_foe_t *, ec_datagram_t *); +void ec_fsm_foe_read_start(ec_fsm_foe_t *, ec_datagram_t *); + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_foe_init( + ec_fsm_foe_t *fsm /**< finite state machine */ + ) +{ + fsm->state = NULL; + fsm->datagram = NULL; +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_foe_clear(ec_fsm_foe_t *fsm /**< finite state machine */) +{ +} + +/*****************************************************************************/ + +/** Executes the current state of the state machine. + * + * \return 1, if the datagram was used, else 0. + */ +int ec_fsm_foe_exec( + ec_fsm_foe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + int datagram_used = 0; + + if (fsm->datagram && + (fsm->datagram->state == EC_DATAGRAM_INIT || + fsm->datagram->state == EC_DATAGRAM_QUEUED || + fsm->datagram->state == EC_DATAGRAM_SENT)) { + // datagram not received yet + return datagram_used; + } + + fsm->state(fsm, datagram); + + datagram_used = + fsm->state != ec_fsm_foe_end && fsm->state != ec_fsm_foe_error; + + if (datagram_used) { + fsm->datagram = datagram; + } else { + fsm->datagram = NULL; + } + + return datagram_used; +} + +/*****************************************************************************/ + +/** Returns, if the state machine terminated with success. + * \return non-zero if successful. + */ +int ec_fsm_foe_success(const ec_fsm_foe_t *fsm /**< Finite state machine */) +{ + return fsm->state == ec_fsm_foe_end; +} + +/*****************************************************************************/ + +/** Prepares an FoE transfer. + */ +void ec_fsm_foe_transfer( + ec_fsm_foe_t *fsm, /**< State machine. */ + ec_slave_t *slave, /**< EtherCAT slave. */ + ec_foe_request_t *request /**< Sdo request. */ + ) +{ + fsm->slave = slave; + fsm->request = request; + + if (request->dir == EC_DIR_OUTPUT) { + fsm->tx_buffer = fsm->request->buffer; + fsm->tx_buffer_size = fsm->request->data_size; + fsm->tx_buffer_offset = 0; + + fsm->tx_filename = fsm->request->file_name; + fsm->tx_filename_len = strlen(fsm->tx_filename); + + fsm->state = ec_fsm_foe_write_start; + } + else { + fsm->rx_buffer = fsm->request->buffer; + fsm->rx_buffer_size = fsm->request->buffer_size; + + fsm->rx_filename = fsm->request->file_name; + fsm->rx_filename_len = strlen(fsm->rx_filename); + + fsm->state = ec_fsm_foe_read_start; + } +} + +/*****************************************************************************/ + +/** State: ERROR. + */ +void ec_fsm_foe_error( + ec_fsm_foe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif +} + +/*****************************************************************************/ + +/** State: END. + */ +void ec_fsm_foe_end( + ec_fsm_foe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif +} + +/*****************************************************************************/ + +/** Sends a file or the next fragment. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_foe_prepare_data_send( + ec_fsm_foe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + size_t remaining_size, current_size; + uint8_t *data; + + remaining_size = fsm->tx_buffer_size - fsm->tx_buffer_offset; + + if (remaining_size < fsm->slave->configured_tx_mailbox_size + - EC_MBOX_HEADER_SIZE - EC_FOE_HEADER_SIZE) { + current_size = remaining_size; + fsm->tx_last_packet = 1; + } else { + current_size = fsm->slave->configured_tx_mailbox_size + - EC_MBOX_HEADER_SIZE - EC_FOE_HEADER_SIZE; + } + + data = ec_slave_mbox_prepare_send(fsm->slave, + datagram, EC_MBOX_TYPE_FILEACCESS, + current_size + EC_FOE_HEADER_SIZE); + if (IS_ERR(data)) { + return -1; + } + + EC_WRITE_U16(data, EC_FOE_OPCODE_DATA); // OpCode = DataBlock req. + EC_WRITE_U32(data + 2, fsm->tx_packet_no); // PacketNo, Password + + memcpy(data + EC_FOE_HEADER_SIZE, + fsm->tx_buffer + fsm->tx_buffer_offset, current_size); + fsm->tx_current_size = current_size; + + return 0; +} + +/*****************************************************************************/ + +/** Prepare a write request (WRQ) with filename + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_foe_prepare_wrq_send( + ec_fsm_foe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + size_t current_size; + uint8_t *data; + + fsm->tx_buffer_offset = 0; + fsm->tx_current_size = 0; + fsm->tx_packet_no = 0; + fsm->tx_last_packet = 0; + + current_size = fsm->tx_filename_len; + + data = ec_slave_mbox_prepare_send(fsm->slave, datagram, + EC_MBOX_TYPE_FILEACCESS, current_size + EC_FOE_HEADER_SIZE); + if (IS_ERR(data)) { + return -1; + } + + EC_WRITE_U16( data, EC_FOE_OPCODE_WRQ); // fsm write request + EC_WRITE_U32( data + 2, fsm->tx_packet_no ); + + memcpy(data + EC_FOE_HEADER_SIZE, fsm->tx_filename, current_size); + + return 0; +} + +/*****************************************************************************/ + +/** Initializes the FoE write state machine. + */ +void ec_fsm_foe_write_start( + ec_fsm_foe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + fsm->tx_buffer_offset = 0; + fsm->tx_current_size = 0; + fsm->tx_packet_no = 0; + fsm->tx_last_packet = 0; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (!(slave->sii.mailbox_protocols & EC_MBOX_FOE)) { + ec_foe_set_tx_error(fsm, FOE_MBOX_PROT_ERROR); + EC_SLAVE_ERR(slave, "Slave does not support FoE!\n"); + return; + } + + if (ec_foe_prepare_wrq_send(fsm, datagram)) { + ec_foe_set_tx_error(fsm, FOE_PROT_ERROR); + return; + } + + fsm->state = ec_fsm_foe_state_wrq_sent; +} + +/*****************************************************************************/ + +/** Check for acknowledge. + */ +void ec_fsm_foe_state_ack_check( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + ec_foe_set_rx_error(fsm, FOE_RECEIVE_ERROR); + EC_SLAVE_ERR(slave, "Failed to receive FoE mailbox check datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + ec_foe_set_rx_error(fsm, FOE_WC_ERROR); + EC_SLAVE_ERR(slave, "Reception of FoE mailbox check datagram" + " failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + // slave did not put anything in the mailbox yet + unsigned long diff_ms = (fsm->datagram->jiffies_received - + fsm->jiffies_start) * 1000 / HZ; + if (diff_ms >= EC_FSM_FOE_TIMEOUT) { + ec_foe_set_tx_error(fsm, FOE_TIMEOUT_ERROR); + EC_SLAVE_ERR(slave, "Timeout while waiting for ack response.\n"); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_foe_state_ack_read; +} + +/*****************************************************************************/ + +/** Acknowledge a read operation. + */ +void ec_fsm_foe_state_ack_read( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + uint8_t *data, mbox_prot; + uint8_t opCode; + size_t rec_size; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + ec_foe_set_rx_error(fsm, FOE_RECEIVE_ERROR); + EC_SLAVE_ERR(slave, "Failed to receive FoE ack response datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + ec_foe_set_rx_error(fsm, FOE_WC_ERROR); + EC_SLAVE_ERR(slave, "Reception of FoE ack response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + ec_foe_set_tx_error(fsm, FOE_PROT_ERROR); + return; + } + + if (mbox_prot != EC_MBOX_TYPE_FILEACCESS) { // FoE + ec_foe_set_tx_error(fsm, FOE_MBOX_PROT_ERROR); + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + return; + } + + opCode = EC_READ_U8(data); + + if (opCode == EC_FOE_OPCODE_BUSY) { + // slave not ready + if (ec_foe_prepare_data_send(fsm, datagram)) { + ec_foe_set_tx_error(fsm, FOE_PROT_ERROR); + EC_SLAVE_ERR(slave, "Slave is busy.\n"); + return; + } + fsm->state = ec_fsm_foe_state_data_sent; + return; + } + + if (opCode == EC_FOE_OPCODE_ACK) { + fsm->tx_packet_no++; + fsm->tx_buffer_offset += fsm->tx_current_size; + + if (fsm->tx_last_packet) { + fsm->state = ec_fsm_foe_end; + return; + } + + if (ec_foe_prepare_data_send(fsm, datagram)) { + ec_foe_set_tx_error(fsm, FOE_PROT_ERROR); + return; + } + fsm->state = ec_fsm_foe_state_data_sent; + return; + } + ec_foe_set_tx_error(fsm, FOE_ACK_ERROR); +} + +/*****************************************************************************/ + +/** State: WRQ SENT. + * + * Checks is the previous transmit datagram succeded and sends the next + * fragment, if necessary. + */ +void ec_fsm_foe_state_wrq_sent( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + ec_foe_set_rx_error(fsm, FOE_RECEIVE_ERROR); + EC_SLAVE_ERR(slave, "Failed to send FoE WRQ: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + // slave did not put anything in the mailbox yet + ec_foe_set_rx_error(fsm, FOE_WC_ERROR); + EC_SLAVE_ERR(slave, "Reception of FoE WRQ failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_foe_state_ack_check; +} + +/*****************************************************************************/ + +/** State: WRQ SENT. + * + * Checks is the previous transmit datagram succeded and sends the next + * fragment, if necessary. + */ +void ec_fsm_foe_state_data_sent( + ec_fsm_foe_t *fsm, /**< Foe statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + ec_foe_set_tx_error(fsm, FOE_RECEIVE_ERROR); + EC_SLAVE_ERR(slave, "Failed to receive FoE ack response datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + ec_foe_set_tx_error(fsm, FOE_WC_ERROR); + EC_SLAVE_ERR(slave, "Reception of FoE data send failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); + fsm->jiffies_start = jiffies; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_foe_state_ack_check; +} + +/*****************************************************************************/ + +/** Prepare a read request (RRQ) with filename + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_foe_prepare_rrq_send( + ec_fsm_foe_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + size_t current_size; + uint8_t *data; + + current_size = fsm->rx_filename_len; + + data = ec_slave_mbox_prepare_send(fsm->slave, datagram, + EC_MBOX_TYPE_FILEACCESS, current_size + EC_FOE_HEADER_SIZE); + if (IS_ERR(data)) { + return -1; + } + + EC_WRITE_U16(data, EC_FOE_OPCODE_RRQ); // fsm read request + EC_WRITE_U32(data + 2, 0x00000000); // no passwd + memcpy(data + EC_FOE_HEADER_SIZE, fsm->rx_filename, current_size); + + if (fsm->slave->master->debug_level) { + EC_SLAVE_DBG(fsm->slave, 1, "FoE Read Request:\n"); + ec_print_data(data, current_size + EC_FOE_HEADER_SIZE); + } + + return 0; +} + +/*****************************************************************************/ + +/** Prepare to send an acknowledge. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_foe_prepare_send_ack( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + uint8_t *data; + + data = ec_slave_mbox_prepare_send(fsm->slave, datagram, + EC_MBOX_TYPE_FILEACCESS, EC_FOE_HEADER_SIZE); + if (IS_ERR(data)) { + return -1; + } + + EC_WRITE_U16(data, EC_FOE_OPCODE_ACK); + EC_WRITE_U32(data + 2, fsm->rx_expected_packet_no); + + return 0; +} + +/*****************************************************************************/ + +/** State: RRQ SENT. + * + * Checks is the previous transmit datagram succeeded and sends the next + * fragment, if necessary. + */ +void ec_fsm_foe_state_rrq_sent( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + ec_foe_set_rx_error(fsm, FOE_RECEIVE_ERROR); + EC_SLAVE_ERR(slave, "Failed to send FoE RRQ: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + // slave did not put anything in the mailbox yet + ec_foe_set_rx_error(fsm, FOE_WC_ERROR); + EC_SLAVE_ERR(slave, "Reception of FoE RRQ failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_foe_state_data_check; +} + +/*****************************************************************************/ + +/** Starting state for read operations. + */ +void ec_fsm_foe_read_start( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + fsm->rx_buffer_offset = 0; + fsm->rx_expected_packet_no = 1; + fsm->rx_last_packet = 0; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (!(slave->sii.mailbox_protocols & EC_MBOX_FOE)) { + ec_foe_set_tx_error(fsm, FOE_MBOX_PROT_ERROR); + EC_SLAVE_ERR(slave, "Slave does not support FoE!\n"); + return; + } + + if (ec_foe_prepare_rrq_send(fsm, datagram)) { + ec_foe_set_rx_error(fsm, FOE_PROT_ERROR); + return; + } + + fsm->state = ec_fsm_foe_state_rrq_sent; +} + +/*****************************************************************************/ + +/** Check for data. + */ +void ec_fsm_foe_state_data_check( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + ec_foe_set_rx_error(fsm, FOE_RECEIVE_ERROR); + EC_SLAVE_ERR(slave, "Failed to send FoE DATA READ: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + ec_foe_set_rx_error(fsm, FOE_WC_ERROR); + EC_SLAVE_ERR(slave, "Reception of FoE DATA READ: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = (fsm->datagram->jiffies_received - + fsm->jiffies_start) * 1000 / HZ; + if (diff_ms >= EC_FSM_FOE_TIMEOUT) { + ec_foe_set_tx_error(fsm, FOE_TIMEOUT_ERROR); + EC_SLAVE_ERR(slave, "Timeout while waiting for ack response.\n"); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_foe_state_data_read; +} + +/*****************************************************************************/ + +/** Start reading data. + */ +void ec_fsm_foe_state_data_read( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + size_t rec_size; + uint8_t *data, opCode, packet_no, mbox_prot; + + ec_slave_t *slave = fsm->slave; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + ec_foe_set_rx_error(fsm, FOE_RECEIVE_ERROR); + EC_SLAVE_ERR(slave, "Failed to receive FoE DATA READ datagram: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + ec_foe_set_rx_error(fsm, FOE_WC_ERROR); + EC_SLAVE_ERR(slave, "Reception of FoE DATA READ failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + ec_foe_set_rx_error(fsm, FOE_MBOX_FETCH_ERROR); + return; + } + + if (mbox_prot != EC_MBOX_TYPE_FILEACCESS) { // FoE + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + ec_foe_set_rx_error(fsm, FOE_PROT_ERROR); + return; + } + + opCode = EC_READ_U8(data); + + if (opCode == EC_FOE_OPCODE_BUSY) { + if (ec_foe_prepare_send_ack(fsm, datagram)) { + ec_foe_set_rx_error(fsm, FOE_PROT_ERROR); + } + return; + } + + if (opCode == EC_FOE_OPCODE_ERR) { + fsm->request->error_code = EC_READ_U32(data + 2); + EC_SLAVE_ERR(slave, "Received FoE Error Request (code 0x%08x).\n", + fsm->request->error_code); + if (rec_size > 6) { + uint8_t text[256]; + strncpy(text, data + 6, min(rec_size - 6, sizeof(text))); + EC_SLAVE_ERR(slave, "FoE Error Text: %s\n", text); + } + ec_foe_set_rx_error(fsm, FOE_OPCODE_ERROR); + return; + } + + if (opCode != EC_FOE_OPCODE_DATA) { + EC_SLAVE_ERR(slave, "Received OPCODE %x, expected %x.\n", + opCode, EC_FOE_OPCODE_DATA); + fsm->request->error_code = 0x00000000; + ec_foe_set_rx_error(fsm, FOE_OPCODE_ERROR); + return; + } + + packet_no = EC_READ_U16(data + 2); + if (packet_no != fsm->rx_expected_packet_no) { + EC_SLAVE_ERR(slave, "Received unexpected packet number.\n"); + ec_foe_set_rx_error(fsm, FOE_PACKETNO_ERROR); + return; + } + + rec_size -= EC_FOE_HEADER_SIZE; + + if (fsm->rx_buffer_size >= fsm->rx_buffer_offset + rec_size) { + memcpy(fsm->rx_buffer + fsm->rx_buffer_offset, + data + EC_FOE_HEADER_SIZE, rec_size); + fsm->rx_buffer_offset += rec_size; + } + + fsm->rx_last_packet = + (rec_size + EC_MBOX_HEADER_SIZE + EC_FOE_HEADER_SIZE + != slave->configured_rx_mailbox_size); + + if (fsm->rx_last_packet || + (slave->configured_rx_mailbox_size - EC_MBOX_HEADER_SIZE + - EC_FOE_HEADER_SIZE + fsm->rx_buffer_offset) + <= fsm->rx_buffer_size) { + // either it was the last packet or a new packet will fit into the + // delivered buffer +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "last_packet=true\n"); +#endif + if (ec_foe_prepare_send_ack(fsm, datagram)) { + ec_foe_set_rx_error(fsm, FOE_RX_DATA_ACK_ERROR); + return; + } + + fsm->state = ec_fsm_foe_state_sent_ack; + } + else { + // no more data fits into the delivered buffer + // ... wait for new read request + EC_SLAVE_ERR(slave, "Data do not fit in receive buffer!\n"); + printk(" rx_buffer_size = %d\n", fsm->rx_buffer_size); + printk("rx_buffer_offset = %d\n", fsm->rx_buffer_offset); + printk(" rec_size = %zd\n", rec_size); + printk(" rx_mailbox_size = %d\n", slave->configured_rx_mailbox_size); + printk(" rx_last_packet = %d\n", fsm->rx_last_packet); + fsm->request->result = FOE_READY; + } +} + +/*****************************************************************************/ + +/** Sent an acknowledge. + */ +void ec_fsm_foe_state_sent_ack( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + +#ifdef DEBUG_FOE + EC_SLAVE_DBG(fsm->slave, 0, "%s()\n", __func__); +#endif + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + ec_foe_set_rx_error(fsm, FOE_RECEIVE_ERROR); + EC_SLAVE_ERR(slave, "Failed to send FoE ACK: "); + ec_datagram_print_state(fsm->datagram); + return; + } + + if (fsm->datagram->working_counter != 1) { + // slave did not put anything into the mailbox yet + ec_foe_set_rx_error(fsm, FOE_WC_ERROR); + EC_SLAVE_ERR(slave, "Reception of FoE ACK failed: "); + ec_datagram_print_wc_error(fsm->datagram); + return; + } + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + + if (fsm->rx_last_packet) { + fsm->rx_expected_packet_no = 0; + fsm->request->data_size = fsm->rx_buffer_offset; + fsm->state = ec_fsm_foe_end; + } + else { + fsm->rx_expected_packet_no++; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_foe_state_data_check; + } +} + +/*****************************************************************************/ + +/** Set an error code and go to the send error state. + */ +void ec_foe_set_tx_error( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + uint32_t errorcode /**< FoE error code. */ + ) +{ + fsm->request->result = errorcode; + fsm->state = ec_fsm_foe_error; +} + +/*****************************************************************************/ + +/** Set an error code and go to the receive error state. + */ +void ec_foe_set_rx_error( + ec_fsm_foe_t *fsm, /**< FoE statemachine. */ + uint32_t errorcode /**< FoE error code. */ + ) +{ + fsm->request->result = errorcode; + fsm->state = ec_fsm_foe_error; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_foe.h b/net/ethercat/master/fsm_foe.h new file mode 100644 index 000000000000..91d6df548ca5 --- /dev/null +++ b/net/ethercat/master/fsm_foe.h @@ -0,0 +1,94 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2008 Olav Zarges, imc Messsysteme GmbH + * 2009-2012 Florian Pose <fp@igh-essen.com> + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT FoE state machines. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_FOE_H__ +#define __EC_FSM_FOE_H__ + +#include "globals.h" +#include "../include/ecrt.h" +#include "datagram.h" +#include "slave.h" +#include "foe_request.h" + +/*****************************************************************************/ + +typedef struct ec_fsm_foe ec_fsm_foe_t; /**< \see ec_fsm_foe */ + +/** Finite state machines for the CANopen-over-EtherCAT protocol. + */ +struct ec_fsm_foe { + ec_slave_t *slave; /**< Slave the FSM runs on. */ + unsigned int retries; /**< Retries upon datagram timeout */ + + void (*state)(ec_fsm_foe_t *, ec_datagram_t *); /**< FoE state function. */ + ec_datagram_t *datagram; /**< Datagram used in previous step. */ + unsigned long jiffies_start; /**< FoE timestamp. */ + uint8_t subindex; /**< Current subindex. */ + ec_foe_request_t *request; /**< FoE request. */ + uint8_t toggle; /**< Toggle bit for segment commands. */ + + uint8_t *tx_buffer; /**< Buffer with data to transmit. */ + uint32_t tx_buffer_size; /**< Size of data to transmit. */ + uint32_t tx_buffer_offset; /**< Offset of data to tranmit next. */ + uint32_t tx_last_packet; /**< Current packet is last one to send. */ + uint32_t tx_packet_no; /**< FoE packet number. */ + uint32_t tx_current_size; /**< Size of current packet to send. */ + uint8_t *tx_filename; /**< Name of file to transmit. */ + uint32_t tx_filename_len; /**< Lenth of transmit file name. */ + + uint8_t *rx_buffer; /**< Buffer for received data. */ + uint32_t rx_buffer_size; /**< Size of receive buffer. */ + uint32_t rx_buffer_offset; /**< Offset in receive buffer. */ + uint32_t rx_expected_packet_no; /**< Expected receive packet number. */ + uint32_t rx_last_packet; /**< Current packet is the last to receive. */ + uint8_t *rx_filename; /**< Name of the file to receive. */ + uint32_t rx_filename_len; /**< Length of the receive file name. */ +}; + +/*****************************************************************************/ + +void ec_fsm_foe_init(ec_fsm_foe_t *); +void ec_fsm_foe_clear(ec_fsm_foe_t *); + +int ec_fsm_foe_exec(ec_fsm_foe_t *, ec_datagram_t *); +int ec_fsm_foe_success(const ec_fsm_foe_t *); + +void ec_fsm_foe_transfer(ec_fsm_foe_t *, ec_slave_t *, ec_foe_request_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_master.c b/net/ethercat/master/fsm_master.c new file mode 100644 index 000000000000..719e91bfa02b --- /dev/null +++ b/net/ethercat/master/fsm_master.c @@ -0,0 +1,1306 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT master state machine. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "slave_config.h" +#ifdef EC_EOE +#include "ethernet.h" +#endif + +#include "fsm_master.h" +#include "fsm_foe.h" + +/*****************************************************************************/ + +/** Time difference [ns] to tolerate without setting a new system time offset. + */ +#define EC_SYSTEM_TIME_TOLERANCE_NS 1000000 + +/*****************************************************************************/ + +void ec_fsm_master_state_start(ec_fsm_master_t *); +void ec_fsm_master_state_broadcast(ec_fsm_master_t *); +void ec_fsm_master_state_read_state(ec_fsm_master_t *); +void ec_fsm_master_state_acknowledge(ec_fsm_master_t *); +void ec_fsm_master_state_configure_slave(ec_fsm_master_t *); +void ec_fsm_master_state_clear_addresses(ec_fsm_master_t *); +void ec_fsm_master_state_dc_measure_delays(ec_fsm_master_t *); +void ec_fsm_master_state_scan_slave(ec_fsm_master_t *); +void ec_fsm_master_state_dc_read_offset(ec_fsm_master_t *); +void ec_fsm_master_state_dc_write_offset(ec_fsm_master_t *); +void ec_fsm_master_state_write_sii(ec_fsm_master_t *); +void ec_fsm_master_state_sdo_dictionary(ec_fsm_master_t *); +void ec_fsm_master_state_sdo_request(ec_fsm_master_t *); + +void ec_fsm_master_enter_clear_addresses(ec_fsm_master_t *); +void ec_fsm_master_enter_write_system_times(ec_fsm_master_t *); + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_master_init( + ec_fsm_master_t *fsm, /**< Master state machine. */ + ec_master_t *master, /**< EtherCAT master. */ + ec_datagram_t *datagram /**< Datagram object to use. */ + ) +{ + fsm->master = master; + fsm->datagram = datagram; + + ec_fsm_master_reset(fsm); + + // init sub-state-machines + ec_fsm_coe_init(&fsm->fsm_coe); + ec_fsm_soe_init(&fsm->fsm_soe); + ec_fsm_pdo_init(&fsm->fsm_pdo, &fsm->fsm_coe); + ec_fsm_change_init(&fsm->fsm_change, fsm->datagram); + ec_fsm_slave_config_init(&fsm->fsm_slave_config, fsm->datagram, + &fsm->fsm_change, &fsm->fsm_coe, &fsm->fsm_soe, &fsm->fsm_pdo); + ec_fsm_slave_scan_init(&fsm->fsm_slave_scan, fsm->datagram, + &fsm->fsm_slave_config, &fsm->fsm_pdo); + ec_fsm_sii_init(&fsm->fsm_sii, fsm->datagram); +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_master_clear( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + // clear sub-state machines + ec_fsm_coe_clear(&fsm->fsm_coe); + ec_fsm_soe_clear(&fsm->fsm_soe); + ec_fsm_pdo_clear(&fsm->fsm_pdo); + ec_fsm_change_clear(&fsm->fsm_change); + ec_fsm_slave_config_clear(&fsm->fsm_slave_config); + ec_fsm_slave_scan_clear(&fsm->fsm_slave_scan); + ec_fsm_sii_clear(&fsm->fsm_sii); +} + +/*****************************************************************************/ + +/** Reset state machine. + */ +void ec_fsm_master_reset( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_device_index_t dev_idx; + + fsm->state = ec_fsm_master_state_start; + fsm->idle = 0; + fsm->dev_idx = EC_DEVICE_MAIN; + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(fsm->master); dev_idx++) { + fsm->link_state[dev_idx] = 0; + fsm->slaves_responding[dev_idx] = 0; + fsm->slave_states[dev_idx] = EC_SLAVE_STATE_UNKNOWN; + } + + fsm->rescan_required = 0; +} + +/*****************************************************************************/ + +/** Executes the current state of the state machine. + * + * If the state machine's datagram is not sent or received yet, the execution + * of the state machine is delayed to the next cycle. + * + * \return true, if the state machine was executed + */ +int ec_fsm_master_exec( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + if (fsm->datagram->state == EC_DATAGRAM_SENT + || fsm->datagram->state == EC_DATAGRAM_QUEUED) { + // datagram was not sent or received yet. + return 0; + } + + fsm->state(fsm); + return 1; +} + +/*****************************************************************************/ + +/** + * \return true, if the state machine is in an idle phase + */ +int ec_fsm_master_idle( + const ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + return fsm->idle; +} + +/*****************************************************************************/ + +/** Restarts the master state machine. + */ +void ec_fsm_master_restart( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + fsm->dev_idx = EC_DEVICE_MAIN; + fsm->state = ec_fsm_master_state_start; + fsm->state(fsm); // execute immediately +} + +/****************************************************************************** + * Master state machine + *****************************************************************************/ + +/** Master state: START. + * + * Starts with getting slave count and slave states. + */ +void ec_fsm_master_state_start( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + + fsm->idle = 1; + + // check for emergency requests + if (!list_empty(&master->emerg_reg_requests)) { + ec_reg_request_t *request; + + // get first request + request = list_entry(master->emerg_reg_requests.next, + ec_reg_request_t, list); + list_del_init(&request->list); // dequeue + request->state = EC_INT_REQUEST_BUSY; + + if (request->transfer_size > fsm->datagram->mem_size) { + EC_MASTER_ERR(master, "Emergency request data too large!\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&master->request_queue); + fsm->state(fsm); // continue + return; + } + + if (request->dir != EC_DIR_OUTPUT) { + EC_MASTER_ERR(master, "Emergency requests must be" + " write requests!\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&master->request_queue); + fsm->state(fsm); // continue + return; + } + + EC_MASTER_DBG(master, 1, "Writing emergency register request...\n"); + ec_datagram_apwr(fsm->datagram, request->ring_position, + request->address, request->transfer_size); + memcpy(fsm->datagram->data, request->data, request->transfer_size); + fsm->datagram->device_index = EC_DEVICE_MAIN; + request->state = EC_INT_REQUEST_SUCCESS; + wake_up_all(&master->request_queue); + return; + } + + ec_datagram_brd(fsm->datagram, 0x0130, 2); + ec_datagram_zero(fsm->datagram); + fsm->datagram->device_index = fsm->dev_idx; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_master_state_broadcast; +} + +/*****************************************************************************/ + +/** Master state: BROADCAST. + * + * Processes the broadcast read slave count and slaves states. + */ +void ec_fsm_master_state_broadcast( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + unsigned int i, size; + ec_slave_t *slave; + ec_master_t *master = fsm->master; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + return; + } + + // bus topology change? + if (datagram->working_counter != fsm->slaves_responding[fsm->dev_idx]) { + fsm->rescan_required = 1; + fsm->slaves_responding[fsm->dev_idx] = datagram->working_counter; + EC_MASTER_INFO(master, "%u slave(s) responding on %s device.\n", + fsm->slaves_responding[fsm->dev_idx], + ec_device_names[fsm->dev_idx != 0]); + } + + if (fsm->link_state[fsm->dev_idx] && + !master->devices[fsm->dev_idx].link_state) { + ec_device_index_t dev_idx; + + EC_MASTER_DBG(master, 1, "Master state machine detected " + "link down on %s device. Clearing slave list.\n", + ec_device_names[fsm->dev_idx != 0]); + +#ifdef EC_EOE + ec_master_eoe_stop(master); + ec_master_clear_eoe_handlers(master); +#endif + ec_master_clear_slaves(master); + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(master); dev_idx++) { + fsm->slave_states[dev_idx] = 0x00; + fsm->slaves_responding[dev_idx] = 0; /* Reset to trigger rescan on + next link up. */ + } + } + fsm->link_state[fsm->dev_idx] = master->devices[fsm->dev_idx].link_state; + + if (datagram->state == EC_DATAGRAM_RECEIVED && + fsm->slaves_responding[fsm->dev_idx]) { + uint8_t states = EC_READ_U8(datagram->data); + if (states != fsm->slave_states[fsm->dev_idx]) { + // slave states changed + char state_str[EC_STATE_STRING_SIZE]; + fsm->slave_states[fsm->dev_idx] = states; + ec_state_string(states, state_str, 1); + EC_MASTER_INFO(master, "Slave states on %s device: %s.\n", + ec_device_names[fsm->dev_idx != 0], state_str); + } + } else { + fsm->slave_states[fsm->dev_idx] = 0x00; + } + + fsm->dev_idx++; + if (fsm->dev_idx < ec_master_num_devices(master)) { + // check number of responding slaves on next device + fsm->state = ec_fsm_master_state_start; + fsm->state(fsm); // execute immediately + return; + } + + if (fsm->rescan_required) { + down(&master->scan_sem); + if (!master->allow_scan) { + up(&master->scan_sem); + } else { + unsigned int count = 0, next_dev_slave, ring_position; + ec_device_index_t dev_idx; + + master->scan_busy = 1; + up(&master->scan_sem); + + // clear all slaves and scan the bus + fsm->rescan_required = 0; + fsm->idle = 0; + fsm->scan_jiffies = jiffies; + +#ifdef EC_EOE + ec_master_eoe_stop(master); + ec_master_clear_eoe_handlers(master); +#endif + ec_master_clear_slaves(master); + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(master); dev_idx++) { + count += fsm->slaves_responding[dev_idx]; + } + + if (!count) { + // no slaves present -> finish state machine. + master->scan_busy = 0; + wake_up_interruptible(&master->scan_queue); + ec_fsm_master_restart(fsm); + return; + } + + size = sizeof(ec_slave_t) * count; + if (!(master->slaves = + (ec_slave_t *) kmalloc(size, GFP_KERNEL))) { + EC_MASTER_ERR(master, "Failed to allocate %u bytes" + " of slave memory!\n", size); + master->scan_busy = 0; + wake_up_interruptible(&master->scan_queue); + ec_fsm_master_restart(fsm); + return; + } + + // init slaves + dev_idx = EC_DEVICE_MAIN; + next_dev_slave = fsm->slaves_responding[dev_idx]; + ring_position = 0; + for (i = 0; i < count; i++, ring_position++) { + slave = master->slaves + i; + while (i >= next_dev_slave) { + dev_idx++; + next_dev_slave += fsm->slaves_responding[dev_idx]; + ring_position = 0; + } + + ec_slave_init(slave, master, dev_idx, ring_position, i + 1); + + // do not force reconfiguration in operation phase to avoid + // unnecesssary process data interruptions + if (master->phase != EC_OPERATION) { + slave->force_config = 1; + } + } + master->slave_count = count; + master->fsm_slave = master->slaves; + + /* start with first device with slaves responding; at least one + * has responding slaves, otherwise count would be zero. */ + fsm->dev_idx = EC_DEVICE_MAIN; + while (!fsm->slaves_responding[fsm->dev_idx]) { + fsm->dev_idx++; + } + + ec_fsm_master_enter_clear_addresses(fsm); + return; + } + } + + if (master->slave_count) { + + // application applied configurations + if (master->config_changed) { + master->config_changed = 0; + + EC_MASTER_DBG(master, 1, "Configuration changed.\n"); + + fsm->slave = master->slaves; // begin with first slave + ec_fsm_master_enter_write_system_times(fsm); + + } else { + // fetch state from first slave + fsm->slave = master->slaves; + ec_datagram_fprd(fsm->datagram, fsm->slave->station_address, + 0x0130, 2); + ec_datagram_zero(datagram); + fsm->datagram->device_index = fsm->slave->device_index; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_master_state_read_state; + } + } else { + ec_fsm_master_restart(fsm); + } +} + +/*****************************************************************************/ + +/** Check for pending SII write requests and process one. + * + * \return non-zero, if an SII write request is processed. + */ +int ec_fsm_master_action_process_sii( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + ec_sii_write_request_t *request; + + // search the first request to be processed + while (1) { + if (list_empty(&master->sii_requests)) + break; + + // get first request + request = list_entry(master->sii_requests.next, + ec_sii_write_request_t, list); + list_del_init(&request->list); // dequeue + request->state = EC_INT_REQUEST_BUSY; + + // found pending SII write operation. execute it! + EC_SLAVE_DBG(request->slave, 1, "Writing SII data...\n"); + fsm->sii_request = request; + fsm->sii_index = 0; + ec_fsm_sii_write(&fsm->fsm_sii, request->slave, request->offset, + request->words, EC_FSM_SII_USE_CONFIGURED_ADDRESS); + fsm->state = ec_fsm_master_state_write_sii; + fsm->state(fsm); // execute immediately + return 1; + } + + return 0; +} + +/*****************************************************************************/ + +/** Check for pending SDO requests and process one. + * + * \return non-zero, if an SDO request is processed. + */ +int ec_fsm_master_action_process_sdo( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + ec_slave_t *slave; + ec_sdo_request_t *req; + + // search for internal requests to be processed + for (slave = master->slaves; + slave < master->slaves + master->slave_count; + slave++) { + + if (!slave->config) { + continue; + } + + list_for_each_entry(req, &slave->config->sdo_requests, list) { + if (req->state == EC_INT_REQUEST_QUEUED) { + + if (ec_sdo_request_timed_out(req)) { + req->state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_DBG(slave, 1, "Internal SDO request" + " timed out.\n"); + continue; + } + + if (slave->current_state == EC_SLAVE_STATE_INIT) { + req->state = EC_INT_REQUEST_FAILURE; + continue; + } + + req->state = EC_INT_REQUEST_BUSY; + EC_SLAVE_DBG(slave, 1, "Processing internal" + " SDO request...\n"); + fsm->idle = 0; + fsm->sdo_request = req; + fsm->slave = slave; + fsm->state = ec_fsm_master_state_sdo_request; + ec_fsm_coe_transfer(&fsm->fsm_coe, slave, req); + ec_fsm_coe_exec(&fsm->fsm_coe, fsm->datagram); + return 1; + } + } + } + return 0; +} + +/*****************************************************************************/ + +/** Master action: IDLE. + * + * Does secondary work. + */ +void ec_fsm_master_action_idle( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + ec_slave_t *slave; + + // Check for pending internal SDO requests + if (ec_fsm_master_action_process_sdo(fsm)) { + return; + } + + // enable processing of requests + for (slave = master->slaves; + slave < master->slaves + master->slave_count; + slave++) { + ec_fsm_slave_set_ready(&slave->fsm); + } + + // check, if slaves have an SDO dictionary to read out. + for (slave = master->slaves; + slave < master->slaves + master->slave_count; + slave++) { + if (!(slave->sii.mailbox_protocols & EC_MBOX_COE) + || (slave->sii.has_general + && !slave->sii.coe_details.enable_sdo_info) + || slave->sdo_dictionary_fetched + || slave->current_state == EC_SLAVE_STATE_INIT + || slave->current_state == EC_SLAVE_STATE_UNKNOWN + || jiffies - slave->jiffies_preop < EC_WAIT_SDO_DICT * HZ + ) continue; + + EC_SLAVE_DBG(slave, 1, "Fetching SDO dictionary.\n"); + + slave->sdo_dictionary_fetched = 1; + + // start fetching SDO dictionary + fsm->idle = 0; + fsm->slave = slave; + fsm->state = ec_fsm_master_state_sdo_dictionary; + ec_fsm_coe_dictionary(&fsm->fsm_coe, slave); + ec_fsm_coe_exec(&fsm->fsm_coe, fsm->datagram); // execute immediately + fsm->datagram->device_index = fsm->slave->device_index; + return; + } + + // check for pending SII write operations. + if (ec_fsm_master_action_process_sii(fsm)) { + return; // SII write request found + } + + ec_fsm_master_restart(fsm); +} + +/*****************************************************************************/ + +/** Master action: Get state of next slave. + */ +void ec_fsm_master_action_next_slave_state( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + + // is there another slave to query? + fsm->slave++; + if (fsm->slave < master->slaves + master->slave_count) { + // fetch state from next slave + fsm->idle = 1; + ec_datagram_fprd(fsm->datagram, + fsm->slave->station_address, 0x0130, 2); + ec_datagram_zero(fsm->datagram); + fsm->datagram->device_index = fsm->slave->device_index; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_master_state_read_state; + return; + } + + // all slaves processed + ec_fsm_master_action_idle(fsm); +} + +/*****************************************************************************/ + +/** Master action: Configure. + */ +void ec_fsm_master_action_configure( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + ec_slave_t *slave = fsm->slave; + + if (master->config_changed) { + master->config_changed = 0; + + // abort iterating through slaves, + // first compensate DC system time offsets, + // then begin configuring at slave 0 + EC_MASTER_DBG(master, 1, "Configuration changed" + " (aborting state check).\n"); + + fsm->slave = master->slaves; // begin with first slave + ec_fsm_master_enter_write_system_times(fsm); + return; + } + + // Does the slave have to be configured? + if ((slave->current_state != slave->requested_state + || slave->force_config) && !slave->error_flag) { + + // Start slave configuration + down(&master->config_sem); + master->config_busy = 1; + up(&master->config_sem); + + if (master->debug_level) { + char old_state[EC_STATE_STRING_SIZE], + new_state[EC_STATE_STRING_SIZE]; + ec_state_string(slave->current_state, old_state, 0); + ec_state_string(slave->requested_state, new_state, 0); + EC_SLAVE_DBG(slave, 1, "Changing state from %s to %s%s.\n", + old_state, new_state, + slave->force_config ? " (forced)" : ""); + } + + fsm->idle = 0; + fsm->state = ec_fsm_master_state_configure_slave; + ec_fsm_slave_config_start(&fsm->fsm_slave_config, slave); + fsm->state(fsm); // execute immediately + fsm->datagram->device_index = fsm->slave->device_index; + return; + } + + // process next slave + ec_fsm_master_action_next_slave_state(fsm); +} + +/*****************************************************************************/ + +/** Master state: READ STATE. + * + * Fetches the AL state of a slave. + */ +void ec_fsm_master_state_read_state( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + return; + } + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + EC_SLAVE_ERR(slave, "Failed to receive AL state datagram: "); + ec_datagram_print_state(datagram); + ec_fsm_master_restart(fsm); + return; + } + + // did the slave not respond to its station address? + if (datagram->working_counter != 1) { + if (!slave->error_flag) { + slave->error_flag = 1; + EC_SLAVE_DBG(slave, 1, "Slave did not respond to state query.\n"); + } + fsm->rescan_required = 1; + ec_fsm_master_restart(fsm); + return; + } + + // A single slave responded + ec_slave_set_state(slave, EC_READ_U8(datagram->data)); + + if (!slave->error_flag) { + // Check, if new slave state has to be acknowledged + if (slave->current_state & EC_SLAVE_STATE_ACK_ERR) { + fsm->idle = 0; + fsm->state = ec_fsm_master_state_acknowledge; + ec_fsm_change_ack(&fsm->fsm_change, slave); + fsm->state(fsm); // execute immediately + return; + } + + // No acknowlegde necessary; check for configuration + ec_fsm_master_action_configure(fsm); + return; + } + + // slave has error flag set; process next one + ec_fsm_master_action_next_slave_state(fsm); +} + +/*****************************************************************************/ + +/** Master state: ACKNOWLEDGE. + */ +void ec_fsm_master_state_acknowledge( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (ec_fsm_change_exec(&fsm->fsm_change)) { + return; + } + + if (!ec_fsm_change_success(&fsm->fsm_change)) { + fsm->slave->error_flag = 1; + EC_SLAVE_ERR(slave, "Failed to acknowledge state change.\n"); + } + + ec_fsm_master_action_configure(fsm); +} + +/*****************************************************************************/ + +/** Start clearing slave addresses. + */ +void ec_fsm_master_enter_clear_addresses( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + // broadcast clear all station addresses + ec_datagram_bwr(fsm->datagram, 0x0010, 2); + EC_WRITE_U16(fsm->datagram->data, 0x0000); + fsm->datagram->device_index = fsm->dev_idx; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_master_state_clear_addresses; +} + +/*****************************************************************************/ + +/** Master state: CLEAR ADDRESSES. + */ +void ec_fsm_master_state_clear_addresses( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + return; + } + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + EC_MASTER_ERR(master, "Failed to receive address" + " clearing datagram on %s link: ", + ec_device_names[fsm->dev_idx != 0]); + ec_datagram_print_state(datagram); + master->scan_busy = 0; + wake_up_interruptible(&master->scan_queue); + ec_fsm_master_restart(fsm); + return; + } + + if (datagram->working_counter != fsm->slaves_responding[fsm->dev_idx]) { + EC_MASTER_WARN(master, "Failed to clear station addresses on %s link:" + " Cleared %u of %u", + ec_device_names[fsm->dev_idx != 0], datagram->working_counter, + fsm->slaves_responding[fsm->dev_idx]); + } + + EC_MASTER_DBG(master, 1, "Sending broadcast-write" + " to measure transmission delays on %s link.\n", + ec_device_names[fsm->dev_idx != 0]); + + ec_datagram_bwr(datagram, 0x0900, 1); + ec_datagram_zero(datagram); + fsm->datagram->device_index = fsm->dev_idx; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_master_state_dc_measure_delays; +} + +/*****************************************************************************/ + +/** Master state: DC MEASURE DELAYS. + */ +void ec_fsm_master_state_dc_measure_delays( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + return; + } + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + EC_MASTER_ERR(master, "Failed to receive delay measuring datagram" + " on %s link: ", ec_device_names[fsm->dev_idx != 0]); + ec_datagram_print_state(datagram); + master->scan_busy = 0; + wake_up_interruptible(&master->scan_queue); + ec_fsm_master_restart(fsm); + return; + } + + EC_MASTER_DBG(master, 1, "%u slaves responded to delay measuring" + " on %s link.\n", + datagram->working_counter, ec_device_names[fsm->dev_idx != 0]); + + do { + fsm->dev_idx++; + } while (fsm->dev_idx < ec_master_num_devices(master) && + !fsm->slaves_responding[fsm->dev_idx]); + if (fsm->dev_idx < ec_master_num_devices(master)) { + ec_fsm_master_enter_clear_addresses(fsm); + return; + } + + EC_MASTER_INFO(master, "Scanning bus.\n"); + + // begin scanning of slaves + fsm->slave = master->slaves; + EC_MASTER_DBG(master, 1, "Scanning slave %u on %s link.\n", + fsm->slave->ring_position, + ec_device_names[fsm->slave->device_index != 0]); + fsm->state = ec_fsm_master_state_scan_slave; + ec_fsm_slave_scan_start(&fsm->fsm_slave_scan, fsm->slave); + ec_fsm_slave_scan_exec(&fsm->fsm_slave_scan); // execute immediately + fsm->datagram->device_index = fsm->slave->device_index; +} + +/*****************************************************************************/ + +/** Master state: SCAN SLAVE. + * + * Executes the sub-statemachine for the scanning of a slave. + */ +void ec_fsm_master_state_scan_slave( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; +#ifdef EC_EOE + ec_slave_t *slave = fsm->slave; +#endif + + if (ec_fsm_slave_scan_exec(&fsm->fsm_slave_scan)) { + return; + } + +#ifdef EC_EOE + if (slave->sii.mailbox_protocols & EC_MBOX_EOE) { + // create EoE handler for this slave + ec_eoe_t *eoe; + if (!(eoe = kmalloc(sizeof(ec_eoe_t), GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate EoE handler memory!\n"); + } else if (ec_eoe_init(eoe, slave)) { + EC_SLAVE_ERR(slave, "Failed to init EoE handler!\n"); + kfree(eoe); + } else { + list_add_tail(&eoe->list, &master->eoe_handlers); + } + } +#endif + + // another slave to fetch? + fsm->slave++; + if (fsm->slave < master->slaves + master->slave_count) { + EC_MASTER_DBG(master, 1, "Scanning slave %u on %s link.\n", + fsm->slave->ring_position, + ec_device_names[fsm->slave->device_index != 0]); + ec_fsm_slave_scan_start(&fsm->fsm_slave_scan, fsm->slave); + ec_fsm_slave_scan_exec(&fsm->fsm_slave_scan); // execute immediately + fsm->datagram->device_index = fsm->slave->device_index; + return; + } + + EC_MASTER_INFO(master, "Bus scanning completed in %lu ms.\n", + (jiffies - fsm->scan_jiffies) * 1000 / HZ); + + master->scan_busy = 0; + wake_up_interruptible(&master->scan_queue); + + ec_master_calc_dc(master); + + // Attach slave configurations + ec_master_attach_slave_configs(master); + +#ifdef EC_EOE + // check if EoE processing has to be started + ec_master_eoe_start(master); +#endif + + if (master->slave_count) { + master->config_changed = 0; + + fsm->slave = master->slaves; // begin with first slave + ec_fsm_master_enter_write_system_times(fsm); + } else { + ec_fsm_master_restart(fsm); + } +} + +/*****************************************************************************/ + +/** Master state: CONFIGURE SLAVE. + * + * Starts configuring a slave. + */ +void ec_fsm_master_state_configure_slave( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + + if (ec_fsm_slave_config_exec(&fsm->fsm_slave_config)) { + return; + } + + fsm->slave->force_config = 0; + + // configuration finished + master->config_busy = 0; + wake_up_interruptible(&master->config_queue); + + if (!ec_fsm_slave_config_success(&fsm->fsm_slave_config)) { + // TODO: mark slave_config as failed. + } + + fsm->idle = 1; + ec_fsm_master_action_next_slave_state(fsm); +} + +/*****************************************************************************/ + +/** Start writing DC system times. + */ +void ec_fsm_master_enter_write_system_times( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + + if (master->dc_ref_time) { + + while (fsm->slave < master->slaves + master->slave_count) { + if (!fsm->slave->base_dc_supported + || !fsm->slave->has_dc_system_time) { + fsm->slave++; + continue; + } + + EC_SLAVE_DBG(fsm->slave, 1, "Checking system time offset.\n"); + + // read DC system time (0x0910, 64 bit) + // gap (64 bit) + // and time offset (0x0920, 64 bit) + ec_datagram_fprd(fsm->datagram, fsm->slave->station_address, + 0x0910, 24); + fsm->datagram->device_index = fsm->slave->device_index; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_master_state_dc_read_offset; + return; + } + + } else { + if (master->active) { + EC_MASTER_WARN(master, "No application time received up to now," + " but master already active.\n"); + } else { + EC_MASTER_DBG(master, 1, "No app_time received up to now.\n"); + } + } + + // scanning and setting system times complete + ec_master_request_op(master); + ec_fsm_master_restart(fsm); +} + +/*****************************************************************************/ + +/** Configure 32 bit time offset. + * + * \return New offset. + */ +u64 ec_fsm_master_dc_offset32( + ec_fsm_master_t *fsm, /**< Master state machine. */ + u64 system_time, /**< System time register. */ + u64 old_offset, /**< Time offset register. */ + unsigned long jiffies_since_read /**< Jiffies for correction. */ + ) +{ + ec_slave_t *slave = fsm->slave; + u32 correction, system_time32, old_offset32, new_offset; + s32 time_diff; + + system_time32 = (u32) system_time; + old_offset32 = (u32) old_offset; + + // correct read system time by elapsed time since read operation + correction = jiffies_since_read * 1000 / HZ * 1000000; + system_time32 += correction; + time_diff = (u32) slave->master->app_time - system_time32; + + EC_SLAVE_DBG(slave, 1, "DC 32 bit system time offset calculation:" + " system_time=%u (corrected with %u)," + " app_time=%llu, diff=%i\n", + system_time32, correction, + slave->master->app_time, time_diff); + + if (EC_ABS(time_diff) > EC_SYSTEM_TIME_TOLERANCE_NS) { + new_offset = time_diff + old_offset32; + EC_SLAVE_DBG(slave, 1, "Setting time offset to %u (was %u)\n", + new_offset, old_offset32); + return (u64) new_offset; + } else { + EC_SLAVE_DBG(slave, 1, "Not touching time offset.\n"); + return old_offset; + } +} + +/*****************************************************************************/ + +/** Configure 64 bit time offset. + * + * \return New offset. + */ +u64 ec_fsm_master_dc_offset64( + ec_fsm_master_t *fsm, /**< Master state machine. */ + u64 system_time, /**< System time register. */ + u64 old_offset, /**< Time offset register. */ + unsigned long jiffies_since_read /**< Jiffies for correction. */ + ) +{ + ec_slave_t *slave = fsm->slave; + u64 new_offset, correction; + s64 time_diff; + + // correct read system time by elapsed time since read operation + correction = (u64) (jiffies_since_read * 1000 / HZ) * 1000000; + system_time += correction; + time_diff = fsm->slave->master->app_time - system_time; + + EC_SLAVE_DBG(slave, 1, "DC 64 bit system time offset calculation:" + " system_time=%llu (corrected with %llu)," + " app_time=%llu, diff=%lli\n", + system_time, correction, + slave->master->app_time, time_diff); + + if (EC_ABS(time_diff) > EC_SYSTEM_TIME_TOLERANCE_NS) { + new_offset = time_diff + old_offset; + EC_SLAVE_DBG(slave, 1, "Setting time offset to %llu (was %llu)\n", + new_offset, old_offset); + } else { + new_offset = old_offset; + EC_SLAVE_DBG(slave, 1, "Not touching time offset.\n"); + } + + return new_offset; +} + +/*****************************************************************************/ + +/** Master state: DC READ OFFSET. + */ +void ec_fsm_master_state_dc_read_offset( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + u64 system_time, old_offset, new_offset; + unsigned long jiffies_since_read; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + EC_SLAVE_ERR(slave, "Failed to receive DC times datagram: "); + ec_datagram_print_state(datagram); + fsm->slave++; + ec_fsm_master_enter_write_system_times(fsm); + return; + } + + if (datagram->working_counter != 1) { + EC_SLAVE_WARN(slave, "Failed to get DC times: "); + ec_datagram_print_wc_error(datagram); + fsm->slave++; + ec_fsm_master_enter_write_system_times(fsm); + return; + } + + system_time = EC_READ_U64(datagram->data); // 0x0910 + old_offset = EC_READ_U64(datagram->data + 16); // 0x0920 + jiffies_since_read = jiffies - datagram->jiffies_sent; + + if (slave->base_dc_range == EC_DC_32) { + new_offset = ec_fsm_master_dc_offset32(fsm, + system_time, old_offset, jiffies_since_read); + } else { + new_offset = ec_fsm_master_dc_offset64(fsm, + system_time, old_offset, jiffies_since_read); + } + + // set DC system time offset and transmission delay + ec_datagram_fpwr(datagram, slave->station_address, 0x0920, 12); + EC_WRITE_U64(datagram->data, new_offset); + EC_WRITE_U32(datagram->data + 8, slave->transmission_delay); + fsm->datagram->device_index = slave->device_index; + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_master_state_dc_write_offset; +} + +/*****************************************************************************/ + +/** Master state: DC WRITE OFFSET. + */ +void ec_fsm_master_state_dc_write_offset( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + EC_SLAVE_ERR(slave, + "Failed to receive DC system time offset datagram: "); + ec_datagram_print_state(datagram); + fsm->slave++; + ec_fsm_master_enter_write_system_times(fsm); + return; + } + + if (datagram->working_counter != 1) { + EC_SLAVE_ERR(slave, "Failed to set DC system time offset: "); + ec_datagram_print_wc_error(datagram); + fsm->slave++; + ec_fsm_master_enter_write_system_times(fsm); + return; + } + + fsm->slave++; + ec_fsm_master_enter_write_system_times(fsm); +} + +/*****************************************************************************/ + +/** Master state: WRITE SII. + */ +void ec_fsm_master_state_write_sii( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_master_t *master = fsm->master; + ec_sii_write_request_t *request = fsm->sii_request; + ec_slave_t *slave = request->slave; + + if (ec_fsm_sii_exec(&fsm->fsm_sii)) return; + + if (!ec_fsm_sii_success(&fsm->fsm_sii)) { + EC_SLAVE_ERR(slave, "Failed to write SII data.\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&master->request_queue); + ec_fsm_master_restart(fsm); + return; + } + + fsm->sii_index++; + if (fsm->sii_index < request->nwords) { + ec_fsm_sii_write(&fsm->fsm_sii, slave, + request->offset + fsm->sii_index, + request->words + fsm->sii_index, + EC_FSM_SII_USE_CONFIGURED_ADDRESS); + ec_fsm_sii_exec(&fsm->fsm_sii); // execute immediately + return; + } + + // finished writing SII + EC_SLAVE_DBG(slave, 1, "Finished writing %zu words of SII data.\n", + request->nwords); + + if (request->offset <= 4 && request->offset + request->nwords > 4) { + // alias was written + slave->sii.alias = EC_READ_U16(request->words + 4); + // TODO: read alias from register 0x0012 + slave->effective_alias = slave->sii.alias; + } + // TODO: Evaluate other SII contents! + + request->state = EC_INT_REQUEST_SUCCESS; + wake_up_all(&master->request_queue); + + // check for another SII write request + if (ec_fsm_master_action_process_sii(fsm)) + return; // processing another request + + ec_fsm_master_restart(fsm); +} + +/*****************************************************************************/ + +/** Master state: SDO DICTIONARY. + */ +void ec_fsm_master_state_sdo_dictionary( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_master_t *master = fsm->master; + + if (ec_fsm_coe_exec(&fsm->fsm_coe, fsm->datagram)) { + return; + } + + if (!ec_fsm_coe_success(&fsm->fsm_coe)) { + ec_fsm_master_restart(fsm); + return; + } + + // SDO dictionary fetching finished + + if (master->debug_level) { + unsigned int sdo_count, entry_count; + ec_slave_sdo_dict_info(slave, &sdo_count, &entry_count); + EC_SLAVE_DBG(slave, 1, "Fetched %u SDOs and %u entries.\n", + sdo_count, entry_count); + } + + // attach pdo names from dictionary + ec_slave_attach_pdo_names(slave); + + ec_fsm_master_restart(fsm); +} + +/*****************************************************************************/ + +/** Master state: SDO REQUEST. + */ +void ec_fsm_master_state_sdo_request( + ec_fsm_master_t *fsm /**< Master state machine. */ + ) +{ + ec_sdo_request_t *request = fsm->sdo_request; + + if (!request) { + // configuration was cleared in the meantime + ec_fsm_master_restart(fsm); + return; + } + + if (ec_fsm_coe_exec(&fsm->fsm_coe, fsm->datagram)) { + return; + } + + if (!ec_fsm_coe_success(&fsm->fsm_coe)) { + EC_SLAVE_DBG(fsm->slave, 1, + "Failed to process internal SDO request.\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&fsm->master->request_queue); + ec_fsm_master_restart(fsm); + return; + } + + // SDO request finished + request->state = EC_INT_REQUEST_SUCCESS; + wake_up_all(&fsm->master->request_queue); + + EC_SLAVE_DBG(fsm->slave, 1, "Finished internal SDO request.\n"); + + // check for another SDO request + if (ec_fsm_master_action_process_sdo(fsm)) { + return; // processing another request + } + + ec_fsm_master_restart(fsm); +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_master.h b/net/ethercat/master/fsm_master.h new file mode 100644 index 000000000000..a41b949c1b97 --- /dev/null +++ b/net/ethercat/master/fsm_master.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT master state machine. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_MASTER_H__ +#define __EC_FSM_MASTER_H__ + +#include "globals.h" +#include "datagram.h" +#include "foe_request.h" +#include "sdo_request.h" +#include "soe_request.h" +#include "fsm_slave_config.h" +#include "fsm_slave_scan.h" +#include "fsm_pdo.h" + +/*****************************************************************************/ + +/** SII write request. + */ +typedef struct { + struct list_head list; /**< List head. */ + ec_slave_t *slave; /**< EtherCAT slave. */ + uint16_t offset; /**< SII word offset. */ + size_t nwords; /**< Number of words. */ + const uint16_t *words; /**< Pointer to the data words. */ + ec_internal_request_state_t state; /**< State of the request. */ +} ec_sii_write_request_t; + +/*****************************************************************************/ + +typedef struct ec_fsm_master ec_fsm_master_t; /**< \see ec_fsm_master */ + +/** Finite state machine of an EtherCAT master. + */ +struct ec_fsm_master { + ec_master_t *master; /**< master the FSM runs on */ + ec_datagram_t *datagram; /**< datagram used in the state machine */ + unsigned int retries; /**< retries on datagram timeout. */ + + void (*state)(ec_fsm_master_t *); /**< master state function */ + ec_device_index_t dev_idx; /**< Current device index (for scanning etc.). + */ + int idle; /**< state machine is in idle phase */ + unsigned long scan_jiffies; /**< beginning of slave scanning */ + uint8_t link_state[EC_MAX_NUM_DEVICES]; /**< Last link state for every + device. */ + unsigned int slaves_responding[EC_MAX_NUM_DEVICES]; /**< Number of + responding slaves + for every device. */ + unsigned int rescan_required; /**< A bus rescan is required. */ + ec_slave_state_t slave_states[EC_MAX_NUM_DEVICES]; /**< AL states of + responding slaves for + every device. */ + ec_slave_t *slave; /**< current slave */ + ec_sii_write_request_t *sii_request; /**< SII write request */ + off_t sii_index; /**< index to SII write request data */ + ec_sdo_request_t *sdo_request; /**< SDO request to process. */ + + ec_fsm_coe_t fsm_coe; /**< CoE state machine */ + ec_fsm_soe_t fsm_soe; /**< SoE state machine */ + ec_fsm_pdo_t fsm_pdo; /**< PDO configuration state machine. */ + ec_fsm_change_t fsm_change; /**< State change state machine */ + ec_fsm_slave_config_t fsm_slave_config; /**< slave state machine */ + ec_fsm_slave_scan_t fsm_slave_scan; /**< slave state machine */ + ec_fsm_sii_t fsm_sii; /**< SII state machine */ +}; + +/*****************************************************************************/ + +void ec_fsm_master_init(ec_fsm_master_t *, ec_master_t *, ec_datagram_t *); +void ec_fsm_master_clear(ec_fsm_master_t *); + +void ec_fsm_master_reset(ec_fsm_master_t *); + +int ec_fsm_master_exec(ec_fsm_master_t *); +int ec_fsm_master_idle(const ec_fsm_master_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_pdo.c b/net/ethercat/master/fsm_pdo.c new file mode 100644 index 000000000000..d6fe964ad33d --- /dev/null +++ b/net/ethercat/master/fsm_pdo.c @@ -0,0 +1,806 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT PDO configuration state machine. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "slave_config.h" + +#include "fsm_pdo.h" + +/*****************************************************************************/ + +void ec_fsm_pdo_read_state_start(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_read_state_pdo_count(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_read_state_pdo(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_read_state_pdo_entries(ec_fsm_pdo_t *, ec_datagram_t *); + +void ec_fsm_pdo_read_action_next_sync(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_read_action_next_pdo(ec_fsm_pdo_t *, ec_datagram_t *); + +void ec_fsm_pdo_conf_state_start(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_state_read_mapping(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_state_mapping(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_state_zero_pdo_count(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_state_assign_pdo(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_state_set_pdo_count(ec_fsm_pdo_t *, ec_datagram_t *); + +void ec_fsm_pdo_conf_action_next_sync(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_action_pdo_mapping(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_action_check_mapping(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_action_next_pdo_mapping(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_action_check_assignment(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_conf_action_assign_pdo(ec_fsm_pdo_t *, ec_datagram_t *); + +void ec_fsm_pdo_state_end(ec_fsm_pdo_t *, ec_datagram_t *); +void ec_fsm_pdo_state_error(ec_fsm_pdo_t *, ec_datagram_t *); + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_pdo_init( + ec_fsm_pdo_t *fsm, /**< PDO configuration state machine. */ + ec_fsm_coe_t *fsm_coe /**< CoE state machine to use */ + ) +{ + fsm->fsm_coe = fsm_coe; + ec_fsm_pdo_entry_init(&fsm->fsm_pdo_entry, fsm_coe); + ec_pdo_list_init(&fsm->pdos); + ec_sdo_request_init(&fsm->request); + ec_pdo_init(&fsm->slave_pdo); +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_pdo_clear( + ec_fsm_pdo_t *fsm /**< PDO configuration state machine. */ + ) +{ + ec_fsm_pdo_entry_clear(&fsm->fsm_pdo_entry); + ec_pdo_list_clear(&fsm->pdos); + ec_sdo_request_clear(&fsm->request); + ec_pdo_clear(&fsm->slave_pdo); +} + +/*****************************************************************************/ + +/** Print the current and desired PDO assignment. + */ +void ec_fsm_pdo_print( + ec_fsm_pdo_t *fsm /**< PDO configuration state machine. */ + ) +{ + printk("Currently assigned PDOs: "); + ec_pdo_list_print(&fsm->sync->pdos); + printk(". PDOs to assign: "); + ec_pdo_list_print(&fsm->pdos); + printk("\n"); +} + +/*****************************************************************************/ + +/** Start reading the PDO configuration. + */ +void ec_fsm_pdo_start_reading( + ec_fsm_pdo_t *fsm, /**< PDO configuration state machine. */ + ec_slave_t *slave /**< slave to configure */ + ) +{ + fsm->slave = slave; + fsm->state = ec_fsm_pdo_read_state_start; +} + +/*****************************************************************************/ + +/** Start writing the PDO configuration. + */ +void ec_fsm_pdo_start_configuration( + ec_fsm_pdo_t *fsm, /**< PDO configuration state machine. */ + ec_slave_t *slave /**< slave to configure */ + ) +{ + fsm->slave = slave; + fsm->state = ec_fsm_pdo_conf_state_start; +} + +/*****************************************************************************/ + +/** Get running state. + * + * \return false, if state machine has terminated + */ +int ec_fsm_pdo_running( + const ec_fsm_pdo_t *fsm /**< PDO configuration state machine. */ + ) +{ + return fsm->state != ec_fsm_pdo_state_end + && fsm->state != ec_fsm_pdo_state_error; +} + +/*****************************************************************************/ + +/** Executes the current state of the state machine. + * + * If the state machine's datagram is not sent or received yet, the execution + * of the state machine is delayed to the next cycle. + * + * \return false, if state machine has terminated + */ +int ec_fsm_pdo_exec( + ec_fsm_pdo_t *fsm, /**< PDO configuration state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + fsm->state(fsm, datagram); + + return ec_fsm_pdo_running(fsm); +} + +/*****************************************************************************/ + +/** Get execution result. + * + * \return true, if the state machine terminated gracefully + */ +int ec_fsm_pdo_success( + const ec_fsm_pdo_t *fsm /**< PDO configuration state machine. */ + ) +{ + return fsm->state == ec_fsm_pdo_state_end; +} + +/****************************************************************************** + * Reading state funtions. + *****************************************************************************/ + +/** Start reading PDO assignment. + */ +void ec_fsm_pdo_read_state_start( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + // read PDO assignment for first sync manager not reserved for mailbox + fsm->sync_index = 1; // next is 2 + ec_fsm_pdo_read_action_next_sync(fsm, datagram); +} + +/*****************************************************************************/ + +/** Read PDO assignment of next sync manager. + */ +void ec_fsm_pdo_read_action_next_sync( + ec_fsm_pdo_t *fsm, /**< finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + fsm->sync_index++; + + for (; fsm->sync_index < EC_MAX_SYNC_MANAGERS; fsm->sync_index++) { + if (!(fsm->sync = ec_slave_get_sync(slave, fsm->sync_index))) + continue; + + EC_SLAVE_DBG(slave, 1, "Reading PDO assignment of SM%u.\n", + fsm->sync_index); + + ec_pdo_list_clear_pdos(&fsm->pdos); + + ecrt_sdo_request_index(&fsm->request, 0x1C10 + fsm->sync_index, 0); + ecrt_sdo_request_read(&fsm->request); + fsm->state = ec_fsm_pdo_read_state_pdo_count; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately + return; + } + + EC_SLAVE_DBG(slave, 1, "Reading of PDO configuration finished.\n"); + + ec_pdo_list_clear_pdos(&fsm->pdos); + fsm->state = ec_fsm_pdo_state_end; +} + +/*****************************************************************************/ + +/** Count assigned PDOs. + */ +void ec_fsm_pdo_read_state_pdo_count( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_ERR(fsm->slave, "Failed to read number of assigned PDOs" + " for SM%u.\n", fsm->sync_index); + ec_fsm_pdo_read_action_next_sync(fsm, datagram); + return; + } + + if (fsm->request.data_size != sizeof(uint8_t)) { + EC_SLAVE_ERR(fsm->slave, "Invalid data size %zu returned" + " when uploading SDO 0x%04X:%02X.\n", fsm->request.data_size, + fsm->request.index, fsm->request.subindex); + ec_fsm_pdo_read_action_next_sync(fsm, datagram); + return; + } + fsm->pdo_count = EC_READ_U8(fsm->request.data); + + EC_SLAVE_DBG(fsm->slave, 1, "%u PDOs assigned.\n", fsm->pdo_count); + + // read first PDO + fsm->pdo_pos = 1; + ec_fsm_pdo_read_action_next_pdo(fsm, datagram); +} + +/*****************************************************************************/ + +/** Read next PDO. + */ +void ec_fsm_pdo_read_action_next_pdo( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (fsm->pdo_pos <= fsm->pdo_count) { + ecrt_sdo_request_index(&fsm->request, 0x1C10 + fsm->sync_index, + fsm->pdo_pos); + ecrt_sdo_request_read(&fsm->request); + fsm->state = ec_fsm_pdo_read_state_pdo; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately + return; + } + + // finished reading PDO configuration + + ec_pdo_list_copy(&fsm->sync->pdos, &fsm->pdos); + ec_pdo_list_clear_pdos(&fsm->pdos); + + // next sync manager + ec_fsm_pdo_read_action_next_sync(fsm, datagram); +} + +/*****************************************************************************/ + +/** Fetch PDO information. + */ +void ec_fsm_pdo_read_state_pdo( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_ERR(fsm->slave, "Failed to read index of" + " assigned PDO %u from SM%u.\n", + fsm->pdo_pos, fsm->sync_index); + ec_fsm_pdo_read_action_next_sync(fsm, datagram); + return; + } + + if (fsm->request.data_size != sizeof(uint16_t)) { + EC_SLAVE_ERR(fsm->slave, "Invalid data size %zu returned" + " when uploading SDO 0x%04X:%02X.\n", fsm->request.data_size, + fsm->request.index, fsm->request.subindex); + ec_fsm_pdo_read_action_next_sync(fsm, datagram); + return; + } + + if (!(fsm->pdo = (ec_pdo_t *) + kmalloc(sizeof(ec_pdo_t), GFP_KERNEL))) { + EC_SLAVE_ERR(fsm->slave, "Failed to allocate PDO.\n"); + ec_fsm_pdo_read_action_next_sync(fsm, datagram); + return; + } + + ec_pdo_init(fsm->pdo); + fsm->pdo->index = EC_READ_U16(fsm->request.data); + fsm->pdo->sync_index = fsm->sync_index; + + EC_SLAVE_DBG(fsm->slave, 1, "PDO 0x%04X.\n", fsm->pdo->index); + + list_add_tail(&fsm->pdo->list, &fsm->pdos.list); + + fsm->state = ec_fsm_pdo_read_state_pdo_entries; + ec_fsm_pdo_entry_start_reading(&fsm->fsm_pdo_entry, fsm->slave, fsm->pdo); + fsm->state(fsm, datagram); // execute immediately +} + +/*****************************************************************************/ + +/** Fetch PDO information. + */ +void ec_fsm_pdo_read_state_pdo_entries( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_pdo_entry_exec(&fsm->fsm_pdo_entry, datagram)) { + return; + } + + if (!ec_fsm_pdo_entry_success(&fsm->fsm_pdo_entry)) { + EC_SLAVE_ERR(fsm->slave, "Failed to read mapped PDO entries" + " for PDO 0x%04X.\n", fsm->pdo->index); + ec_fsm_pdo_read_action_next_sync(fsm, datagram); + return; + } + + // next PDO + fsm->pdo_pos++; + ec_fsm_pdo_read_action_next_pdo(fsm, datagram); +} + +/****************************************************************************** + * Writing state functions. + *****************************************************************************/ + +/** Start PDO configuration. + */ +void ec_fsm_pdo_conf_state_start( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (!fsm->slave->config) { + fsm->state = ec_fsm_pdo_state_end; + return; + } + + fsm->sync_index = 1; // next is 2 + ec_fsm_pdo_conf_action_next_sync(fsm, datagram); +} + +/*****************************************************************************/ + +/** Assign next PDO. + * + * \return Next PDO, or NULL. + */ +ec_pdo_t *ec_fsm_pdo_conf_action_next_pdo( + const ec_fsm_pdo_t *fsm, /**< PDO configuration state machine. */ + const struct list_head *list /**< current PDO list item */ + ) +{ + list = list->next; + if (list == &fsm->pdos.list) + return NULL; // no next PDO + return list_entry(list, ec_pdo_t, list); +} + +/*****************************************************************************/ + +/** Get the next sync manager for a pdo configuration. + */ +void ec_fsm_pdo_conf_action_next_sync( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + fsm->sync_index++; + + for (; fsm->sync_index < EC_MAX_SYNC_MANAGERS; fsm->sync_index++) { + if (!fsm->slave->config) { + // slave configuration removed in the meantime + fsm->state = ec_fsm_pdo_state_error; + return; + } + + if (ec_pdo_list_copy(&fsm->pdos, + &fsm->slave->config->sync_configs[fsm->sync_index].pdos)) + { + fsm->state = ec_fsm_pdo_state_error; + return; + } + + if (!(fsm->sync = ec_slave_get_sync(fsm->slave, fsm->sync_index))) { + if (!list_empty(&fsm->pdos.list)) + EC_SLAVE_WARN(fsm->slave, "PDOs configured for SM%u," + " but slave does not provide the" + " sync manager information!\n", + fsm->sync_index); + continue; + } + + // get first configured PDO + if (!(fsm->pdo = + ec_fsm_pdo_conf_action_next_pdo(fsm, &fsm->pdos.list))) { + // no pdos configured + ec_fsm_pdo_conf_action_check_assignment(fsm, datagram); + return; + } + + ec_fsm_pdo_conf_action_pdo_mapping(fsm, datagram); + return; + } + + fsm->state = ec_fsm_pdo_state_end; +} + +/*****************************************************************************/ + +/** Check if the mapping has to be read, otherwise start to configure it. + */ +void ec_fsm_pdo_conf_action_pdo_mapping( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + const ec_pdo_t *assigned_pdo; + + fsm->slave_pdo.index = fsm->pdo->index; + + if ((assigned_pdo = ec_slave_find_pdo(fsm->slave, fsm->pdo->index))) { + ec_pdo_copy_entries(&fsm->slave_pdo, assigned_pdo); + } else { // configured PDO is not assigned and thus unknown + ec_pdo_clear_entries(&fsm->slave_pdo); + } + + if (list_empty(&fsm->slave_pdo.entries)) { + EC_SLAVE_DBG(fsm->slave, 1, "Reading mapping of PDO 0x%04X.\n", + fsm->pdo->index); + + // pdo mapping is unknown; start loading it + ec_fsm_pdo_entry_start_reading(&fsm->fsm_pdo_entry, fsm->slave, + &fsm->slave_pdo); + fsm->state = ec_fsm_pdo_conf_state_read_mapping; + fsm->state(fsm, datagram); // execute immediately + return; + } + + // pdo mapping is known, check if it most be re-configured + ec_fsm_pdo_conf_action_check_mapping(fsm, datagram); +} + +/*****************************************************************************/ + +/** Execute the PDO entry state machine to read the current PDO's mapping. + */ +void ec_fsm_pdo_conf_state_read_mapping( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_pdo_entry_exec(&fsm->fsm_pdo_entry, datagram)) { + return; + } + + if (!ec_fsm_pdo_entry_success(&fsm->fsm_pdo_entry)) + EC_SLAVE_WARN(fsm->slave, + "Failed to read PDO entries for PDO 0x%04X.\n", + fsm->pdo->index); + + // check if the mapping must be re-configured + ec_fsm_pdo_conf_action_check_mapping(fsm, datagram); +} + +/*****************************************************************************/ + +/** Check if the mapping has to be re-configured. + * + * \todo Display mapping differences. + */ +void ec_fsm_pdo_conf_action_check_mapping( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + // check, if slave supports PDO configuration + if ((fsm->slave->sii.mailbox_protocols & EC_MBOX_COE) + && fsm->slave->sii.has_general + && fsm->slave->sii.coe_details.enable_pdo_configuration) { + + // always write PDO mapping + ec_fsm_pdo_entry_start_configuration(&fsm->fsm_pdo_entry, fsm->slave, + fsm->pdo, &fsm->slave_pdo); + fsm->state = ec_fsm_pdo_conf_state_mapping; + fsm->state(fsm, datagram); // execure immediately + return; + } + else if (!ec_pdo_equal_entries(fsm->pdo, &fsm->slave_pdo)) { + EC_SLAVE_WARN(fsm->slave, "Slave does not support" + " changing the PDO mapping!\n"); + EC_SLAVE_WARN(fsm->slave, ""); + printk("Currently mapped PDO entries: "); + ec_pdo_print_entries(&fsm->slave_pdo); + printk(". Entries to map: "); + ec_pdo_print_entries(fsm->pdo); + printk("\n"); + } + + ec_fsm_pdo_conf_action_next_pdo_mapping(fsm, datagram); +} + +/*****************************************************************************/ + +/** Let the PDO entry state machine configure the current PDO's mapping. + */ +void ec_fsm_pdo_conf_state_mapping( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_pdo_entry_exec(&fsm->fsm_pdo_entry, datagram)) { + return; + } + + if (!ec_fsm_pdo_entry_success(&fsm->fsm_pdo_entry)) + EC_SLAVE_WARN(fsm->slave, + "Failed to configure mapping of PDO 0x%04X.\n", + fsm->pdo->index); + + ec_fsm_pdo_conf_action_next_pdo_mapping(fsm, datagram); +} + +/*****************************************************************************/ + +/** Check mapping of next PDO, otherwise configure assignment. + */ +void ec_fsm_pdo_conf_action_next_pdo_mapping( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + // get next configured PDO + if (!(fsm->pdo = ec_fsm_pdo_conf_action_next_pdo(fsm, &fsm->pdo->list))) { + // no more configured pdos + ec_fsm_pdo_conf_action_check_assignment(fsm, datagram); + return; + } + + ec_fsm_pdo_conf_action_pdo_mapping(fsm, datagram); +} + +/*****************************************************************************/ + +/** Check if the PDO assignment of the current SM has to be re-configured. + */ +void ec_fsm_pdo_conf_action_check_assignment( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if ((fsm->slave->sii.mailbox_protocols & EC_MBOX_COE) + && fsm->slave->sii.has_general + && fsm->slave->sii.coe_details.enable_pdo_assign) { + + // always write PDO assignment + if (fsm->slave->master->debug_level) { + EC_SLAVE_DBG(fsm->slave, 1, "Setting PDO assignment of SM%u:\n", + fsm->sync_index); + EC_SLAVE_DBG(fsm->slave, 1, ""); ec_fsm_pdo_print(fsm); + } + + if (ec_sdo_request_alloc(&fsm->request, 2)) { + fsm->state = ec_fsm_pdo_state_error; + return; + } + + // set mapped PDO count to zero + EC_WRITE_U8(fsm->request.data, 0); // zero PDOs mapped + fsm->request.data_size = 1; + ecrt_sdo_request_index(&fsm->request, 0x1C10 + fsm->sync_index, 0); + ecrt_sdo_request_write(&fsm->request); + + EC_SLAVE_DBG(fsm->slave, 1, "Setting number of assigned" + " PDOs to zero.\n"); + + fsm->state = ec_fsm_pdo_conf_state_zero_pdo_count; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately + return; + } + else if (!ec_pdo_list_equal(&fsm->sync->pdos, &fsm->pdos)) { + EC_SLAVE_WARN(fsm->slave, "Slave does not support assigning PDOs!\n"); + EC_SLAVE_WARN(fsm->slave, ""); ec_fsm_pdo_print(fsm); + } + + ec_fsm_pdo_conf_action_next_sync(fsm, datagram); +} + +/*****************************************************************************/ + +/** Set the number of assigned PDOs to zero. + */ +void ec_fsm_pdo_conf_state_zero_pdo_count( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_WARN(fsm->slave, "Failed to clear PDO assignment of SM%u.\n", + fsm->sync_index); + EC_SLAVE_WARN(fsm->slave, ""); + ec_fsm_pdo_print(fsm); + ec_fsm_pdo_conf_action_next_sync(fsm, datagram); + return; + } + + // the sync manager's assigned PDOs have been cleared + ec_pdo_list_clear_pdos(&fsm->sync->pdos); + + // assign all PDOs belonging to the current sync manager + + // find first PDO + if (!(fsm->pdo = ec_fsm_pdo_conf_action_next_pdo(fsm, &fsm->pdos.list))) { + // check for mapping to be altered + ec_fsm_pdo_conf_action_next_sync(fsm, datagram); + return; + } + + // assign first PDO + fsm->pdo_pos = 1; + ec_fsm_pdo_conf_action_assign_pdo(fsm, datagram); +} + +/*****************************************************************************/ + +/** Assign a PDO. + */ +void ec_fsm_pdo_conf_action_assign_pdo( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + EC_WRITE_U16(fsm->request.data, fsm->pdo->index); + fsm->request.data_size = 2; + ecrt_sdo_request_index(&fsm->request, + 0x1C10 + fsm->sync_index, fsm->pdo_pos); + ecrt_sdo_request_write(&fsm->request); + + EC_SLAVE_DBG(fsm->slave, 1, "Assigning PDO 0x%04X at position %u.\n", + fsm->pdo->index, fsm->pdo_pos); + + fsm->state = ec_fsm_pdo_conf_state_assign_pdo; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately +} + +/*****************************************************************************/ + +/** Add a PDO to the sync managers PDO assignment. + */ +void ec_fsm_pdo_conf_state_assign_pdo( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_WARN(fsm->slave, "Failed to assign PDO 0x%04X at position %u" + " of SM%u.\n", + fsm->pdo->index, fsm->pdo_pos, fsm->sync_index); + EC_SLAVE_WARN(fsm->slave, ""); ec_fsm_pdo_print(fsm); + fsm->state = ec_fsm_pdo_state_error; + return; + } + + // find next PDO + if (!(fsm->pdo = ec_fsm_pdo_conf_action_next_pdo(fsm, &fsm->pdo->list))) { + // no more PDOs to assign, set PDO count + EC_WRITE_U8(fsm->request.data, fsm->pdo_pos); + fsm->request.data_size = 1; + ecrt_sdo_request_index(&fsm->request, 0x1C10 + fsm->sync_index, 0); + ecrt_sdo_request_write(&fsm->request); + + EC_SLAVE_DBG(fsm->slave, 1, + "Setting number of assigned PDOs to %u.\n", + fsm->pdo_pos); + + fsm->state = ec_fsm_pdo_conf_state_set_pdo_count; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately + return; + } + + // add next PDO to assignment + fsm->pdo_pos++; + ec_fsm_pdo_conf_action_assign_pdo(fsm, datagram); +} + +/*****************************************************************************/ + +/** Set the number of assigned PDOs. + */ +void ec_fsm_pdo_conf_state_set_pdo_count( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_WARN(fsm->slave, "Failed to set number of" + " assigned PDOs of SM%u.\n", fsm->sync_index); + EC_SLAVE_WARN(fsm->slave, ""); ec_fsm_pdo_print(fsm); + fsm->state = ec_fsm_pdo_state_error; + return; + } + + // PDOs have been configured + ec_pdo_list_copy(&fsm->sync->pdos, &fsm->pdos); + + EC_SLAVE_DBG(fsm->slave, 1, "Successfully configured" + " PDO assignment of SM%u.\n", fsm->sync_index); + + // check if PDO mapping has to be altered + ec_fsm_pdo_conf_action_next_sync(fsm, datagram); +} + +/****************************************************************************** + * Common state functions + *****************************************************************************/ + +/** State: ERROR. + */ +void ec_fsm_pdo_state_error( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +} + +/*****************************************************************************/ + +/** State: END. + */ +void ec_fsm_pdo_state_end( + ec_fsm_pdo_t *fsm, /**< Finite state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_pdo.h b/net/ethercat/master/fsm_pdo.h new file mode 100644 index 000000000000..bd3b36565a9f --- /dev/null +++ b/net/ethercat/master/fsm_pdo.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT PDO configuration state machine structures. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_PDO_H__ +#define __EC_FSM_PDO_H__ + +#include "globals.h" +#include "datagram.h" +#include "fsm_coe.h" +#include "fsm_pdo_entry.h" + +/*****************************************************************************/ + +/** + * \see ec_fsm_pdo + */ +typedef struct ec_fsm_pdo ec_fsm_pdo_t; + +/** PDO configuration state machine. + */ +struct ec_fsm_pdo +{ + void (*state)(ec_fsm_pdo_t *, ec_datagram_t *); /**< State function. */ + ec_fsm_coe_t *fsm_coe; /**< CoE state machine to use. */ + ec_fsm_pdo_entry_t fsm_pdo_entry; /**< PDO entry state machine. */ + ec_pdo_list_t pdos; /**< PDO configuration. */ + ec_sdo_request_t request; /**< SDO request. */ + ec_pdo_t slave_pdo; /**< PDO actually appearing in a slave. */ + + ec_slave_t *slave; /**< Slave the FSM runs on. */ + uint8_t sync_index; /**< Current sync manager index. */ + ec_sync_t *sync; /**< Current sync manager. */ + ec_pdo_t *pdo; /**< Current PDO. */ + unsigned int pdo_pos; /**< Assignment position of current PDOs. */ + unsigned int pdo_count; /**< Number of assigned PDOs. */ +}; + +/*****************************************************************************/ + +void ec_fsm_pdo_init(ec_fsm_pdo_t *, ec_fsm_coe_t *); +void ec_fsm_pdo_clear(ec_fsm_pdo_t *); + +void ec_fsm_pdo_start_reading(ec_fsm_pdo_t *, ec_slave_t *); +void ec_fsm_pdo_start_configuration(ec_fsm_pdo_t *, ec_slave_t *); + +int ec_fsm_pdo_exec(ec_fsm_pdo_t *, ec_datagram_t *); +int ec_fsm_pdo_success(const ec_fsm_pdo_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_pdo_entry.c b/net/ethercat/master/fsm_pdo_entry.c new file mode 100644 index 000000000000..16208d2664be --- /dev/null +++ b/net/ethercat/master/fsm_pdo_entry.c @@ -0,0 +1,541 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT PDO mapping state machine. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "slave_config.h" + +#include "fsm_pdo_entry.h" + +/*****************************************************************************/ + +void ec_fsm_pdo_entry_read_state_start(ec_fsm_pdo_entry_t *, ec_datagram_t *); +void ec_fsm_pdo_entry_read_state_count(ec_fsm_pdo_entry_t *, ec_datagram_t *); +void ec_fsm_pdo_entry_read_state_entry(ec_fsm_pdo_entry_t *, ec_datagram_t *); + +void ec_fsm_pdo_entry_read_action_next(ec_fsm_pdo_entry_t *, ec_datagram_t *); + +void ec_fsm_pdo_entry_conf_state_start(ec_fsm_pdo_entry_t *, ec_datagram_t *); +void ec_fsm_pdo_entry_conf_state_zero_entry_count(ec_fsm_pdo_entry_t *, + ec_datagram_t *); +void ec_fsm_pdo_entry_conf_state_map_entry(ec_fsm_pdo_entry_t *, + ec_datagram_t *); +void ec_fsm_pdo_entry_conf_state_set_entry_count(ec_fsm_pdo_entry_t *, + ec_datagram_t *); + +void ec_fsm_pdo_entry_conf_action_map(ec_fsm_pdo_entry_t *, ec_datagram_t *); + +void ec_fsm_pdo_entry_state_end(ec_fsm_pdo_entry_t *, ec_datagram_t *); +void ec_fsm_pdo_entry_state_error(ec_fsm_pdo_entry_t *, ec_datagram_t *); + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_pdo_entry_init( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_fsm_coe_t *fsm_coe /**< CoE state machine to use. */ + ) +{ + fsm->fsm_coe = fsm_coe; + ec_sdo_request_init(&fsm->request); +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_pdo_entry_clear( + ec_fsm_pdo_entry_t *fsm /**< PDO mapping state machine. */ + ) +{ + ec_sdo_request_clear(&fsm->request); +} + +/*****************************************************************************/ + +/** Print the current and desired PDO mapping. + */ +void ec_fsm_pdo_entry_print( + ec_fsm_pdo_entry_t *fsm /**< PDO mapping state machine. */ + ) +{ + printk("Currently mapped PDO entries: "); + ec_pdo_print_entries(fsm->cur_pdo); + printk(". Entries to map: "); + ec_pdo_print_entries(fsm->source_pdo); + printk("\n"); +} + +/*****************************************************************************/ + +/** Start reading a PDO's entries. + */ +void ec_fsm_pdo_entry_start_reading( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_slave_t *slave, /**< Slave to configure. */ + ec_pdo_t *pdo /**< PDO to read entries for. */ + ) +{ + fsm->slave = slave; + fsm->target_pdo = pdo; + + ec_pdo_clear_entries(fsm->target_pdo); + + fsm->state = ec_fsm_pdo_entry_read_state_start; +} + +/*****************************************************************************/ + +/** Start PDO mapping state machine. + */ +void ec_fsm_pdo_entry_start_configuration( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_slave_t *slave, /**< Slave to configure. */ + const ec_pdo_t *pdo, /**< PDO with the desired entries. */ + const ec_pdo_t *cur_pdo /**< Current PDO mapping. */ + ) +{ + fsm->slave = slave; + fsm->source_pdo = pdo; + fsm->cur_pdo = cur_pdo; + + if (fsm->slave->master->debug_level) { + EC_SLAVE_DBG(slave, 1, "Changing mapping of PDO 0x%04X.\n", + pdo->index); + EC_SLAVE_DBG(slave, 1, ""); ec_fsm_pdo_entry_print(fsm); + } + + fsm->state = ec_fsm_pdo_entry_conf_state_start; +} + +/*****************************************************************************/ + +/** Get running state. + * + * \return false, if state machine has terminated + */ +int ec_fsm_pdo_entry_running( + const ec_fsm_pdo_entry_t *fsm /**< PDO mapping state machine. */ + ) +{ + return fsm->state != ec_fsm_pdo_entry_state_end + && fsm->state != ec_fsm_pdo_entry_state_error; +} + +/*****************************************************************************/ + +/** Executes the current state. + * + * \return false, if state machine has terminated + */ +int ec_fsm_pdo_entry_exec( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + fsm->state(fsm, datagram); + + return ec_fsm_pdo_entry_running(fsm); +} + +/*****************************************************************************/ + +/** Get execution result. + * + * \return true, if the state machine terminated gracefully + */ +int ec_fsm_pdo_entry_success( + const ec_fsm_pdo_entry_t *fsm /**< PDO mapping state machine. */ + ) +{ + return fsm->state == ec_fsm_pdo_entry_state_end; +} + +/****************************************************************************** + * Reading state functions. + *****************************************************************************/ + +/** Request reading the number of mapped PDO entries. + */ +void ec_fsm_pdo_entry_read_state_start( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ecrt_sdo_request_index(&fsm->request, fsm->target_pdo->index, 0); + ecrt_sdo_request_read(&fsm->request); + + fsm->state = ec_fsm_pdo_entry_read_state_count; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately +} + +/*****************************************************************************/ + +/** Read number of mapped PDO entries. + */ +void ec_fsm_pdo_entry_read_state_count( + ec_fsm_pdo_entry_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_ERR(fsm->slave, + "Failed to read number of mapped PDO entries.\n"); + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + + if (fsm->request.data_size != sizeof(uint8_t)) { + EC_SLAVE_ERR(fsm->slave, "Invalid data size %zu at uploading" + " SDO 0x%04X:%02X.\n", + fsm->request.data_size, fsm->request.index, + fsm->request.subindex); + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + + fsm->entry_count = EC_READ_U8(fsm->request.data); + + EC_SLAVE_DBG(fsm->slave, 1, "%u PDO entries mapped.\n", fsm->entry_count); + + // read first PDO entry + fsm->entry_pos = 1; + ec_fsm_pdo_entry_read_action_next(fsm, datagram); +} + +/*****************************************************************************/ + +/** Read next PDO entry. + */ +void ec_fsm_pdo_entry_read_action_next( + ec_fsm_pdo_entry_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (fsm->entry_pos <= fsm->entry_count) { + ecrt_sdo_request_index(&fsm->request, fsm->target_pdo->index, + fsm->entry_pos); + ecrt_sdo_request_read(&fsm->request); + fsm->state = ec_fsm_pdo_entry_read_state_entry; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately + return; + } + + // finished reading entries. + fsm->state = ec_fsm_pdo_entry_state_end; +} + +/*****************************************************************************/ + +/** Read PDO entry information. + */ +void ec_fsm_pdo_entry_read_state_entry( + ec_fsm_pdo_entry_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_ERR(fsm->slave, "Failed to read mapped PDO entry.\n"); + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + + if (fsm->request.data_size != sizeof(uint32_t)) { + EC_SLAVE_ERR(fsm->slave, "Invalid data size %zu at" + " uploading SDO 0x%04X:%02X.\n", + fsm->request.data_size, fsm->request.index, + fsm->request.subindex); + fsm->state = ec_fsm_pdo_entry_state_error; + } else { + uint32_t pdo_entry_info; + ec_pdo_entry_t *pdo_entry; + + pdo_entry_info = EC_READ_U32(fsm->request.data); + + if (!(pdo_entry = (ec_pdo_entry_t *) + kmalloc(sizeof(ec_pdo_entry_t), GFP_KERNEL))) { + EC_SLAVE_ERR(fsm->slave, "Failed to allocate PDO entry.\n"); + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + + ec_pdo_entry_init(pdo_entry); + pdo_entry->index = pdo_entry_info >> 16; + pdo_entry->subindex = (pdo_entry_info >> 8) & 0xFF; + pdo_entry->bit_length = pdo_entry_info & 0xFF; + + if (!pdo_entry->index && !pdo_entry->subindex) { + if (ec_pdo_entry_set_name(pdo_entry, "Gap")) { + ec_pdo_entry_clear(pdo_entry); + kfree(pdo_entry); + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + } + + EC_SLAVE_DBG(fsm->slave, 1, + "PDO entry 0x%04X:%02X, %u bit, \"%s\".\n", + pdo_entry->index, pdo_entry->subindex, + pdo_entry->bit_length, + pdo_entry->name ? pdo_entry->name : "???"); + + list_add_tail(&pdo_entry->list, &fsm->target_pdo->entries); + + // next PDO entry + fsm->entry_pos++; + ec_fsm_pdo_entry_read_action_next(fsm, datagram); + } +} + +/****************************************************************************** + * Configuration state functions. + *****************************************************************************/ + +/** Start PDO mapping. + */ +void ec_fsm_pdo_entry_conf_state_start( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_sdo_request_alloc(&fsm->request, 4)) { + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + + // set mapped PDO entry count to zero + EC_WRITE_U8(fsm->request.data, 0); + fsm->request.data_size = 1; + ecrt_sdo_request_index(&fsm->request, fsm->source_pdo->index, 0); + ecrt_sdo_request_write(&fsm->request); + + EC_SLAVE_DBG(fsm->slave, 1, "Setting entry count to zero.\n"); + + fsm->state = ec_fsm_pdo_entry_conf_state_zero_entry_count; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately +} + +/*****************************************************************************/ + +/** Process next PDO entry. + * + * \return Next PDO entry, or NULL. + */ +ec_pdo_entry_t *ec_fsm_pdo_entry_conf_next_entry( + const ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + const struct list_head *list /**< current entry list item */ + ) +{ + list = list->next; + if (list == &fsm->source_pdo->entries) + return NULL; // no next entry + return list_entry(list, ec_pdo_entry_t, list); +} + +/*****************************************************************************/ + +/** Set the number of mapped entries to zero. + */ +void ec_fsm_pdo_entry_conf_state_zero_entry_count( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_WARN(fsm->slave, "Failed to clear PDO mapping.\n"); + EC_SLAVE_WARN(fsm->slave, ""); ec_fsm_pdo_entry_print(fsm); + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + + // find first entry + if (!(fsm->entry = ec_fsm_pdo_entry_conf_next_entry( + fsm, &fsm->source_pdo->entries))) { + + EC_SLAVE_DBG(fsm->slave, 1, "No entries to map.\n"); + + fsm->state = ec_fsm_pdo_entry_state_end; // finished + return; + } + + // add first entry + fsm->entry_pos = 1; + ec_fsm_pdo_entry_conf_action_map(fsm, datagram); +} + +/*****************************************************************************/ + +/** Starts to add a PDO entry. + */ +void ec_fsm_pdo_entry_conf_action_map( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + uint32_t value; + + EC_SLAVE_DBG(fsm->slave, 1, "Mapping PDO entry 0x%04X:%02X (%u bit)" + " at position %u.\n", + fsm->entry->index, fsm->entry->subindex, + fsm->entry->bit_length, fsm->entry_pos); + + value = fsm->entry->index << 16 + | fsm->entry->subindex << 8 | fsm->entry->bit_length; + EC_WRITE_U32(fsm->request.data, value); + fsm->request.data_size = 4; + ecrt_sdo_request_index(&fsm->request, fsm->source_pdo->index, + fsm->entry_pos); + ecrt_sdo_request_write(&fsm->request); + + fsm->state = ec_fsm_pdo_entry_conf_state_map_entry; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately +} + +/*****************************************************************************/ + +/** Add a PDO entry. + */ +void ec_fsm_pdo_entry_conf_state_map_entry( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_WARN(fsm->slave, "Failed to map PDO entry" + " 0x%04X:%02X (%u bit) to position %u.\n", + fsm->entry->index, fsm->entry->subindex, + fsm->entry->bit_length, fsm->entry_pos); + EC_SLAVE_WARN(fsm->slave, ""); ec_fsm_pdo_entry_print(fsm); + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + + // find next entry + if (!(fsm->entry = ec_fsm_pdo_entry_conf_next_entry( + fsm, &fsm->entry->list))) { + + // No more entries to add. Write entry count. + EC_WRITE_U8(fsm->request.data, fsm->entry_pos); + fsm->request.data_size = 1; + ecrt_sdo_request_index(&fsm->request, fsm->source_pdo->index, 0); + ecrt_sdo_request_write(&fsm->request); + + EC_SLAVE_DBG(fsm->slave, 1, "Setting number of PDO entries to %u.\n", + fsm->entry_pos); + + fsm->state = ec_fsm_pdo_entry_conf_state_set_entry_count; + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request); + ec_fsm_coe_exec(fsm->fsm_coe, datagram); // execute immediately + return; + } + + // add next entry + fsm->entry_pos++; + ec_fsm_pdo_entry_conf_action_map(fsm, datagram); +} + +/*****************************************************************************/ + +/** Set the number of entries. + */ +void ec_fsm_pdo_entry_conf_state_set_entry_count( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_WARN(fsm->slave, "Failed to set number of entries.\n"); + EC_SLAVE_WARN(fsm->slave, ""); ec_fsm_pdo_entry_print(fsm); + fsm->state = ec_fsm_pdo_entry_state_error; + return; + } + + EC_SLAVE_DBG(fsm->slave, 1, "Successfully configured" + " mapping for PDO 0x%04X.\n", fsm->source_pdo->index); + + fsm->state = ec_fsm_pdo_entry_state_end; // finished +} + +/****************************************************************************** + * Common state functions + *****************************************************************************/ + +/** State: ERROR. + */ +void ec_fsm_pdo_entry_state_error( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +} + +/*****************************************************************************/ + +/** State: END. + */ +void ec_fsm_pdo_entry_state_end( + ec_fsm_pdo_entry_t *fsm, /**< PDO mapping state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_pdo_entry.h b/net/ethercat/master/fsm_pdo_entry.h new file mode 100644 index 000000000000..af15e3af88f2 --- /dev/null +++ b/net/ethercat/master/fsm_pdo_entry.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT PDO entry configuration state machine structures. + */ + +/*****************************************************************************/ + +#ifndef __EC_FSM_PDO_ENTRY_H__ +#define __EC_FSM_PDO_ENTRY_H__ + +#include "globals.h" +#include "datagram.h" +#include "fsm_coe.h" + +/*****************************************************************************/ + +/** + * \see ec_fsm_pdo_entry + */ +typedef struct ec_fsm_pdo_entry ec_fsm_pdo_entry_t; + +/** PDO configuration state machine. + */ +struct ec_fsm_pdo_entry +{ + void (*state)(ec_fsm_pdo_entry_t *, ec_datagram_t *); /**< state function + */ + ec_fsm_coe_t *fsm_coe; /**< CoE state machine to use */ + ec_sdo_request_t request; /**< SDO request. */ + + ec_slave_t *slave; /**< Slave the FSM runs on. */ + ec_pdo_t *target_pdo; /**< PDO to read the mapping for. */ + const ec_pdo_t *source_pdo; /**< PDO with desired mapping. */ + const ec_pdo_t *cur_pdo; /**< PDO with current mapping (display only). */ + const ec_pdo_entry_t *entry; /**< Current entry. */ + unsigned int entry_count; /**< Number of entries. */ + unsigned int entry_pos; /**< Position in PDO mapping. */ +}; + +/*****************************************************************************/ + +void ec_fsm_pdo_entry_init(ec_fsm_pdo_entry_t *, ec_fsm_coe_t *); +void ec_fsm_pdo_entry_clear(ec_fsm_pdo_entry_t *); + +void ec_fsm_pdo_entry_start_reading(ec_fsm_pdo_entry_t *, ec_slave_t *, + ec_pdo_t *); +void ec_fsm_pdo_entry_start_configuration(ec_fsm_pdo_entry_t *, ec_slave_t *, + const ec_pdo_t *, const ec_pdo_t *); + +int ec_fsm_pdo_entry_exec(ec_fsm_pdo_entry_t *, ec_datagram_t *); +int ec_fsm_pdo_entry_success(const ec_fsm_pdo_entry_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_sii.c b/net/ethercat/master/fsm_sii.c new file mode 100644 index 000000000000..fb911b38fecf --- /dev/null +++ b/net/ethercat/master/fsm_sii.c @@ -0,0 +1,490 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave information interface FSM. +*/ + +/*****************************************************************************/ + +#include "globals.h" +#include "mailbox.h" +#include "master.h" +#include "fsm_sii.h" + +/** Read/write timeout [ms]. + * + * Used to calculate timeouts bsed on the jiffies counter. + * + * \attention Must be more than 10 to avoid problems on kernels that run with + * a timer interupt frequency of 100 Hz. + */ +#define SII_TIMEOUT 20 + +/** Time before evaluating answer at writing [ms]. + */ +#define SII_INHIBIT 5 + +//#define SII_DEBUG + +/*****************************************************************************/ + +void ec_fsm_sii_state_start_reading(ec_fsm_sii_t *); +void ec_fsm_sii_state_read_check(ec_fsm_sii_t *); +void ec_fsm_sii_state_read_fetch(ec_fsm_sii_t *); +void ec_fsm_sii_state_start_writing(ec_fsm_sii_t *); +void ec_fsm_sii_state_write_check(ec_fsm_sii_t *); +void ec_fsm_sii_state_write_check2(ec_fsm_sii_t *); +void ec_fsm_sii_state_end(ec_fsm_sii_t *); +void ec_fsm_sii_state_error(ec_fsm_sii_t *); + +/*****************************************************************************/ + +/** + Constructor. +*/ + +void ec_fsm_sii_init(ec_fsm_sii_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< datagram structure to use */ + ) +{ + fsm->state = NULL; + fsm->datagram = datagram; +} + +/*****************************************************************************/ + +/** + Destructor. +*/ + +void ec_fsm_sii_clear(ec_fsm_sii_t *fsm /**< finite state machine */) +{ +} + +/*****************************************************************************/ + +/** + Initializes the SII read state machine. +*/ + +void ec_fsm_sii_read(ec_fsm_sii_t *fsm, /**< finite state machine */ + ec_slave_t *slave, /**< slave to read from */ + uint16_t word_offset, /**< offset to read from */ + ec_fsm_sii_addressing_t mode /**< addressing scheme */ + ) +{ + fsm->state = ec_fsm_sii_state_start_reading; + fsm->slave = slave; + fsm->word_offset = word_offset; + fsm->mode = mode; +} + +/*****************************************************************************/ + +/** + Initializes the SII write state machine. +*/ + +void ec_fsm_sii_write(ec_fsm_sii_t *fsm, /**< finite state machine */ + ec_slave_t *slave, /**< slave to read from */ + uint16_t word_offset, /**< offset to read from */ + const uint16_t *value, /**< pointer to 2 bytes of data */ + ec_fsm_sii_addressing_t mode /**< addressing scheme */ + ) +{ + fsm->state = ec_fsm_sii_state_start_writing; + fsm->slave = slave; + fsm->word_offset = word_offset; + fsm->mode = mode; + memcpy(fsm->value, value, 2); +} + +/*****************************************************************************/ + +/** + Executes the SII state machine. + \return false, if the state machine has terminated +*/ + +int ec_fsm_sii_exec(ec_fsm_sii_t *fsm /**< finite state machine */) +{ + fsm->state(fsm); + + return fsm->state != ec_fsm_sii_state_end + && fsm->state != ec_fsm_sii_state_error; +} + +/*****************************************************************************/ + +/** + Returns, if the master startup state machine terminated with success. + \return non-zero if successful. +*/ + +int ec_fsm_sii_success(ec_fsm_sii_t *fsm /**< Finite state machine */) +{ + return fsm->state == ec_fsm_sii_state_end; +} + +/****************************************************************************** + * state functions + *****************************************************************************/ + +/** + SII state: START READING. + Starts reading the slave information interface. +*/ + +void ec_fsm_sii_state_start_reading( + ec_fsm_sii_t *fsm /**< finite state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + // initiate read operation + switch (fsm->mode) { + case EC_FSM_SII_USE_INCREMENT_ADDRESS: + ec_datagram_apwr(datagram, fsm->slave->ring_position, 0x502, 4); + break; + case EC_FSM_SII_USE_CONFIGURED_ADDRESS: + ec_datagram_fpwr(datagram, fsm->slave->station_address, 0x502, 4); + break; + } + + EC_WRITE_U8 (datagram->data, 0x80); // two address octets + EC_WRITE_U8 (datagram->data + 1, 0x01); // request read operation + EC_WRITE_U16(datagram->data + 2, fsm->word_offset); + +#ifdef SII_DEBUG + EC_SLAVE_DBG(fsm->slave, 0, "reading SII data, word %u:\n", + fsm->word_offset); + ec_print_data(datagram->data, 4); +#endif + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_sii_state_read_check; +} + +/*****************************************************************************/ + +/** + SII state: READ CHECK. + Checks, if the SII-read-datagram has been sent and issues a fetch datagram. +*/ + +void ec_fsm_sii_state_read_check( + ec_fsm_sii_t *fsm /**< finite state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_sii_state_error; + EC_SLAVE_ERR(fsm->slave, "Failed to receive SII read datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->state = ec_fsm_sii_state_error; + EC_SLAVE_ERR(fsm->slave, "Reception of SII read datagram failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + + fsm->jiffies_start = datagram->jiffies_sent; + fsm->check_once_more = 1; + + // issue check/fetch datagram + switch (fsm->mode) { + case EC_FSM_SII_USE_INCREMENT_ADDRESS: + ec_datagram_aprd(datagram, fsm->slave->ring_position, 0x502, 10); + break; + case EC_FSM_SII_USE_CONFIGURED_ADDRESS: + ec_datagram_fprd(datagram, fsm->slave->station_address, 0x502, 10); + break; + } + + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_sii_state_read_fetch; +} + +/*****************************************************************************/ + +/** + SII state: READ FETCH. + Fetches the result of an SII-read datagram. +*/ +void ec_fsm_sii_state_read_fetch( + ec_fsm_sii_t *fsm /**< finite state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_sii_state_error; + EC_SLAVE_ERR(fsm->slave, + "Failed to receive SII check/fetch datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->state = ec_fsm_sii_state_error; + EC_SLAVE_ERR(fsm->slave, + "Reception of SII check/fetch datagram failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + +#ifdef SII_DEBUG + EC_SLAVE_DBG(fsm->slave, 0, "checking SII read state:\n"); + ec_print_data(datagram->data, 10); +#endif + + if (EC_READ_U8(datagram->data + 1) & 0x20) { + EC_SLAVE_ERR(fsm->slave, "Error on last command while" + " reading from SII word 0x%04x.\n", fsm->word_offset); + fsm->state = ec_fsm_sii_state_error; + return; + } + + // check "busy bit" + if (EC_READ_U8(datagram->data + 1) & 0x81) { /* busy bit or + read operation busy */ + // still busy... timeout? + unsigned long diff_ms = + (datagram->jiffies_received - fsm->jiffies_start) * 1000 / HZ; + if (diff_ms >= SII_TIMEOUT) { + if (fsm->check_once_more) { + fsm->check_once_more = 0; + } else { + EC_SLAVE_ERR(fsm->slave, "SII: Read timeout.\n"); + fsm->state = ec_fsm_sii_state_error; + return; + } + } + + // issue check/fetch datagram again + fsm->retries = EC_FSM_RETRIES; + return; + } + + // SII value received. + memcpy(fsm->value, datagram->data + 6, 4); + fsm->state = ec_fsm_sii_state_end; +} + +/*****************************************************************************/ + +/** + SII state: START WRITING. + Starts writing a word through the slave information interface. +*/ + +void ec_fsm_sii_state_start_writing( + ec_fsm_sii_t *fsm /**< finite state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + // initiate write operation + ec_datagram_fpwr(datagram, fsm->slave->station_address, 0x502, 8); + EC_WRITE_U8 (datagram->data, 0x81); /* two address octets + + enable write access */ + EC_WRITE_U8 (datagram->data + 1, 0x02); // request write operation + EC_WRITE_U16(datagram->data + 2, fsm->word_offset); + memset(datagram->data + 4, 0x00, 2); + memcpy(datagram->data + 6, fsm->value, 2); + +#ifdef SII_DEBUG + EC_SLAVE_DBG(fsm->slave, 0, "writing SII data:\n"); + ec_print_data(datagram->data, 8); +#endif + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_sii_state_write_check; +} + +/*****************************************************************************/ + +/** + SII state: WRITE CHECK. +*/ + +void ec_fsm_sii_state_write_check( + ec_fsm_sii_t *fsm /**< finite state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_sii_state_error; + EC_SLAVE_ERR(fsm->slave, "Failed to receive SII write datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->state = ec_fsm_sii_state_error; + EC_SLAVE_ERR(fsm->slave, "Reception of SII write datagram failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + + fsm->jiffies_start = datagram->jiffies_sent; + fsm->check_once_more = 1; + + // issue check datagram + ec_datagram_fprd(datagram, fsm->slave->station_address, 0x502, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_sii_state_write_check2; +} + +/*****************************************************************************/ + +/** + SII state: WRITE CHECK 2. +*/ + +void ec_fsm_sii_state_write_check2( + ec_fsm_sii_t *fsm /**< finite state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + unsigned long diff_ms; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_sii_state_error; + EC_SLAVE_ERR(fsm->slave, + "Failed to receive SII write check datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->state = ec_fsm_sii_state_error; + EC_SLAVE_ERR(fsm->slave, + "Reception of SII write check datagram failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + +#ifdef SII_DEBUG + EC_SLAVE_DBG(fsm->slave, 0, "checking SII write state:\n"); + ec_print_data(datagram->data, 2); +#endif + + if (EC_READ_U8(datagram->data + 1) & 0x20) { + EC_SLAVE_ERR(fsm->slave, "SII: Error on last SII command!\n"); + fsm->state = ec_fsm_sii_state_error; + return; + } + + /* FIXME: some slaves never answer with the busy flag set... + * wait a few ms for the write operation to complete. */ + diff_ms = (datagram->jiffies_received - fsm->jiffies_start) * 1000 / HZ; + if (diff_ms < SII_INHIBIT) { +#ifdef SII_DEBUG + EC_SLAVE_DBG(fsm->slave, 0, "too early.\n"); +#endif + // issue check datagram again + fsm->retries = EC_FSM_RETRIES; + return; + } + + if (EC_READ_U8(datagram->data + 1) & 0x82) { /* busy bit or + write operation busy bit */ + // still busy... timeout? + if (diff_ms >= SII_TIMEOUT) { + if (fsm->check_once_more) { + fsm->check_once_more = 0; + } else { + EC_SLAVE_ERR(fsm->slave, "SII: Write timeout.\n"); + fsm->state = ec_fsm_sii_state_error; + return; + } + } + + // issue check datagram again + fsm->retries = EC_FSM_RETRIES; + return; + } + + if (EC_READ_U8(datagram->data + 1) & 0x40) { + EC_SLAVE_ERR(fsm->slave, "SII: Write operation failed!\n"); + fsm->state = ec_fsm_sii_state_error; + return; + } + + // success + fsm->state = ec_fsm_sii_state_end; +} + +/*****************************************************************************/ + +/** + State: ERROR. +*/ + +void ec_fsm_sii_state_error( + ec_fsm_sii_t *fsm /**< finite state machine */ + ) +{ +} + +/*****************************************************************************/ + +/** + State: END. +*/ + +void ec_fsm_sii_state_end( + ec_fsm_sii_t *fsm /**< finite state machine */ + ) +{ +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_sii.h b/net/ethercat/master/fsm_sii.h new file mode 100644 index 000000000000..eefac5e7a3e6 --- /dev/null +++ b/net/ethercat/master/fsm_sii.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave information interface FSM structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_SII_H__ +#define __EC_FSM_SII_H__ + +#include "globals.h" +#include "datagram.h" +#include "slave.h" + +/*****************************************************************************/ + +/** SII access addressing mode. + */ +typedef enum { + EC_FSM_SII_USE_INCREMENT_ADDRESS, /**< Use auto-increment addressing. */ + EC_FSM_SII_USE_CONFIGURED_ADDRESS /**< Use configured addresses. */ +} ec_fsm_sii_addressing_t; + +/*****************************************************************************/ + +typedef struct ec_fsm_sii ec_fsm_sii_t; /**< \see ec_fsm_sii */ + +/** + Slave information interface FSM. +*/ + +struct ec_fsm_sii +{ + ec_slave_t *slave; /**< slave the FSM runs on */ + ec_datagram_t *datagram; /**< datagram used in the state machine */ + unsigned int retries; /**< retries upon datagram timeout */ + + void (*state)(ec_fsm_sii_t *); /**< SII state function */ + uint16_t word_offset; /**< input: word offset in SII */ + ec_fsm_sii_addressing_t mode; /**< reading via APRD or NPRD */ + uint8_t value[4]; /**< raw SII value (32bit) */ + unsigned long jiffies_start; /**< Start timestamp. */ + uint8_t check_once_more; /**< one more try after timeout */ +}; + +/*****************************************************************************/ + +void ec_fsm_sii_init(ec_fsm_sii_t *, ec_datagram_t *); +void ec_fsm_sii_clear(ec_fsm_sii_t *); + +void ec_fsm_sii_read(ec_fsm_sii_t *, ec_slave_t *, + uint16_t, ec_fsm_sii_addressing_t); +void ec_fsm_sii_write(ec_fsm_sii_t *, ec_slave_t *, uint16_t, + const uint16_t *, ec_fsm_sii_addressing_t); + +int ec_fsm_sii_exec(ec_fsm_sii_t *); +int ec_fsm_sii_success(ec_fsm_sii_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_slave.c b/net/ethercat/master/fsm_slave.c new file mode 100644 index 000000000000..0ddb76f7e817 --- /dev/null +++ b/net/ethercat/master/fsm_slave.c @@ -0,0 +1,582 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT slave (SDO) state machine. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "slave_config.h" + +#include "fsm_slave.h" + +/*****************************************************************************/ + +void ec_fsm_slave_state_idle(ec_fsm_slave_t *, ec_datagram_t *); +void ec_fsm_slave_state_ready(ec_fsm_slave_t *, ec_datagram_t *); +int ec_fsm_slave_action_process_sdo(ec_fsm_slave_t *, ec_datagram_t *); +void ec_fsm_slave_state_sdo_request(ec_fsm_slave_t *, ec_datagram_t *); +int ec_fsm_slave_action_process_reg(ec_fsm_slave_t *, ec_datagram_t *); +void ec_fsm_slave_state_reg_request(ec_fsm_slave_t *, ec_datagram_t *); +int ec_fsm_slave_action_process_foe(ec_fsm_slave_t *, ec_datagram_t *); +void ec_fsm_slave_state_foe_request(ec_fsm_slave_t *, ec_datagram_t *); +int ec_fsm_slave_action_process_soe(ec_fsm_slave_t *, ec_datagram_t *); +void ec_fsm_slave_state_soe_request(ec_fsm_slave_t *, ec_datagram_t *); + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_slave_init( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_slave_t *slave /**< EtherCAT slave. */ + ) +{ + fsm->slave = slave; + INIT_LIST_HEAD(&fsm->list); // mark as unlisted + + fsm->state = ec_fsm_slave_state_idle; + fsm->datagram = NULL; + fsm->sdo_request = NULL; + fsm->reg_request = NULL; + fsm->foe_request = NULL; + fsm->soe_request = NULL; + + // Init sub-state-machines + ec_fsm_coe_init(&fsm->fsm_coe); + ec_fsm_foe_init(&fsm->fsm_foe); + ec_fsm_soe_init(&fsm->fsm_soe); +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_slave_clear( + ec_fsm_slave_t *fsm /**< Master state machine. */ + ) +{ + // signal requests that are currently in operation + + if (fsm->sdo_request) { + fsm->sdo_request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&fsm->slave->master->request_queue); + } + + if (fsm->reg_request) { + fsm->reg_request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&fsm->slave->master->request_queue); + } + + if (fsm->foe_request) { + fsm->foe_request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&fsm->slave->master->request_queue); + } + + if (fsm->soe_request) { + fsm->soe_request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&fsm->slave->master->request_queue); + } + + // clear sub-state machines + ec_fsm_coe_clear(&fsm->fsm_coe); + ec_fsm_foe_clear(&fsm->fsm_foe); + ec_fsm_soe_clear(&fsm->fsm_soe); +} + +/*****************************************************************************/ + +/** Executes the current state of the state machine. + * + * \return 1 if \a datagram was used, else 0. + */ +int ec_fsm_slave_exec( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< New datagram to use. */ + ) +{ + int datagram_used; + + fsm->state(fsm, datagram); + + datagram_used = fsm->state != ec_fsm_slave_state_idle && + fsm->state != ec_fsm_slave_state_ready; + + if (datagram_used) { + fsm->datagram = datagram; + } else { + fsm->datagram = NULL; + } + + return datagram_used; +} + +/*****************************************************************************/ + +/** Sets the current state of the state machine to READY + */ +void ec_fsm_slave_set_ready( + ec_fsm_slave_t *fsm /**< Slave state machine. */ + ) +{ + if (fsm->state == ec_fsm_slave_state_idle) { + EC_SLAVE_DBG(fsm->slave, 1, "Ready for requests.\n"); + fsm->state = ec_fsm_slave_state_ready; + } +} + +/*****************************************************************************/ + +/** Returns, if the FSM is currently not busy and ready to execute. + * + * \return Non-zero if ready. + */ +int ec_fsm_slave_is_ready( + const ec_fsm_slave_t *fsm /**< Slave state machine. */ + ) +{ + return fsm->state == ec_fsm_slave_state_ready; +} + +/****************************************************************************** + * Slave state machine + *****************************************************************************/ + +/** Slave state: IDLE. + */ +void ec_fsm_slave_state_idle( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + // do nothing +} + +/*****************************************************************************/ + +/** Slave state: READY. + */ +void ec_fsm_slave_state_ready( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + // Check for pending external SDO requests + if (ec_fsm_slave_action_process_sdo(fsm, datagram)) { + return; + } + + // Check for pending external register requests + if (ec_fsm_slave_action_process_reg(fsm, datagram)) { + return; + } + + // Check for pending FoE requests + if (ec_fsm_slave_action_process_foe(fsm, datagram)) { + return; + } + + // Check for pending SoE requests + if (ec_fsm_slave_action_process_soe(fsm, datagram)) { + return; + } +} + +/*****************************************************************************/ + +/** Check for pending SDO requests and process one. + * + * \return non-zero, if an SDO request is processed. + */ +int ec_fsm_slave_action_process_sdo( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_sdo_request_t *request; + + if (list_empty(&slave->sdo_requests)) { + return 0; + } + + // take the first request to be processed + request = list_entry(slave->sdo_requests.next, ec_sdo_request_t, list); + list_del_init(&request->list); // dequeue + + if (slave->current_state & EC_SLAVE_STATE_ACK_ERR) { + EC_SLAVE_WARN(slave, "Aborting SDO request," + " slave has error flag set.\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->state = ec_fsm_slave_state_idle; + return 1; + } + + if (slave->current_state == EC_SLAVE_STATE_INIT) { + EC_SLAVE_WARN(slave, "Aborting SDO request, slave is in INIT.\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->state = ec_fsm_slave_state_idle; + return 1; + } + + fsm->sdo_request = request; + request->state = EC_INT_REQUEST_BUSY; + + // Found pending SDO request. Execute it! + EC_SLAVE_DBG(slave, 1, "Processing SDO request...\n"); + + // Start SDO transfer + fsm->state = ec_fsm_slave_state_sdo_request; + ec_fsm_coe_transfer(&fsm->fsm_coe, slave, request); + ec_fsm_coe_exec(&fsm->fsm_coe, datagram); // execute immediately + return 1; +} + +/*****************************************************************************/ + +/** Slave state: SDO_REQUEST. + */ +void ec_fsm_slave_state_sdo_request( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_sdo_request_t *request = fsm->sdo_request; + + if (ec_fsm_coe_exec(&fsm->fsm_coe, datagram)) { + return; + } + + if (!ec_fsm_coe_success(&fsm->fsm_coe)) { + EC_SLAVE_ERR(slave, "Failed to process SDO request.\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->sdo_request = NULL; + fsm->state = ec_fsm_slave_state_ready; + return; + } + + EC_SLAVE_DBG(slave, 1, "Finished SDO request.\n"); + + // SDO request finished + request->state = EC_INT_REQUEST_SUCCESS; + wake_up_all(&slave->master->request_queue); + fsm->sdo_request = NULL; + fsm->state = ec_fsm_slave_state_ready; +} + +/*****************************************************************************/ + +/** Check for pending register requests and process one. + * + * \return non-zero, if a register request is processed. + */ +int ec_fsm_slave_action_process_reg( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_reg_request_t *reg; + + fsm->reg_request = NULL; + + if (slave->config) { + // search the first internal register request to be processed + list_for_each_entry(reg, &slave->config->reg_requests, list) { + if (reg->state == EC_INT_REQUEST_QUEUED) { + fsm->reg_request = reg; + break; + } + } + } + + if (!fsm->reg_request && !list_empty(&slave->reg_requests)) { + // take the first external request to be processed + fsm->reg_request = + list_entry(slave->reg_requests.next, ec_reg_request_t, list); + list_del_init(&fsm->reg_request->list); // dequeue + } + + if (!fsm->reg_request) { // no register request to process + return 0; + } + + if (slave->current_state & EC_SLAVE_STATE_ACK_ERR) { + EC_SLAVE_WARN(slave, "Aborting register request," + " slave has error flag set.\n"); + fsm->reg_request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->reg_request = NULL; + fsm->state = ec_fsm_slave_state_idle; + return 1; + } + + // Found pending register request. Execute it! + EC_SLAVE_DBG(slave, 1, "Processing register request...\n"); + + fsm->reg_request->state = EC_INT_REQUEST_BUSY; + + // Start register access + if (fsm->reg_request->dir == EC_DIR_INPUT) { + ec_datagram_fprd(datagram, slave->station_address, + fsm->reg_request->address, fsm->reg_request->transfer_size); + ec_datagram_zero(datagram); + } else { + ec_datagram_fpwr(datagram, slave->station_address, + fsm->reg_request->address, fsm->reg_request->transfer_size); + memcpy(datagram->data, fsm->reg_request->data, + fsm->reg_request->transfer_size); + } + datagram->device_index = slave->device_index; + fsm->state = ec_fsm_slave_state_reg_request; + return 1; +} + +/*****************************************************************************/ + +/** Slave state: Register request. + */ +void ec_fsm_slave_state_reg_request( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_reg_request_t *reg = fsm->reg_request; + + if (!reg) { + // configuration was cleared in the meantime + fsm->state = ec_fsm_slave_state_ready; + fsm->reg_request = NULL; + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + EC_SLAVE_ERR(slave, "Failed to receive register" + " request datagram: "); + ec_datagram_print_state(fsm->datagram); + reg->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->reg_request = NULL; + fsm->state = ec_fsm_slave_state_ready; + return; + } + + if (fsm->datagram->working_counter == 1) { + if (reg->dir == EC_DIR_INPUT) { // read request + memcpy(reg->data, fsm->datagram->data, reg->transfer_size); + } + + reg->state = EC_INT_REQUEST_SUCCESS; + EC_SLAVE_DBG(slave, 1, "Register request successful.\n"); + } else { + reg->state = EC_INT_REQUEST_FAILURE; + ec_datagram_print_state(fsm->datagram); + EC_SLAVE_ERR(slave, "Register request failed" + " (working counter is %u).\n", + fsm->datagram->working_counter); + } + + wake_up_all(&slave->master->request_queue); + fsm->reg_request = NULL; + fsm->state = ec_fsm_slave_state_ready; +} + +/*****************************************************************************/ + +/** Check for pending FoE requests and process one. + * + * \return non-zero, if an FoE request is processed. + */ +int ec_fsm_slave_action_process_foe( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_foe_request_t *request; + + if (list_empty(&slave->foe_requests)) { + return 0; + } + + // take the first request to be processed + request = list_entry(slave->foe_requests.next, ec_foe_request_t, list); + list_del_init(&request->list); // dequeue + + if (slave->current_state & EC_SLAVE_STATE_ACK_ERR) { + EC_SLAVE_WARN(slave, "Aborting FoE request," + " slave has error flag set.\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->state = ec_fsm_slave_state_idle; + return 1; + } + + request->state = EC_INT_REQUEST_BUSY; + fsm->foe_request = request; + + EC_SLAVE_DBG(slave, 1, "Processing FoE request.\n"); + + fsm->state = ec_fsm_slave_state_foe_request; + ec_fsm_foe_transfer(&fsm->fsm_foe, slave, request); + ec_fsm_foe_exec(&fsm->fsm_foe, datagram); + return 1; +} + +/*****************************************************************************/ + +/** Slave state: FOE REQUEST. + */ +void ec_fsm_slave_state_foe_request( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_foe_request_t *request = fsm->foe_request; + + if (ec_fsm_foe_exec(&fsm->fsm_foe, datagram)) { + return; + } + + if (!ec_fsm_foe_success(&fsm->fsm_foe)) { + EC_SLAVE_ERR(slave, "Failed to handle FoE request.\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->foe_request = NULL; + fsm->state = ec_fsm_slave_state_ready; + return; + } + + // finished transferring FoE + EC_SLAVE_DBG(slave, 1, "Successfully transferred %zu bytes of FoE" + " data.\n", request->data_size); + + request->state = EC_INT_REQUEST_SUCCESS; + wake_up_all(&slave->master->request_queue); + fsm->foe_request = NULL; + fsm->state = ec_fsm_slave_state_ready; +} + +/*****************************************************************************/ + +/** Check for pending SoE requests and process one. + * + * \return non-zero, if a request is processed. + */ +int ec_fsm_slave_action_process_soe( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_soe_request_t *req; + + if (list_empty(&slave->soe_requests)) { + return 0; + } + + // take the first request to be processed + req = list_entry(slave->soe_requests.next, ec_soe_request_t, list); + list_del_init(&req->list); // dequeue + + if (slave->current_state & EC_SLAVE_STATE_ACK_ERR) { + EC_SLAVE_WARN(slave, "Aborting SoE request," + " slave has error flag set.\n"); + req->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->state = ec_fsm_slave_state_idle; + return 1; + } + + if (slave->current_state == EC_SLAVE_STATE_INIT) { + EC_SLAVE_WARN(slave, "Aborting SoE request, slave is in INIT.\n"); + req->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->state = ec_fsm_slave_state_idle; + return 0; + } + + fsm->soe_request = req; + req->state = EC_INT_REQUEST_BUSY; + + // Found pending request. Execute it! + EC_SLAVE_DBG(slave, 1, "Processing SoE request...\n"); + + // Start SoE transfer + fsm->state = ec_fsm_slave_state_soe_request; + ec_fsm_soe_transfer(&fsm->fsm_soe, slave, req); + ec_fsm_soe_exec(&fsm->fsm_soe, datagram); // execute immediately + return 1; +} + +/*****************************************************************************/ + +/** Slave state: SOE_REQUEST. + */ +void ec_fsm_slave_state_soe_request( + ec_fsm_slave_t *fsm, /**< Slave state machine. */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_soe_request_t *request = fsm->soe_request; + + if (ec_fsm_soe_exec(&fsm->fsm_soe, datagram)) { + return; + } + + if (!ec_fsm_soe_success(&fsm->fsm_soe)) { + EC_SLAVE_ERR(slave, "Failed to process SoE request.\n"); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&slave->master->request_queue); + fsm->soe_request = NULL; + fsm->state = ec_fsm_slave_state_ready; + return; + } + + EC_SLAVE_DBG(slave, 1, "Finished SoE request.\n"); + + // SoE request finished + request->state = EC_INT_REQUEST_SUCCESS; + wake_up_all(&slave->master->request_queue); + fsm->soe_request = NULL; + fsm->state = ec_fsm_slave_state_ready; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_slave.h b/net/ethercat/master/fsm_slave.h new file mode 100644 index 000000000000..b511a6683392 --- /dev/null +++ b/net/ethercat/master/fsm_slave.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave request state machine. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_SLAVE_H__ +#define __EC_FSM_SLAVE_H__ + +#include "globals.h" +#include "datagram.h" +#include "sdo_request.h" +#include "reg_request.h" +#include "fsm_coe.h" +#include "fsm_foe.h" +#include "fsm_soe.h" + +/*****************************************************************************/ + +typedef struct ec_fsm_slave ec_fsm_slave_t; /**< \see ec_fsm_slave */ + +/** Finite state machine of an EtherCAT slave. + */ +struct ec_fsm_slave { + ec_slave_t *slave; /**< slave the FSM runs on */ + struct list_head list; /**< Used for execution list. */ + + void (*state)(ec_fsm_slave_t *, ec_datagram_t *); /**< State function. */ + ec_datagram_t *datagram; /**< Previous state datagram. */ + ec_sdo_request_t *sdo_request; /**< SDO request to process. */ + ec_reg_request_t *reg_request; /**< Register request to process. */ + ec_foe_request_t *foe_request; /**< FoE request to process. */ + off_t foe_index; /**< Index to FoE write request data. */ + ec_soe_request_t *soe_request; /**< SoE request to process. */ + + ec_fsm_coe_t fsm_coe; /**< CoE state machine. */ + ec_fsm_foe_t fsm_foe; /**< FoE state machine. */ + ec_fsm_soe_t fsm_soe; /**< SoE state machine. */ +}; + +/*****************************************************************************/ + +void ec_fsm_slave_init(ec_fsm_slave_t *, ec_slave_t *); +void ec_fsm_slave_clear(ec_fsm_slave_t *); + +int ec_fsm_slave_exec(ec_fsm_slave_t *, ec_datagram_t *); +void ec_fsm_slave_set_ready(ec_fsm_slave_t *); +int ec_fsm_slave_is_ready(const ec_fsm_slave_t *); + +/*****************************************************************************/ + + +#endif // __EC_FSM_SLAVE_H__ diff --git a/net/ethercat/master/fsm_slave_config.c b/net/ethercat/master/fsm_slave_config.c new file mode 100644 index 000000000000..25ffdd1d5617 --- /dev/null +++ b/net/ethercat/master/fsm_slave_config.c @@ -0,0 +1,1730 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * + * EtherCAT slave configuration state machine. + */ + +/*****************************************************************************/ + +#include <asm/div64.h> + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "slave_config.h" +#include "fsm_slave_config.h" + +/*****************************************************************************/ + +/** Maximum clock difference (in ns) before going to SAFEOP. + * + * Wait for DC time difference to drop under this absolute value before + * requesting SAFEOP. + */ +#define EC_DC_MAX_SYNC_DIFF_NS 10000 + +/** Maximum time (in ms) to wait for clock discipline. + */ +#define EC_DC_SYNC_WAIT_MS 5000 + +/** Time offset (in ns), that is added to cyclic start time. + */ +#define EC_DC_START_OFFSET 100000000ULL + +/*****************************************************************************/ + +void ec_fsm_slave_config_state_start(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_init(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_clear_fmmus(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_clear_sync(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_dc_clear_assign(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_mbox_sync(ec_fsm_slave_config_t *); +#ifdef EC_SII_ASSIGN +void ec_fsm_slave_config_state_assign_pdi(ec_fsm_slave_config_t *); +#endif +void ec_fsm_slave_config_state_boot_preop(ec_fsm_slave_config_t *); +#ifdef EC_SII_ASSIGN +void ec_fsm_slave_config_state_assign_ethercat(ec_fsm_slave_config_t *); +#endif +void ec_fsm_slave_config_state_sdo_conf(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_soe_conf_preop(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_watchdog_divider(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_watchdog(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_pdo_sync(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_pdo_conf(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_fmmu(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_dc_cycle(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_dc_sync_check(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_dc_start(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_dc_assign(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_safeop(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_soe_conf_safeop(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_op(ec_fsm_slave_config_t *); + +void ec_fsm_slave_config_enter_init(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_clear_sync(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_dc_clear_assign(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_mbox_sync(ec_fsm_slave_config_t *); +#ifdef EC_SII_ASSIGN +void ec_fsm_slave_config_enter_assign_pdi(ec_fsm_slave_config_t *); +#endif +void ec_fsm_slave_config_enter_boot_preop(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_sdo_conf(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_soe_conf_preop(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_pdo_conf(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_watchdog_divider(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_watchdog(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_pdo_sync(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_fmmu(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_dc_cycle(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_safeop(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_soe_conf_safeop(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_enter_op(ec_fsm_slave_config_t *); + +void ec_fsm_slave_config_state_end(ec_fsm_slave_config_t *); +void ec_fsm_slave_config_state_error(ec_fsm_slave_config_t *); + +void ec_fsm_slave_config_reconfigure(ec_fsm_slave_config_t *); + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_slave_config_init( + ec_fsm_slave_config_t *fsm, /**< slave state machine */ + ec_datagram_t *datagram, /**< datagram structure to use */ + ec_fsm_change_t *fsm_change, /**< State change state machine to use. */ + ec_fsm_coe_t *fsm_coe, /**< CoE state machine to use. */ + ec_fsm_soe_t *fsm_soe, /**< SoE state machine to use. */ + ec_fsm_pdo_t *fsm_pdo /**< PDO configuration state machine to use. */ + ) +{ + ec_sdo_request_init(&fsm->request_copy); + ec_soe_request_init(&fsm->soe_request_copy); + + fsm->datagram = datagram; + fsm->fsm_change = fsm_change; + fsm->fsm_coe = fsm_coe; + fsm->fsm_soe = fsm_soe; + fsm->fsm_pdo = fsm_pdo; +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_slave_config_clear( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_sdo_request_clear(&fsm->request_copy); + ec_soe_request_clear(&fsm->soe_request_copy); +} + +/*****************************************************************************/ + +/** Start slave configuration state machine. + */ +void ec_fsm_slave_config_start( + ec_fsm_slave_config_t *fsm, /**< slave state machine */ + ec_slave_t *slave /**< slave to configure */ + ) +{ + fsm->slave = slave; + fsm->state = ec_fsm_slave_config_state_start; +} + +/*****************************************************************************/ + +/** + * \return false, if state machine has terminated + */ +int ec_fsm_slave_config_running( + const ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + return fsm->state != ec_fsm_slave_config_state_end + && fsm->state != ec_fsm_slave_config_state_error; +} + +/*****************************************************************************/ + +/** Executes the current state of the state machine. + * + * If the state machine's datagram is not sent or received yet, the execution + * of the state machine is delayed to the next cycle. + * + * \return false, if state machine has terminated + */ +int ec_fsm_slave_config_exec( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + if (fsm->datagram->state == EC_DATAGRAM_SENT + || fsm->datagram->state == EC_DATAGRAM_QUEUED) { + // datagram was not sent or received yet. + return ec_fsm_slave_config_running(fsm); + } + + fsm->state(fsm); + return ec_fsm_slave_config_running(fsm); +} + +/*****************************************************************************/ + +/** + * \return true, if the state machine terminated gracefully + */ +int ec_fsm_slave_config_success( + const ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + return fsm->state == ec_fsm_slave_config_state_end; +} + +/****************************************************************************** + * Slave configuration state machine + *****************************************************************************/ + +/** Slave configuration state: START. + */ +void ec_fsm_slave_config_state_start( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + EC_SLAVE_DBG(fsm->slave, 1, "Configuring...\n"); + ec_fsm_slave_config_enter_init(fsm); +} + +/*****************************************************************************/ + +/** Start state change to INIT. + */ +void ec_fsm_slave_config_enter_init( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_fsm_change_start(fsm->fsm_change, fsm->slave, EC_SLAVE_STATE_INIT); + ec_fsm_change_exec(fsm->fsm_change); + fsm->state = ec_fsm_slave_config_state_init; +} + +/*****************************************************************************/ + +/** Slave configuration state: INIT. + */ +void ec_fsm_slave_config_state_init( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + + if (ec_fsm_change_exec(fsm->fsm_change)) return; + + if (!ec_fsm_change_success(fsm->fsm_change)) { + if (!fsm->fsm_change->spontaneous_change) + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + return; + } + + EC_SLAVE_DBG(slave, 1, "Now in INIT.\n"); + + if (!slave->base_fmmu_count) { // skip FMMU configuration + ec_fsm_slave_config_enter_clear_sync(fsm); + return; + } + + EC_SLAVE_DBG(slave, 1, "Clearing FMMU configurations...\n"); + + // clear FMMU configurations + ec_datagram_fpwr(datagram, slave->station_address, + 0x0600, EC_FMMU_PAGE_SIZE * slave->base_fmmu_count); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_clear_fmmus; +} + +/*****************************************************************************/ + +/** Slave configuration state: CLEAR FMMU. + */ +void ec_fsm_slave_config_state_clear_fmmus( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(fsm->slave, "Failed receive FMMU clearing datagram.\n"); + return; + } + + if (datagram->working_counter != 1) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(fsm->slave, "Failed to clear FMMUs: "); + ec_datagram_print_wc_error(datagram); + return; + } + + ec_fsm_slave_config_enter_clear_sync(fsm); +} + +/*****************************************************************************/ + +/** Clear the sync manager configurations. + */ +void ec_fsm_slave_config_enter_clear_sync( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + size_t sync_size; + + if (!slave->base_sync_count) { + // no sync managers + ec_fsm_slave_config_enter_dc_clear_assign(fsm); + return; + } + + EC_SLAVE_DBG(slave, 1, "Clearing sync manager configurations...\n"); + + sync_size = EC_SYNC_PAGE_SIZE * slave->base_sync_count; + + // clear sync manager configurations + ec_datagram_fpwr(datagram, slave->station_address, 0x0800, sync_size); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_clear_sync; +} + +/*****************************************************************************/ + +/** Slave configuration state: CLEAR SYNC. + */ +void ec_fsm_slave_config_state_clear_sync( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(fsm->slave, "Failed receive sync manager" + " clearing datagram.\n"); + return; + } + + if (datagram->working_counter != 1) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(fsm->slave, + "Failed to clear sync manager configurations: "); + ec_datagram_print_wc_error(datagram); + return; + } + + ec_fsm_slave_config_enter_dc_clear_assign(fsm); +} + +/*****************************************************************************/ + +/** Clear the DC assignment. + */ +void ec_fsm_slave_config_enter_dc_clear_assign( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + + if (!slave->base_dc_supported || !slave->has_dc_system_time) { + ec_fsm_slave_config_enter_mbox_sync(fsm); + return; + } + + EC_SLAVE_DBG(slave, 1, "Clearing DC assignment...\n"); + + ec_datagram_fpwr(datagram, slave->station_address, 0x0980, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_dc_clear_assign; +} + +/*****************************************************************************/ + +/** Slave configuration state: CLEAR DC ASSIGN. + */ +void ec_fsm_slave_config_state_dc_clear_assign( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(fsm->slave, "Failed receive DC assignment" + " clearing datagram.\n"); + return; + } + + if (datagram->working_counter != 1) { + // clearing the DC assignment does not succeed on simple slaves + EC_SLAVE_DBG(fsm->slave, 1, "Failed to clear DC assignment: "); + ec_datagram_print_wc_error(datagram); + } + + ec_fsm_slave_config_enter_mbox_sync(fsm); +} + +/*****************************************************************************/ + +/** Check for mailbox sync managers to be configured. + */ +void ec_fsm_slave_config_enter_mbox_sync( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + unsigned int i; + + // slave is now in INIT + if (slave->current_state == slave->requested_state) { + fsm->state = ec_fsm_slave_config_state_end; // successful + EC_SLAVE_DBG(slave, 1, "Finished configuration.\n"); + return; + } + + if (!slave->sii.mailbox_protocols) { + // no mailbox protocols supported + EC_SLAVE_DBG(slave, 1, "Slave does not support" + " mailbox communication.\n"); +#ifdef EC_SII_ASSIGN + ec_fsm_slave_config_enter_assign_pdi(fsm); +#else + ec_fsm_slave_config_enter_boot_preop(fsm); +#endif + return; + } + + EC_SLAVE_DBG(slave, 1, "Configuring mailbox sync managers...\n"); + + if (slave->requested_state == EC_SLAVE_STATE_BOOT) { + ec_sync_t sync; + + ec_datagram_fpwr(datagram, slave->station_address, 0x0800, + EC_SYNC_PAGE_SIZE * 2); + ec_datagram_zero(datagram); + + ec_sync_init(&sync, slave); + sync.physical_start_address = slave->sii.boot_rx_mailbox_offset; + sync.control_register = 0x26; + sync.enable = 1; + ec_sync_page(&sync, 0, slave->sii.boot_rx_mailbox_size, + EC_DIR_INVALID, // use default direction + 0, // no PDO xfer + datagram->data); + slave->configured_rx_mailbox_offset = + slave->sii.boot_rx_mailbox_offset; + slave->configured_rx_mailbox_size = + slave->sii.boot_rx_mailbox_size; + + ec_sync_init(&sync, slave); + sync.physical_start_address = slave->sii.boot_tx_mailbox_offset; + sync.control_register = 0x22; + sync.enable = 1; + ec_sync_page(&sync, 1, slave->sii.boot_tx_mailbox_size, + EC_DIR_INVALID, // use default direction + 0, // no PDO xfer + datagram->data + EC_SYNC_PAGE_SIZE); + slave->configured_tx_mailbox_offset = + slave->sii.boot_tx_mailbox_offset; + slave->configured_tx_mailbox_size = + slave->sii.boot_tx_mailbox_size; + + } else if (slave->sii.sync_count >= 2) { // mailbox configuration provided + ec_datagram_fpwr(datagram, slave->station_address, 0x0800, + EC_SYNC_PAGE_SIZE * slave->sii.sync_count); + ec_datagram_zero(datagram); + + for (i = 0; i < 2; i++) { + ec_sync_page(&slave->sii.syncs[i], i, + slave->sii.syncs[i].default_length, + NULL, // use default sync manager configuration + 0, // no PDO xfer + datagram->data + EC_SYNC_PAGE_SIZE * i); + } + + slave->configured_rx_mailbox_offset = + slave->sii.syncs[0].physical_start_address; + slave->configured_rx_mailbox_size = + slave->sii.syncs[0].default_length; + slave->configured_tx_mailbox_offset = + slave->sii.syncs[1].physical_start_address; + slave->configured_tx_mailbox_size = + slave->sii.syncs[1].default_length; + } else { // no mailbox sync manager configurations provided + ec_sync_t sync; + + EC_SLAVE_DBG(slave, 1, "Slave does not provide" + " mailbox sync manager configurations.\n"); + + ec_datagram_fpwr(datagram, slave->station_address, 0x0800, + EC_SYNC_PAGE_SIZE * 2); + ec_datagram_zero(datagram); + + ec_sync_init(&sync, slave); + sync.physical_start_address = slave->sii.std_rx_mailbox_offset; + sync.control_register = 0x26; + sync.enable = 1; + ec_sync_page(&sync, 0, slave->sii.std_rx_mailbox_size, + NULL, // use default sync manager configuration + 0, // no PDO xfer + datagram->data); + slave->configured_rx_mailbox_offset = + slave->sii.std_rx_mailbox_offset; + slave->configured_rx_mailbox_size = + slave->sii.std_rx_mailbox_size; + + ec_sync_init(&sync, slave); + sync.physical_start_address = slave->sii.std_tx_mailbox_offset; + sync.control_register = 0x22; + sync.enable = 1; + ec_sync_page(&sync, 1, slave->sii.std_tx_mailbox_size, + NULL, // use default sync manager configuration + 0, // no PDO xfer + datagram->data + EC_SYNC_PAGE_SIZE); + slave->configured_tx_mailbox_offset = + slave->sii.std_tx_mailbox_offset; + slave->configured_tx_mailbox_size = + slave->sii.std_tx_mailbox_size; + } + + fsm->take_time = 1; + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_mbox_sync; +} + +/*****************************************************************************/ + +/** Slave configuration state: SYNC. + * + * \todo Timeout for response. + */ +void ec_fsm_slave_config_state_mbox_sync( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive sync manager" + " configuration datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (fsm->take_time) { + fsm->take_time = 0; + fsm->jiffies_start = datagram->jiffies_sent; + } + + /* Because the sync manager configurations are cleared during the last + * cycle, some slaves do not immediately respond to the mailbox sync + * manager configuration datagram. Therefore, resend the datagram for + * a certain time, if the slave does not respond. + */ + if (datagram->working_counter == 0) { + unsigned long diff = datagram->jiffies_received - fsm->jiffies_start; + + if (diff >= HZ) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Timeout while configuring" + " mailbox sync managers.\n"); + return; + } else { + EC_SLAVE_DBG(slave, 1, "Resending after %u ms...\n", + (unsigned int) diff * 1000 / HZ); + } + + // send configuration datagram again + fsm->retries = EC_FSM_RETRIES; + return; + } + else if (datagram->working_counter != 1) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to set sync managers: "); + ec_datagram_print_wc_error(datagram); + return; + } + +#ifdef EC_SII_ASSIGN + ec_fsm_slave_config_enter_assign_pdi(fsm); +#else + ec_fsm_slave_config_enter_boot_preop(fsm); +#endif +} + +/*****************************************************************************/ + +#ifdef EC_SII_ASSIGN + +/** Assign SII to PDI. + */ +void ec_fsm_slave_config_enter_assign_pdi( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (fsm->slave->requested_state != EC_SLAVE_STATE_BOOT) { + EC_SLAVE_DBG(slave, 1, "Assigning SII access to PDI.\n"); + + ec_datagram_fpwr(datagram, slave->station_address, 0x0500, 0x01); + EC_WRITE_U8(datagram->data, 0x01); // PDI + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_assign_pdi; + } + else { + ec_fsm_slave_config_enter_boot_preop(fsm); + } +} + +/*****************************************************************************/ + +/** Slave configuration state: ASSIGN_PDI. + */ +void ec_fsm_slave_config_state_assign_pdi( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + return; + } + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + EC_SLAVE_WARN(slave, "Failed receive SII assignment datagram: "); + ec_datagram_print_state(datagram); + goto cont_preop; + } + + if (datagram->working_counter != 1) { + EC_SLAVE_WARN(slave, "Failed to assign SII to PDI: "); + ec_datagram_print_wc_error(datagram); + } + +cont_preop: + ec_fsm_slave_config_enter_boot_preop(fsm); +} + +#endif + +/*****************************************************************************/ + +/** Request PREOP state. + */ +void ec_fsm_slave_config_enter_boot_preop( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + fsm->state = ec_fsm_slave_config_state_boot_preop; + + if (fsm->slave->requested_state != EC_SLAVE_STATE_BOOT) { + ec_fsm_change_start(fsm->fsm_change, + fsm->slave, EC_SLAVE_STATE_PREOP); + } else { // BOOT + ec_fsm_change_start(fsm->fsm_change, + fsm->slave, EC_SLAVE_STATE_BOOT); + } + + ec_fsm_change_exec(fsm->fsm_change); // execute immediately +} + +/*****************************************************************************/ + +/** Slave configuration state: BOOT/PREOP. + */ +void ec_fsm_slave_config_state_boot_preop( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (ec_fsm_change_exec(fsm->fsm_change)) { + return; + } + + if (!ec_fsm_change_success(fsm->fsm_change)) { + if (!fsm->fsm_change->spontaneous_change) + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + return; + } + + // slave is now in BOOT or PREOP + slave->jiffies_preop = fsm->datagram->jiffies_received; + + EC_SLAVE_DBG(slave, 1, "Now in %s.\n", + slave->requested_state != EC_SLAVE_STATE_BOOT ? "PREOP" : "BOOT"); + +#ifdef EC_SII_ASSIGN + EC_SLAVE_DBG(slave, 1, "Assigning SII access back to EtherCAT.\n"); + + ec_datagram_fpwr(fsm->datagram, slave->station_address, 0x0500, 0x01); + EC_WRITE_U8(fsm->datagram->data, 0x00); // EtherCAT + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_assign_ethercat; +#else + if (slave->current_state == slave->requested_state) { + fsm->state = ec_fsm_slave_config_state_end; // successful + EC_SLAVE_DBG(slave, 1, "Finished configuration.\n"); + return; + } + + ec_fsm_slave_config_enter_sdo_conf(fsm); +#endif +} + +/*****************************************************************************/ + +#ifdef EC_SII_ASSIGN + +/** Slave configuration state: ASSIGN_ETHERCAT. + */ +void ec_fsm_slave_config_state_assign_ethercat( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + return; + } + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + EC_SLAVE_WARN(slave, "Failed receive SII assignment datagram: "); + ec_datagram_print_state(datagram); + goto cont_sdo_conf; + } + + if (datagram->working_counter != 1) { + EC_SLAVE_WARN(slave, "Failed to assign SII back to EtherCAT: "); + ec_datagram_print_wc_error(datagram); + } + +cont_sdo_conf: + if (slave->current_state == slave->requested_state) { + fsm->state = ec_fsm_slave_config_state_end; // successful + EC_SLAVE_DBG(slave, 1, "Finished configuration.\n"); + return; + } + + ec_fsm_slave_config_enter_sdo_conf(fsm); +} + +#endif + +/*****************************************************************************/ + +/** Check for SDO configurations to be applied. + */ +void ec_fsm_slave_config_enter_sdo_conf( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (!slave->config) { + ec_fsm_slave_config_enter_pdo_sync(fsm); + return; + } + + // No CoE configuration to be applied? + if (list_empty(&slave->config->sdo_configs)) { // skip SDO configuration + ec_fsm_slave_config_enter_soe_conf_preop(fsm); + return; + } + + // start SDO configuration + fsm->state = ec_fsm_slave_config_state_sdo_conf; + fsm->request = list_entry(fsm->slave->config->sdo_configs.next, + ec_sdo_request_t, list); + ec_sdo_request_copy(&fsm->request_copy, fsm->request); + ecrt_sdo_request_write(&fsm->request_copy); + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request_copy); + ec_fsm_coe_exec(fsm->fsm_coe, fsm->datagram); // execute immediately +} + +/*****************************************************************************/ + +/** Slave configuration state: SDO_CONF. + */ +void ec_fsm_slave_config_state_sdo_conf( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + if (ec_fsm_coe_exec(fsm->fsm_coe, fsm->datagram)) { + return; + } + + if (!ec_fsm_coe_success(fsm->fsm_coe)) { + EC_SLAVE_ERR(fsm->slave, "SDO configuration failed.\n"); + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + return; + } + + if (!fsm->slave->config) { // config removed in the meantime + ec_fsm_slave_config_reconfigure(fsm); + return; + } + + // Another SDO to configure? + if (fsm->request->list.next != &fsm->slave->config->sdo_configs) { + fsm->request = list_entry(fsm->request->list.next, + ec_sdo_request_t, list); + ec_sdo_request_copy(&fsm->request_copy, fsm->request); + ecrt_sdo_request_write(&fsm->request_copy); + ec_fsm_coe_transfer(fsm->fsm_coe, fsm->slave, &fsm->request_copy); + ec_fsm_coe_exec(fsm->fsm_coe, fsm->datagram); // execute immediately + return; + } + + // All SDOs are now configured. + ec_fsm_slave_config_enter_soe_conf_preop(fsm); +} + +/*****************************************************************************/ + +/** Check for SoE configurations to be applied. + */ +void ec_fsm_slave_config_enter_soe_conf_preop( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_soe_request_t *req; + + if (!slave->config) { + ec_fsm_slave_config_enter_pdo_sync(fsm); + return; + } + + list_for_each_entry(req, &slave->config->soe_configs, list) { + if (req->al_state == EC_AL_STATE_PREOP) { + // start SoE configuration + fsm->state = ec_fsm_slave_config_state_soe_conf_preop; + fsm->soe_request = req; + ec_soe_request_copy(&fsm->soe_request_copy, fsm->soe_request); + ec_soe_request_write(&fsm->soe_request_copy); + ec_fsm_soe_transfer(fsm->fsm_soe, fsm->slave, + &fsm->soe_request_copy); + ec_fsm_soe_exec(fsm->fsm_soe, fsm->datagram); + return; + } + } + + // No SoE configuration to be applied in PREOP + ec_fsm_slave_config_enter_pdo_conf(fsm); +} + +/*****************************************************************************/ + +/** Slave configuration state: SOE_CONF. + */ +void ec_fsm_slave_config_state_soe_conf_preop( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (ec_fsm_soe_exec(fsm->fsm_soe, fsm->datagram)) { + return; + } + + if (!ec_fsm_soe_success(fsm->fsm_soe)) { + EC_SLAVE_ERR(slave, "SoE configuration failed.\n"); + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + return; + } + + if (!fsm->slave->config) { // config removed in the meantime + ec_fsm_slave_config_reconfigure(fsm); + return; + } + + // Another IDN to configure in PREOP? + while (fsm->soe_request->list.next != &fsm->slave->config->soe_configs) { + fsm->soe_request = list_entry(fsm->soe_request->list.next, + ec_soe_request_t, list); + if (fsm->soe_request->al_state == EC_AL_STATE_PREOP) { + ec_soe_request_copy(&fsm->soe_request_copy, fsm->soe_request); + ec_soe_request_write(&fsm->soe_request_copy); + ec_fsm_soe_transfer(fsm->fsm_soe, fsm->slave, + &fsm->soe_request_copy); + ec_fsm_soe_exec(fsm->fsm_soe, fsm->datagram); + return; + } + } + + // All PREOP IDNs are now configured. + ec_fsm_slave_config_enter_pdo_conf(fsm); +} + +/*****************************************************************************/ + +/** PDO_CONF entry function. + */ +void ec_fsm_slave_config_enter_pdo_conf( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + // Start configuring PDOs + ec_fsm_pdo_start_configuration(fsm->fsm_pdo, fsm->slave); + fsm->state = ec_fsm_slave_config_state_pdo_conf; + fsm->state(fsm); // execute immediately +} + +/*****************************************************************************/ + +/** Slave configuration state: PDO_CONF. + */ +void ec_fsm_slave_config_state_pdo_conf( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + // TODO check for config here + + if (ec_fsm_pdo_exec(fsm->fsm_pdo, fsm->datagram)) { + return; + } + + if (!fsm->slave->config) { // config removed in the meantime + ec_fsm_slave_config_reconfigure(fsm); + return; + } + + if (!ec_fsm_pdo_success(fsm->fsm_pdo)) { + EC_SLAVE_WARN(fsm->slave, "PDO configuration failed.\n"); + } + + ec_fsm_slave_config_enter_watchdog_divider(fsm); +} + +/*****************************************************************************/ + +/** WATCHDOG_DIVIDER entry function. + */ +void ec_fsm_slave_config_enter_watchdog_divider( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + ec_slave_config_t *config = slave->config; + + if (config && config->watchdog_divider) { + EC_SLAVE_DBG(slave, 1, "Setting watchdog divider to %u.\n", + config->watchdog_divider); + + ec_datagram_fpwr(datagram, slave->station_address, 0x0400, 2); + EC_WRITE_U16(datagram->data, config->watchdog_divider); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_watchdog_divider; + } else { + ec_fsm_slave_config_enter_watchdog(fsm); + } +} + +/*****************************************************************************/ + +/** Slave configuration state: WATCHDOG_DIVIDER. + */ +void ec_fsm_slave_config_state_watchdog_divider( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive watchdog divider" + " configuration datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + slave->error_flag = 1; + EC_SLAVE_WARN(slave, "Failed to set watchdog divider: "); + ec_datagram_print_wc_error(datagram); + return; + } + + ec_fsm_slave_config_enter_watchdog(fsm); +} + +/*****************************************************************************/ + +/** WATCHDOG entry function + */ +void ec_fsm_slave_config_enter_watchdog( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + ec_slave_config_t *config = slave->config; + + if (config && config->watchdog_intervals) { + EC_SLAVE_DBG(slave, 1, "Setting process data" + " watchdog intervals to %u.\n", config->watchdog_intervals); + + ec_datagram_fpwr(datagram, slave->station_address, 0x0420, 2); + EC_WRITE_U16(datagram->data, config->watchdog_intervals); + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_watchdog; + } else { + ec_fsm_slave_config_enter_pdo_sync(fsm); + } +} + +/*****************************************************************************/ + +/** Slave configuration state: WATCHDOG. + */ + +void ec_fsm_slave_config_state_watchdog( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive sync manager" + " watchdog configuration datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + EC_SLAVE_WARN(slave, "Failed to set process data" + " watchdog intervals: "); + ec_datagram_print_wc_error(datagram); + } + + ec_fsm_slave_config_enter_pdo_sync(fsm); +} + +/*****************************************************************************/ + +/** Check for PDO sync managers to be configured. + */ +void ec_fsm_slave_config_enter_pdo_sync( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + unsigned int i, j, offset, num_pdo_syncs; + uint8_t sync_index; + const ec_sync_t *sync; + uint16_t size; + + if (slave->sii.mailbox_protocols) { + offset = 2; // slave has mailboxes + } else { + offset = 0; + } + + if (slave->sii.sync_count <= offset) { + // no PDO sync managers to configure + ec_fsm_slave_config_enter_fmmu(fsm); + return; + } + + num_pdo_syncs = slave->sii.sync_count - offset; + + // configure sync managers for process data + ec_datagram_fpwr(datagram, slave->station_address, + 0x0800 + EC_SYNC_PAGE_SIZE * offset, + EC_SYNC_PAGE_SIZE * num_pdo_syncs); + ec_datagram_zero(datagram); + + for (i = 0; i < num_pdo_syncs; i++) { + const ec_sync_config_t *sync_config; + uint8_t pdo_xfer = 0; + sync_index = i + offset; + sync = &slave->sii.syncs[sync_index]; + + if (slave->config) { + const ec_slave_config_t *sc = slave->config; + sync_config = &sc->sync_configs[sync_index]; + size = ec_pdo_list_total_size(&sync_config->pdos); + + // determine, if PDOs shall be transferred via this SM + // inthat case, enable sync manager in every case + for (j = 0; j < sc->used_fmmus; j++) { + if (sc->fmmu_configs[j].sync_index == sync_index) { + pdo_xfer = 1; + break; + } + } + + } else { + sync_config = NULL; + size = sync->default_length; + } + + ec_sync_page(sync, sync_index, size, sync_config, pdo_xfer, + datagram->data + EC_SYNC_PAGE_SIZE * i); + } + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_pdo_sync; +} + +/*****************************************************************************/ + +/** Configure PDO sync managers. + */ +void ec_fsm_slave_config_state_pdo_sync( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive process data sync" + " manager configuration datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to set process data sync managers: "); + ec_datagram_print_wc_error(datagram); + return; + } + + ec_fsm_slave_config_enter_fmmu(fsm); +} + +/*****************************************************************************/ + +/** Check for FMMUs to be configured. + */ +void ec_fsm_slave_config_enter_fmmu( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_datagram_t *datagram = fsm->datagram; + unsigned int i; + const ec_fmmu_config_t *fmmu; + const ec_sync_t *sync; + + if (!slave->config) { + ec_fsm_slave_config_enter_safeop(fsm); + return; + } + + if (slave->base_fmmu_count < slave->config->used_fmmus) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Slave has less FMMUs (%u)" + " than requested (%u).\n", slave->base_fmmu_count, + slave->config->used_fmmus); + return; + } + + if (!slave->base_fmmu_count) { // skip FMMU configuration + ec_fsm_slave_config_enter_dc_cycle(fsm); + return; + } + + // configure FMMUs + ec_datagram_fpwr(datagram, slave->station_address, + 0x0600, EC_FMMU_PAGE_SIZE * slave->base_fmmu_count); + ec_datagram_zero(datagram); + for (i = 0; i < slave->config->used_fmmus; i++) { + fmmu = &slave->config->fmmu_configs[i]; + if (!(sync = ec_slave_get_sync(slave, fmmu->sync_index))) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to determine PDO sync manager" + " for FMMU!\n"); + return; + } + ec_fmmu_config_page(fmmu, sync, + datagram->data + EC_FMMU_PAGE_SIZE * i); + } + + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_fmmu; +} + +/*****************************************************************************/ + +/** Slave configuration state: FMMU. + */ +void ec_fsm_slave_config_state_fmmu( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive FMMUs datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to set FMMUs: "); + ec_datagram_print_wc_error(datagram); + return; + } + + ec_fsm_slave_config_enter_dc_cycle(fsm); +} + +/*****************************************************************************/ + +/** Check for DC to be configured. + */ +void ec_fsm_slave_config_enter_dc_cycle( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + ec_slave_config_t *config = slave->config; + + if (!config) { // config removed in the meantime + ec_fsm_slave_config_reconfigure(fsm); + return; + } + + if (config->dc_assign_activate) { + if (!slave->base_dc_supported || !slave->has_dc_system_time) { + EC_SLAVE_WARN(slave, "Slave seems not to support" + " distributed clocks!\n"); + } + + EC_SLAVE_DBG(slave, 1, "Setting DC cycle times to %u / %u.\n", + config->dc_sync[0].cycle_time, config->dc_sync[1].cycle_time); + + // set DC cycle times + ec_datagram_fpwr(datagram, slave->station_address, 0x09A0, 8); + EC_WRITE_U32(datagram->data, config->dc_sync[0].cycle_time); + EC_WRITE_U32(datagram->data + 4, config->dc_sync[1].cycle_time); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_dc_cycle; + } else { + // DC are unused + ec_fsm_slave_config_enter_safeop(fsm); + } +} + +/*****************************************************************************/ + +/** Slave configuration state: DC CYCLE. + */ +void ec_fsm_slave_config_state_dc_cycle( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + ec_slave_config_t *config = slave->config; + + if (!config) { // config removed in the meantime + ec_fsm_slave_config_reconfigure(fsm); + return; + } + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive DC cycle times datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to set DC cycle times: "); + ec_datagram_print_wc_error(datagram); + return; + } + + EC_SLAVE_DBG(slave, 1, "Checking for synchrony.\n"); + + fsm->jiffies_start = jiffies; + ec_datagram_fprd(datagram, slave->station_address, 0x092c, 4); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_dc_sync_check; +} + +/*****************************************************************************/ + +/** Slave configuration state: DC SYNC CHECK. + */ +void ec_fsm_slave_config_state_dc_sync_check( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + ec_master_t *master = slave->master; + ec_slave_config_t *config = slave->config; + uint32_t abs_sync_diff; + unsigned long diff_ms; + ec_sync_signal_t *sync0 = &config->dc_sync[0]; + ec_sync_signal_t *sync1 = &config->dc_sync[1]; + u64 start_time; + + if (!config) { // config removed in the meantime + ec_fsm_slave_config_reconfigure(fsm); + return; + } + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive DC sync check datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to check DC synchrony: "); + ec_datagram_print_wc_error(datagram); + return; + } + + abs_sync_diff = EC_READ_U32(datagram->data) & 0x7fffffff; + diff_ms = (datagram->jiffies_received - fsm->jiffies_start) * 1000 / HZ; + + if (abs_sync_diff > EC_DC_MAX_SYNC_DIFF_NS) { + + if (diff_ms >= EC_DC_SYNC_WAIT_MS) { + EC_SLAVE_WARN(slave, "Slave did not sync after %lu ms.\n", + diff_ms); + } else { + EC_SLAVE_DBG(slave, 1, "Sync after %4lu ms: %10u ns\n", + diff_ms, abs_sync_diff); + + // check synchrony again + ec_datagram_fprd(datagram, slave->station_address, 0x092c, 4); + fsm->retries = EC_FSM_RETRIES; + return; + } + } else { + EC_SLAVE_DBG(slave, 1, "%u ns difference after %lu ms.\n", + abs_sync_diff, diff_ms); + } + + // set DC start time (roughly in the future, not in-phase) + start_time = master->app_time + EC_DC_START_OFFSET; // now + X ns + + if (sync0->cycle_time) { + // find correct phase + if (master->dc_ref_time) { + u64 diff, start; + u32 remainder, cycle; + + diff = start_time - master->dc_ref_time; + cycle = sync0->cycle_time + sync1->cycle_time; + remainder = do_div(diff, cycle); + + start = start_time + cycle - remainder + sync0->shift_time; + + EC_SLAVE_DBG(slave, 1, " ref_time=%llu\n", master->dc_ref_time); + EC_SLAVE_DBG(slave, 1, " app_time=%llu\n", master->app_time); + EC_SLAVE_DBG(slave, 1, " start_time=%llu\n", start_time); + EC_SLAVE_DBG(slave, 1, " cycle=%u\n", cycle); + EC_SLAVE_DBG(slave, 1, " shift_time=%i\n", sync0->shift_time); + EC_SLAVE_DBG(slave, 1, " remainder=%u\n", remainder); + EC_SLAVE_DBG(slave, 1, " start=%llu\n", start); + start_time = start; + } else { + EC_SLAVE_WARN(slave, "No application time supplied." + " Cyclic start time will not be in phase.\n"); + } + } + + EC_SLAVE_DBG(slave, 1, "Setting DC cyclic operation" + " start time to %llu.\n", start_time); + + ec_datagram_fpwr(datagram, slave->station_address, 0x0990, 8); + EC_WRITE_U64(datagram->data, start_time); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_dc_start; +} + +/*****************************************************************************/ + +/** Slave configuration state: DC START. + */ +void ec_fsm_slave_config_state_dc_start( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + ec_slave_config_t *config = slave->config; + + if (!config) { // config removed in the meantime + ec_fsm_slave_config_reconfigure(fsm); + return; + } + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive DC start time datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to set DC start time: "); + ec_datagram_print_wc_error(datagram); + return; + } + + EC_SLAVE_DBG(slave, 1, "Setting DC AssignActivate to 0x%04x.\n", + config->dc_assign_activate); + + // assign sync unit to EtherCAT or PDI + ec_datagram_fpwr(datagram, slave->station_address, 0x0980, 2); + EC_WRITE_U16(datagram->data, config->dc_assign_activate); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_config_state_dc_assign; +} + +/*****************************************************************************/ + +/** Slave configuration state: DC ASSIGN. + */ +void ec_fsm_slave_config_state_dc_assign( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to receive DC activation datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + EC_SLAVE_ERR(slave, "Failed to activate DC: "); + ec_datagram_print_wc_error(datagram); + return; + } + + ec_fsm_slave_config_enter_safeop(fsm); +} + +/*****************************************************************************/ + +/** Request SAFEOP state. + */ +void ec_fsm_slave_config_enter_safeop( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + fsm->state = ec_fsm_slave_config_state_safeop; + ec_fsm_change_start(fsm->fsm_change, fsm->slave, EC_SLAVE_STATE_SAFEOP); + ec_fsm_change_exec(fsm->fsm_change); // execute immediately +} + +/*****************************************************************************/ + +/** Slave configuration state: SAFEOP. + */ +void ec_fsm_slave_config_state_safeop( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (ec_fsm_change_exec(fsm->fsm_change)) return; + + if (!ec_fsm_change_success(fsm->fsm_change)) { + if (!fsm->fsm_change->spontaneous_change) + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + return; + } + + // slave is now in SAFEOP + + EC_SLAVE_DBG(slave, 1, "Now in SAFEOP.\n"); + + if (fsm->slave->current_state == fsm->slave->requested_state) { + fsm->state = ec_fsm_slave_config_state_end; // successful + EC_SLAVE_DBG(slave, 1, "Finished configuration.\n"); + return; + } + + ec_fsm_slave_config_enter_soe_conf_safeop(fsm); +} + +/*****************************************************************************/ + +/** Check for SoE configurations to be applied in SAFEOP. + */ +void ec_fsm_slave_config_enter_soe_conf_safeop( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_soe_request_t *req; + + if (!slave->config) { + ec_fsm_slave_config_enter_op(fsm); + return; + } + + list_for_each_entry(req, &slave->config->soe_configs, list) { + if (req->al_state == EC_AL_STATE_SAFEOP) { + // start SoE configuration + fsm->state = ec_fsm_slave_config_state_soe_conf_safeop; + fsm->soe_request = req; + ec_soe_request_copy(&fsm->soe_request_copy, fsm->soe_request); + ec_soe_request_write(&fsm->soe_request_copy); + ec_fsm_soe_transfer(fsm->fsm_soe, fsm->slave, + &fsm->soe_request_copy); + ec_fsm_soe_exec(fsm->fsm_soe, fsm->datagram); + return; + } + } + + // No SoE configuration to be applied in SAFEOP + ec_fsm_slave_config_enter_op(fsm); +} + +/*****************************************************************************/ + +/** Slave configuration state: SOE_CONF. + */ +void ec_fsm_slave_config_state_soe_conf_safeop( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (ec_fsm_soe_exec(fsm->fsm_soe, fsm->datagram)) { + return; + } + + if (!ec_fsm_soe_success(fsm->fsm_soe)) { + EC_SLAVE_ERR(slave, "SoE configuration failed.\n"); + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + return; + } + + if (!fsm->slave->config) { // config removed in the meantime + ec_fsm_slave_config_reconfigure(fsm); + return; + } + + // Another IDN to configure in SAFEOP? + while (fsm->soe_request->list.next != &fsm->slave->config->soe_configs) { + fsm->soe_request = list_entry(fsm->soe_request->list.next, + ec_soe_request_t, list); + if (fsm->soe_request->al_state == EC_AL_STATE_SAFEOP) { + ec_soe_request_copy(&fsm->soe_request_copy, fsm->soe_request); + ec_soe_request_write(&fsm->soe_request_copy); + ec_fsm_soe_transfer(fsm->fsm_soe, fsm->slave, + &fsm->soe_request_copy); + ec_fsm_soe_exec(fsm->fsm_soe, fsm->datagram); + return; + } + } + + // All SAFEOP IDNs are now configured. + ec_fsm_slave_config_enter_op(fsm); +} + +/*****************************************************************************/ + +/** Bring slave to OP. + */ +void ec_fsm_slave_config_enter_op( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + // set state to OP + fsm->state = ec_fsm_slave_config_state_op; + ec_fsm_change_start(fsm->fsm_change, fsm->slave, EC_SLAVE_STATE_OP); + ec_fsm_change_exec(fsm->fsm_change); // execute immediately +} + +/*****************************************************************************/ + +/** Slave configuration state: OP + */ +void ec_fsm_slave_config_state_op( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (ec_fsm_change_exec(fsm->fsm_change)) return; + + if (!ec_fsm_change_success(fsm->fsm_change)) { + if (!fsm->fsm_change->spontaneous_change) + slave->error_flag = 1; + fsm->state = ec_fsm_slave_config_state_error; + return; + } + + // slave is now in OP + + EC_SLAVE_DBG(slave, 1, "Now in OP. Finished configuration.\n"); + + fsm->state = ec_fsm_slave_config_state_end; // successful +} + +/*****************************************************************************/ + +/** Reconfigure the slave starting at INIT. + */ +void ec_fsm_slave_config_reconfigure( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ + EC_SLAVE_DBG(fsm->slave, 1, "Slave configuration detached during " + "configuration. Reconfiguring."); + + ec_fsm_slave_config_enter_init(fsm); // reconfigure +} + +/****************************************************************************** + * Common state functions + *****************************************************************************/ + +/** State: ERROR. + */ +void ec_fsm_slave_config_state_error( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ +} + +/*****************************************************************************/ + +/** State: END. + */ +void ec_fsm_slave_config_state_end( + ec_fsm_slave_config_t *fsm /**< slave state machine */ + ) +{ +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_slave_config.h b/net/ethercat/master/fsm_slave_config.h new file mode 100644 index 000000000000..0111be7bdaa1 --- /dev/null +++ b/net/ethercat/master/fsm_slave_config.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave configuration state machine. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_SLAVE_CONFIG_H__ +#define __EC_FSM_SLAVE_CONFIG_H__ + +#include "globals.h" +#include "slave.h" +#include "datagram.h" +#include "fsm_change.h" +#include "fsm_coe.h" +#include "fsm_pdo.h" + +/*****************************************************************************/ + +/** \see ec_fsm_slave_config */ +typedef struct ec_fsm_slave_config ec_fsm_slave_config_t; + +/** Finite state machine to configure an EtherCAT slave. + */ +struct ec_fsm_slave_config +{ + ec_datagram_t *datagram; /**< Datagram used in the state machine. */ + ec_fsm_change_t *fsm_change; /**< State change state machine. */ + ec_fsm_coe_t *fsm_coe; /**< CoE state machine. */ + ec_fsm_soe_t *fsm_soe; /**< SoE state machine. */ + ec_fsm_pdo_t *fsm_pdo; /**< PDO configuration state machine. */ + + ec_slave_t *slave; /**< Slave the FSM runs on. */ + void (*state)(ec_fsm_slave_config_t *); /**< State function. */ + unsigned int retries; /**< Retries on datagram timeout. */ + ec_sdo_request_t *request; /**< SDO request for SDO configuration. */ + ec_sdo_request_t request_copy; /**< Copied SDO request. */ + ec_soe_request_t *soe_request; /**< SDO request for SDO configuration. */ + ec_soe_request_t soe_request_copy; /**< Copied SDO request. */ + unsigned long jiffies_start; /**< For timeout calculations. */ + unsigned int take_time; /**< Store jiffies after datagram reception. */ +}; + +/*****************************************************************************/ + +void ec_fsm_slave_config_init(ec_fsm_slave_config_t *, ec_datagram_t *, + ec_fsm_change_t *, ec_fsm_coe_t *, ec_fsm_soe_t *, ec_fsm_pdo_t *); +void ec_fsm_slave_config_clear(ec_fsm_slave_config_t *); + +void ec_fsm_slave_config_start(ec_fsm_slave_config_t *, ec_slave_t *); + +int ec_fsm_slave_config_exec(ec_fsm_slave_config_t *); +int ec_fsm_slave_config_success(const ec_fsm_slave_config_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_slave_scan.c b/net/ethercat/master/fsm_slave_scan.c new file mode 100644 index 000000000000..6f36ba23506e --- /dev/null +++ b/net/ethercat/master/fsm_slave_scan.c @@ -0,0 +1,1121 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave state machines. +*/ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "slave_config.h" + +#include "fsm_slave_scan.h" + +/*****************************************************************************/ + +void ec_fsm_slave_scan_state_start(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_address(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_state(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_base(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_dc_cap(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_dc_times(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_datalink(ec_fsm_slave_scan_t *); +#ifdef EC_SII_ASSIGN +void ec_fsm_slave_scan_state_assign_sii(ec_fsm_slave_scan_t *); +#endif +void ec_fsm_slave_scan_state_sii_size(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_sii_data(ec_fsm_slave_scan_t *); +#ifdef EC_REGALIAS +void ec_fsm_slave_scan_state_regalias(ec_fsm_slave_scan_t *); +#endif +void ec_fsm_slave_scan_state_preop(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_sync(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_pdos(ec_fsm_slave_scan_t *); + +void ec_fsm_slave_scan_state_end(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_state_error(ec_fsm_slave_scan_t *); + +void ec_fsm_slave_scan_enter_datalink(ec_fsm_slave_scan_t *); +#ifdef EC_REGALIAS +void ec_fsm_slave_scan_enter_regalias(ec_fsm_slave_scan_t *); +#endif +void ec_fsm_slave_scan_enter_preop(ec_fsm_slave_scan_t *); +void ec_fsm_slave_scan_enter_pdos(ec_fsm_slave_scan_t *); + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_slave_scan_init( + ec_fsm_slave_scan_t *fsm, /**< Slave scanning state machine. */ + ec_datagram_t *datagram, /**< Datagram to use. */ + ec_fsm_slave_config_t *fsm_slave_config, /**< Slave configuration + state machine to use. */ + ec_fsm_pdo_t *fsm_pdo /**< PDO configuration machine to use. */ + ) +{ + fsm->datagram = datagram; + fsm->fsm_slave_config = fsm_slave_config; + fsm->fsm_pdo = fsm_pdo; + + // init sub state machines + ec_fsm_sii_init(&fsm->fsm_sii, fsm->datagram); +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_slave_scan_clear(ec_fsm_slave_scan_t *fsm /**< slave state machine */) +{ + // clear sub state machines + ec_fsm_sii_clear(&fsm->fsm_sii); +} + +/*****************************************************************************/ + +/** + * Start slave scan state machine. + */ + +void ec_fsm_slave_scan_start( + ec_fsm_slave_scan_t *fsm, /**< slave state machine */ + ec_slave_t *slave /**< slave to configure */ + ) +{ + fsm->slave = slave; + fsm->state = ec_fsm_slave_scan_state_start; +} + +/*****************************************************************************/ + +/** + \return false, if state machine has terminated +*/ + +int ec_fsm_slave_scan_running(const ec_fsm_slave_scan_t *fsm /**< slave state machine */) +{ + return fsm->state != ec_fsm_slave_scan_state_end + && fsm->state != ec_fsm_slave_scan_state_error; +} + +/*****************************************************************************/ + +/** + Executes the current state of the state machine. + If the state machine's datagram is not sent or received yet, the execution + of the state machine is delayed to the next cycle. + \return false, if state machine has terminated +*/ + +int ec_fsm_slave_scan_exec(ec_fsm_slave_scan_t *fsm /**< slave state machine */) +{ + if (fsm->datagram->state == EC_DATAGRAM_SENT + || fsm->datagram->state == EC_DATAGRAM_QUEUED) { + // datagram was not sent or received yet. + return ec_fsm_slave_scan_running(fsm); + } + + fsm->state(fsm); + return ec_fsm_slave_scan_running(fsm); +} + +/*****************************************************************************/ + +/** + \return true, if the state machine terminated gracefully +*/ + +int ec_fsm_slave_scan_success(const ec_fsm_slave_scan_t *fsm /**< slave state machine */) +{ + return fsm->state == ec_fsm_slave_scan_state_end; +} + +/****************************************************************************** + * slave scan state machine + *****************************************************************************/ + +/** + Slave scan state: START. + First state of the slave state machine. Writes the station address to the + slave, according to its ring position. +*/ + +void ec_fsm_slave_scan_state_start(ec_fsm_slave_scan_t *fsm /**< slave state machine */) +{ + // write station address + ec_datagram_apwr(fsm->datagram, fsm->slave->ring_position, 0x0010, 2); + EC_WRITE_U16(fsm->datagram->data, fsm->slave->station_address); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_address; +} + +/*****************************************************************************/ + +/** + Slave scan state: ADDRESS. +*/ + +void ec_fsm_slave_scan_state_address( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(fsm->slave, + "Failed to receive station address datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(fsm->slave, "Failed to write station address: "); + ec_datagram_print_wc_error(datagram); + return; + } + + // Read AL state + ec_datagram_fprd(datagram, fsm->slave->station_address, 0x0130, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_state; +} + +/*****************************************************************************/ + +/** + Slave scan state: STATE. +*/ + +void ec_fsm_slave_scan_state_state( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to receive AL state datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to read AL state: "); + ec_datagram_print_wc_error(datagram); + return; + } + + slave->current_state = EC_READ_U8(datagram->data); + if (slave->current_state & EC_SLAVE_STATE_ACK_ERR) { + char state_str[EC_STATE_STRING_SIZE]; + ec_state_string(slave->current_state, state_str, 0); + EC_SLAVE_WARN(slave, "Slave has state error bit set (%s)!\n", + state_str); + } + + // read base data + ec_datagram_fprd(datagram, fsm->slave->station_address, 0x0000, 12); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_base; +} + +/*****************************************************************************/ + +/** Slave scan state: BASE. + */ +void ec_fsm_slave_scan_state_base( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + u8 octet; + int i; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to receive base data datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to read base data: "); + ec_datagram_print_wc_error(datagram); + return; + } + + slave->base_type = EC_READ_U8 (datagram->data); + slave->base_revision = EC_READ_U8 (datagram->data + 1); + slave->base_build = EC_READ_U16(datagram->data + 2); + + slave->base_fmmu_count = EC_READ_U8 (datagram->data + 4); + if (slave->base_fmmu_count > EC_MAX_FMMUS) { + EC_SLAVE_WARN(slave, "Slave has more FMMUs (%u) than the master can" + " handle (%u).\n", slave->base_fmmu_count, EC_MAX_FMMUS); + slave->base_fmmu_count = EC_MAX_FMMUS; + } + + slave->base_sync_count = EC_READ_U8(datagram->data + 5); + if (slave->base_sync_count > EC_MAX_SYNC_MANAGERS) { + EC_SLAVE_WARN(slave, "Slave provides more sync managers (%u)" + " than the master can handle (%u).\n", + slave->base_sync_count, EC_MAX_SYNC_MANAGERS); + slave->base_sync_count = EC_MAX_SYNC_MANAGERS; + } + + octet = EC_READ_U8(datagram->data + 7); + for (i = 0; i < EC_MAX_PORTS; i++) { + slave->ports[i].desc = (octet >> (2 * i)) & 0x03; + } + + octet = EC_READ_U8(datagram->data + 8); + slave->base_fmmu_bit_operation = octet & 0x01; + slave->base_dc_supported = (octet >> 2) & 0x01; + slave->base_dc_range = ((octet >> 3) & 0x01) ? EC_DC_64 : EC_DC_32; + + if (slave->base_dc_supported) { + // read DC capabilities + ec_datagram_fprd(datagram, slave->station_address, 0x0910, + slave->base_dc_range == EC_DC_64 ? 8 : 4); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_dc_cap; + } else { + ec_fsm_slave_scan_enter_datalink(fsm); + } +} + +/*****************************************************************************/ + +/** + Slave scan state: DC CAPABILITIES. +*/ + +void ec_fsm_slave_scan_state_dc_cap( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to receive system time datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter == 1) { + slave->has_dc_system_time = 1; + EC_SLAVE_DBG(slave, 1, "Slave has the System Time register.\n"); + } else if (datagram->working_counter == 0) { + EC_SLAVE_DBG(slave, 1, "Slave has no System Time register; delay " + "measurement only.\n"); + } else { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to determine, if system time register is " + "supported: "); + ec_datagram_print_wc_error(datagram); + return; + } + + // read DC port receive times + ec_datagram_fprd(datagram, slave->station_address, 0x0900, 16); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_dc_times; +} + +/*****************************************************************************/ + +/** + Slave scan state: DC TIMES. +*/ + +void ec_fsm_slave_scan_state_dc_times( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + int i; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to receive system time datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to get DC receive times: "); + ec_datagram_print_wc_error(datagram); + return; + } + + for (i = 0; i < EC_MAX_PORTS; i++) { + slave->ports[i].receive_time = EC_READ_U32(datagram->data + 4 * i); + } + + ec_fsm_slave_scan_enter_datalink(fsm); +} + +/*****************************************************************************/ + +/** + Slave scan entry function: DATALINK. +*/ + +void ec_fsm_slave_scan_enter_datalink( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + // read data link status + ec_datagram_fprd(datagram, slave->station_address, 0x0110, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_datalink; +} + +/*****************************************************************************/ + +/** Enter slave scan state SII_SIZE. + */ +void ec_fsm_slave_scan_enter_sii_size( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + // Start fetching SII size + + EC_SLAVE_DBG(fsm->slave, 1, "Determining SII size.\n"); + + fsm->sii_offset = EC_FIRST_SII_CATEGORY_OFFSET; // first category header + ec_fsm_sii_read(&fsm->fsm_sii, fsm->slave, fsm->sii_offset, + EC_FSM_SII_USE_CONFIGURED_ADDRESS); + fsm->state = ec_fsm_slave_scan_state_sii_size; + fsm->state(fsm); // execute state immediately +} + +/*****************************************************************************/ + +#ifdef EC_SII_ASSIGN + +/** Enter slave scan state ASSIGN_SII. + */ +void ec_fsm_slave_scan_enter_assign_sii( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + EC_SLAVE_DBG(slave, 1, "Assigning SII access to EtherCAT.\n"); + + // assign SII to ECAT + ec_datagram_fpwr(datagram, slave->station_address, 0x0500, 1); + EC_WRITE_U8(datagram->data, 0x00); // EtherCAT + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_assign_sii; +} + +#endif + +/*****************************************************************************/ + +/** + Slave scan state: DATALINK. +*/ + +void ec_fsm_slave_scan_state_datalink( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + uint16_t dl_status; + unsigned int i; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to receive DL status datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to read DL status: "); + ec_datagram_print_wc_error(datagram); + return; + } + + dl_status = EC_READ_U16(datagram->data); + for (i = 0; i < EC_MAX_PORTS; i++) { + slave->ports[i].link.link_up = + dl_status & (1 << (4 + i)) ? 1 : 0; + slave->ports[i].link.loop_closed = + dl_status & (1 << (8 + i * 2)) ? 1 : 0; + slave->ports[i].link.signal_detected = + dl_status & (1 << (9 + i * 2)) ? 1 : 0; + } + +#ifdef EC_SII_ASSIGN + ec_fsm_slave_scan_enter_assign_sii(fsm); +#else + ec_fsm_slave_scan_enter_sii_size(fsm); +#endif +} + +/*****************************************************************************/ + +#ifdef EC_SII_ASSIGN + +/** + Slave scan state: ASSIGN_SII. +*/ + +void ec_fsm_slave_scan_state_assign_sii( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + return; + } + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + EC_SLAVE_WARN(slave, "Failed to receive SII assignment datagram: "); + ec_datagram_print_state(datagram); + // Try to go on, probably assignment is correct + goto continue_with_sii_size; + } + + if (datagram->working_counter != 1) { + EC_SLAVE_WARN(slave, "Failed to assign SII to EtherCAT: "); + ec_datagram_print_wc_error(datagram); + // Try to go on, probably assignment is correct + } + +continue_with_sii_size: + ec_fsm_slave_scan_enter_sii_size(fsm); +} + +#endif + +/*****************************************************************************/ + +/** + Slave scan state: SII SIZE. +*/ + +void ec_fsm_slave_scan_state_sii_size( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + uint16_t cat_type, cat_size; + + if (ec_fsm_sii_exec(&fsm->fsm_sii)) + return; + + if (!ec_fsm_sii_success(&fsm->fsm_sii)) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to determine SII content size:" + " Reading word offset 0x%04x failed. Assuming %u words.\n", + fsm->sii_offset, EC_FIRST_SII_CATEGORY_OFFSET); + slave->sii_nwords = EC_FIRST_SII_CATEGORY_OFFSET; + goto alloc_sii; + } + + cat_type = EC_READ_U16(fsm->fsm_sii.value); + cat_size = EC_READ_U16(fsm->fsm_sii.value + 2); + + if (cat_type != 0xFFFF) { // not the last category + off_t next_offset = 2UL + fsm->sii_offset + cat_size; + + EC_SLAVE_DBG(slave, 1, "Found category type %u with size %u." + " Proceeding to offset %zu.\n", + cat_type, cat_size, next_offset); + + if (next_offset >= EC_MAX_SII_SIZE) { + EC_SLAVE_WARN(slave, "SII size exceeds %u words" + " (0xffff limiter missing?).\n", EC_MAX_SII_SIZE); + // cut off category data... + slave->sii_nwords = EC_FIRST_SII_CATEGORY_OFFSET; + goto alloc_sii; + } + fsm->sii_offset = next_offset; + ec_fsm_sii_read(&fsm->fsm_sii, slave, fsm->sii_offset, + EC_FSM_SII_USE_CONFIGURED_ADDRESS); + ec_fsm_sii_exec(&fsm->fsm_sii); // execute state immediately + return; + } + + slave->sii_nwords = fsm->sii_offset + 1; + +alloc_sii: + if (slave->sii_words) { + EC_SLAVE_WARN(slave, "Freeing old SII data...\n"); + kfree(slave->sii_words); + } + + if (!(slave->sii_words = + (uint16_t *) kmalloc(slave->sii_nwords * 2, GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate %zu words of SII data.\n", + slave->sii_nwords); + slave->sii_nwords = 0; + slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + return; + } + + // Start fetching SII contents + + fsm->state = ec_fsm_slave_scan_state_sii_data; + fsm->sii_offset = 0x0000; + ec_fsm_sii_read(&fsm->fsm_sii, slave, fsm->sii_offset, + EC_FSM_SII_USE_CONFIGURED_ADDRESS); + ec_fsm_sii_exec(&fsm->fsm_sii); // execute state immediately +} + +/*****************************************************************************/ + +/** + Slave scan state: SII DATA. +*/ + +void ec_fsm_slave_scan_state_sii_data(ec_fsm_slave_scan_t *fsm + /**< slave state machine */) +{ + ec_slave_t *slave = fsm->slave; + uint16_t *cat_word, cat_type, cat_size; + + if (ec_fsm_sii_exec(&fsm->fsm_sii)) return; + + if (!ec_fsm_sii_success(&fsm->fsm_sii)) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to fetch SII contents.\n"); + return; + } + + // 2 words fetched + + if (fsm->sii_offset + 2 <= slave->sii_nwords) { // 2 words fit + memcpy(slave->sii_words + fsm->sii_offset, fsm->fsm_sii.value, 4); + } else { // copy the last word + memcpy(slave->sii_words + fsm->sii_offset, fsm->fsm_sii.value, 2); + } + + if (fsm->sii_offset + 2 < slave->sii_nwords) { + // fetch the next 2 words + fsm->sii_offset += 2; + ec_fsm_sii_read(&fsm->fsm_sii, slave, fsm->sii_offset, + EC_FSM_SII_USE_CONFIGURED_ADDRESS); + ec_fsm_sii_exec(&fsm->fsm_sii); // execute state immediately + return; + } + + // Evaluate SII contents + + ec_slave_clear_sync_managers(slave); + + slave->sii.alias = + EC_READ_U16(slave->sii_words + 0x0004); + slave->effective_alias = slave->sii.alias; + slave->sii.vendor_id = + EC_READ_U32(slave->sii_words + 0x0008); + slave->sii.product_code = + EC_READ_U32(slave->sii_words + 0x000A); + slave->sii.revision_number = + EC_READ_U32(slave->sii_words + 0x000C); + slave->sii.serial_number = + EC_READ_U32(slave->sii_words + 0x000E); + slave->sii.boot_rx_mailbox_offset = + EC_READ_U16(slave->sii_words + 0x0014); + slave->sii.boot_rx_mailbox_size = + EC_READ_U16(slave->sii_words + 0x0015); + slave->sii.boot_tx_mailbox_offset = + EC_READ_U16(slave->sii_words + 0x0016); + slave->sii.boot_tx_mailbox_size = + EC_READ_U16(slave->sii_words + 0x0017); + slave->sii.std_rx_mailbox_offset = + EC_READ_U16(slave->sii_words + 0x0018); + slave->sii.std_rx_mailbox_size = + EC_READ_U16(slave->sii_words + 0x0019); + slave->sii.std_tx_mailbox_offset = + EC_READ_U16(slave->sii_words + 0x001A); + slave->sii.std_tx_mailbox_size = + EC_READ_U16(slave->sii_words + 0x001B); + slave->sii.mailbox_protocols = + EC_READ_U16(slave->sii_words + 0x001C); + if (slave->sii.mailbox_protocols) { + int need_delim = 0; + uint16_t all = EC_MBOX_AOE | EC_MBOX_COE | EC_MBOX_FOE | + EC_MBOX_SOE | EC_MBOX_VOE; + if ((slave->sii.mailbox_protocols & all) && + slave->master->debug_level >= 1) { + EC_SLAVE_DBG(slave, 1, "Slave announces to support "); + if (slave->sii.mailbox_protocols & EC_MBOX_AOE) { + printk(KERN_CONT "AoE"); + need_delim = 1; + } + if (slave->sii.mailbox_protocols & EC_MBOX_COE) { + if (need_delim) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "CoE"); + need_delim = 1; + } + if (slave->sii.mailbox_protocols & EC_MBOX_FOE) { + if (need_delim) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "FoE"); + need_delim = 1; + } + if (slave->sii.mailbox_protocols & EC_MBOX_SOE) { + if (need_delim) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "SoE"); + need_delim = 1; + } + if (slave->sii.mailbox_protocols & EC_MBOX_VOE) { + if (need_delim) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "VoE"); + need_delim = 1; + } + printk(KERN_CONT ".\n"); + } + if (slave->sii.mailbox_protocols & ~all) { + EC_SLAVE_DBG(slave, 1, "Slave announces to support unknown" + " mailbox protocols 0x%04X.", + slave->sii.mailbox_protocols & ~all); + } + } + else { + EC_SLAVE_DBG(slave, 1, "Slave announces to support no mailbox" + " protocols."); + } + + if (slave->sii.boot_rx_mailbox_offset == 0xffff || + slave->sii.boot_rx_mailbox_size == 0xffff || + slave->sii.boot_tx_mailbox_offset == 0xffff || + slave->sii.boot_tx_mailbox_size == 0xffff || + slave->sii.std_rx_mailbox_offset == 0xffff || + slave->sii.std_rx_mailbox_size == 0xffff || + slave->sii.std_tx_mailbox_offset == 0xffff || + slave->sii.std_tx_mailbox_size == 0xffff) { + slave->sii.mailbox_protocols = 0x0000; + EC_SLAVE_ERR(slave, "Invalid mailbox settings in SII." + " Disabling mailbox communication."); + } + + if (slave->sii_nwords == EC_FIRST_SII_CATEGORY_OFFSET) { + // sii does not contain category data + fsm->state = ec_fsm_slave_scan_state_end; + return; + } + + if (slave->sii_nwords < EC_FIRST_SII_CATEGORY_OFFSET + 1) { + EC_SLAVE_ERR(slave, "Unexpected end of SII data:" + " First category header missing.\n"); + goto end; + } + + // evaluate category data + cat_word = slave->sii_words + EC_FIRST_SII_CATEGORY_OFFSET; + while (EC_READ_U16(cat_word) != 0xFFFF) { + + // type and size words must fit + if (cat_word + 2 - slave->sii_words > slave->sii_nwords) { + EC_SLAVE_ERR(slave, "Unexpected end of SII data:" + " Category header incomplete.\n"); + goto end; + } + + cat_type = EC_READ_U16(cat_word) & 0x7FFF; + cat_size = EC_READ_U16(cat_word + 1); + cat_word += 2; + + if (cat_word + cat_size - slave->sii_words > slave->sii_nwords) { + EC_SLAVE_WARN(slave, "Unexpected end of SII data:" + " Category data incomplete.\n"); + goto end; + } + + switch (cat_type) { + case 0x000A: + if (ec_slave_fetch_sii_strings(slave, (uint8_t *) cat_word, + cat_size * 2)) + goto end; + break; + case 0x001E: + if (ec_slave_fetch_sii_general(slave, (uint8_t *) cat_word, + cat_size * 2)) + goto end; + break; + case 0x0028: + break; + case 0x0029: + if (ec_slave_fetch_sii_syncs(slave, (uint8_t *) cat_word, + cat_size * 2)) + goto end; + break; + case 0x0032: + if (ec_slave_fetch_sii_pdos( slave, (uint8_t *) cat_word, + cat_size * 2, EC_DIR_INPUT)) // TxPDO + goto end; + break; + case 0x0033: + if (ec_slave_fetch_sii_pdos( slave, (uint8_t *) cat_word, + cat_size * 2, EC_DIR_OUTPUT)) // RxPDO + goto end; + break; + default: + EC_SLAVE_DBG(slave, 1, "Unknown category type 0x%04X.\n", + cat_type); + } + + cat_word += cat_size; + if (cat_word - slave->sii_words >= slave->sii_nwords) { + EC_SLAVE_WARN(slave, "Unexpected end of SII data:" + " Next category header missing.\n"); + goto end; + } + } + +#ifdef EC_REGALIAS + ec_fsm_slave_scan_enter_regalias(fsm); +#else + if (slave->sii.mailbox_protocols & EC_MBOX_COE) { + ec_fsm_slave_scan_enter_preop(fsm); + } else { + fsm->state = ec_fsm_slave_scan_state_end; + } +#endif + return; + +end: + EC_SLAVE_ERR(slave, "Failed to analyze category data.\n"); + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; +} + +/*****************************************************************************/ + +#ifdef EC_REGALIAS + +/** Slave scan entry function: REGALIAS. + */ +void ec_fsm_slave_scan_enter_regalias( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + // read alias from register + EC_SLAVE_DBG(slave, 1, "Reading alias from register.\n"); + ec_datagram_fprd(datagram, slave->station_address, 0x0012, 2); + ec_datagram_zero(datagram); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_regalias; +} + +/*****************************************************************************/ + +/** Slave scan state: REGALIAS. + */ +void ec_fsm_slave_scan_state_regalias( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to receive register alias datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + EC_SLAVE_DBG(slave, 1, "Failed to read register alias.\n"); + } else { + slave->effective_alias = EC_READ_U16(datagram->data); + EC_SLAVE_DBG(slave, 1, "Read alias %u from register.\n", + slave->effective_alias); + } + + if (slave->sii.mailbox_protocols & EC_MBOX_COE) { + ec_fsm_slave_scan_enter_preop(fsm); + } else { + fsm->state = ec_fsm_slave_scan_state_end; + } +} + +#endif // defined EC_REGALIAS + +/*****************************************************************************/ + +/** Enter slave scan state PREOP. + */ +void ec_fsm_slave_scan_enter_preop( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + uint8_t current_state = slave->current_state & EC_SLAVE_STATE_MASK; + + if (current_state != EC_SLAVE_STATE_PREOP + && current_state != EC_SLAVE_STATE_SAFEOP + && current_state != EC_SLAVE_STATE_OP) { + if (slave->master->debug_level) { + char str[EC_STATE_STRING_SIZE]; + ec_state_string(current_state, str, 0); + EC_SLAVE_DBG(slave, 0, "Slave is not in the state" + " to do mailbox com (%s), setting to PREOP.\n", str); + } + + fsm->state = ec_fsm_slave_scan_state_preop; + ec_slave_request_state(slave, EC_SLAVE_STATE_PREOP); + ec_fsm_slave_config_start(fsm->fsm_slave_config, slave); + ec_fsm_slave_config_exec(fsm->fsm_slave_config); + } else { + EC_SLAVE_DBG(slave, 1, "Reading mailbox" + " sync manager configuration.\n"); + + /* Scan current sync manager configuration to get configured mailbox + * sizes. */ + ec_datagram_fprd(fsm->datagram, slave->station_address, 0x0800, + EC_SYNC_PAGE_SIZE * 2); + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_slave_scan_state_sync; + } +} + +/*****************************************************************************/ + +/** Slave scan state: PREOP. + */ +void ec_fsm_slave_scan_state_preop( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + if (ec_fsm_slave_config_exec(fsm->fsm_slave_config)) + return; + + if (!ec_fsm_slave_config_success(fsm->fsm_slave_config)) { + fsm->state = ec_fsm_slave_scan_state_error; + return; + } + + ec_fsm_slave_scan_enter_pdos(fsm); +} + +/*****************************************************************************/ + +/** Slave scan state: SYNC. + */ +void ec_fsm_slave_scan_state_sync( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_datagram_t *datagram = fsm->datagram; + ec_slave_t *slave = fsm->slave; + uint16_t tx_offset, tx_size, rx_offset, rx_size; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to receive sync manager" + " configuration datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + fsm->slave->error_flag = 1; + fsm->state = ec_fsm_slave_scan_state_error; + EC_SLAVE_ERR(slave, "Failed to read DL status: "); + ec_datagram_print_wc_error(datagram); + return; + } + + rx_offset = EC_READ_U16(datagram->data); + rx_size = EC_READ_U16(datagram->data + 2); + tx_offset = EC_READ_U16(datagram->data + 8); + tx_size = EC_READ_U16(datagram->data + 10); + + if (rx_size == 0xffff) { + fsm->state = ec_fsm_slave_scan_state_error; + slave->sii.mailbox_protocols = 0x0000; + EC_SLAVE_ERR(slave, "Invalid RX mailbox size (%u) configured." + " Disabling mailbox communication.", rx_size); + return; + } + + if (tx_size == 0xffff) { + fsm->state = ec_fsm_slave_scan_state_error; + slave->sii.mailbox_protocols = 0x0000; + EC_SLAVE_ERR(slave, "Invalid TX mailbox size (%u) configured." + " Disabling mailbox communication.", tx_size); + return; + } + + slave->configured_rx_mailbox_offset = rx_offset; + slave->configured_rx_mailbox_size = rx_size; + slave->configured_tx_mailbox_offset = tx_offset; + slave->configured_tx_mailbox_size = tx_size; + + EC_SLAVE_DBG(slave, 1, "Mailbox configuration:\n"); + EC_SLAVE_DBG(slave, 1, " RX offset=0x%04x size=%u\n", + slave->configured_rx_mailbox_offset, + slave->configured_rx_mailbox_size); + EC_SLAVE_DBG(slave, 1, " TX offset=0x%04x size=%u\n", + slave->configured_tx_mailbox_offset, + slave->configured_tx_mailbox_size); + + ec_fsm_slave_scan_enter_pdos(fsm); +} + +/*****************************************************************************/ + +/** Enter slave scan state PDOS. + */ +void ec_fsm_slave_scan_enter_pdos( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + ec_slave_t *slave = fsm->slave; + + EC_SLAVE_DBG(slave, 1, "Scanning PDO assignment and mapping.\n"); + fsm->state = ec_fsm_slave_scan_state_pdos; + ec_fsm_pdo_start_reading(fsm->fsm_pdo, slave); + ec_fsm_pdo_exec(fsm->fsm_pdo, fsm->datagram); // execute immediately +} + +/*****************************************************************************/ + +/** Slave scan state: PDOS. + */ +void ec_fsm_slave_scan_state_pdos( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ + if (ec_fsm_pdo_exec(fsm->fsm_pdo, fsm->datagram)) { + return; + } + + if (!ec_fsm_pdo_success(fsm->fsm_pdo)) { + fsm->state = ec_fsm_slave_scan_state_error; + return; + } + + // reading PDO configuration finished + fsm->state = ec_fsm_slave_scan_state_end; +} + +/****************************************************************************** + * Common state functions + *****************************************************************************/ + +/** State: ERROR. + */ +void ec_fsm_slave_scan_state_error( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ +} + +/*****************************************************************************/ + +/** State: END. + */ +void ec_fsm_slave_scan_state_end( + ec_fsm_slave_scan_t *fsm /**< slave state machine */ + ) +{ +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_slave_scan.h b/net/ethercat/master/fsm_slave_scan.h new file mode 100644 index 000000000000..ac63c6f38402 --- /dev/null +++ b/net/ethercat/master/fsm_slave_scan.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave scanning state machine. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_SLAVE_SCAN_H__ +#define __EC_FSM_SLAVE_SCAN_H__ + +#include "globals.h" +#include "datagram.h" +#include "slave.h" +#include "fsm_sii.h" +#include "fsm_change.h" +#include "fsm_coe.h" +#include "fsm_pdo.h" + +/*****************************************************************************/ + +/** \see ec_fsm_slave_scan */ +typedef struct ec_fsm_slave_scan ec_fsm_slave_scan_t; + +/** Finite state machine for scanning an EtherCAT slave. + */ +struct ec_fsm_slave_scan +{ + ec_slave_t *slave; /**< Slave the FSM runs on. */ + ec_datagram_t *datagram; /**< Datagram used in the state machine. */ + ec_fsm_slave_config_t *fsm_slave_config; /**< Slave configuration state + machine to use. */ + ec_fsm_pdo_t *fsm_pdo; /**< PDO configuration state machine to use. */ + unsigned int retries; /**< Retries on datagram timeout. */ + + void (*state)(ec_fsm_slave_scan_t *); /**< State function. */ + uint16_t sii_offset; /**< SII offset in words. */ + + ec_fsm_sii_t fsm_sii; /**< SII state machine. */ +}; + +/*****************************************************************************/ + +void ec_fsm_slave_scan_init(ec_fsm_slave_scan_t *, ec_datagram_t *, + ec_fsm_slave_config_t *, ec_fsm_pdo_t *); +void ec_fsm_slave_scan_clear(ec_fsm_slave_scan_t *); + +void ec_fsm_slave_scan_start(ec_fsm_slave_scan_t *, ec_slave_t *); + +int ec_fsm_slave_scan_exec(ec_fsm_slave_scan_t *); +int ec_fsm_slave_scan_success(const ec_fsm_slave_scan_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/fsm_soe.c b/net/ethercat/master/fsm_soe.c new file mode 100644 index 000000000000..00d39faffe7f --- /dev/null +++ b/net/ethercat/master/fsm_soe.c @@ -0,0 +1,846 @@ +/****************************************************************************** + * + * Copyright (C) 2006-2020 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT SoE state machines. +*/ + +/*****************************************************************************/ + +#include "globals.h" +#include "master.h" +#include "mailbox.h" +#include "fsm_soe.h" + +/*****************************************************************************/ + +/** Mailbox type for SoE. + */ +#define EC_MBOX_TYPE_SOE 0x05 + +/** SoE operations + */ +enum ec_soe_opcodes { + OPCODE_READ_REQUEST = 0x01, /**< Read request. */ + OPCODE_READ_RESPONSE = 0x02, /**< Read response. */ + OPCODE_WRITE_REQUEST = 0x03, /**< Write request. */ + OPCODE_WRITE_RESPONSE = 0x04 /**< Write response. */ +}; + +/** Size of all SoE headers. + */ +#define EC_SOE_SIZE 0x04 + +/** SoE header size. + */ +#define EC_SOE_HEADER_SIZE (EC_MBOX_HEADER_SIZE + EC_SOE_SIZE) + +/** SoE response timeout [ms]. + */ +#define EC_SOE_RESPONSE_TIMEOUT 1000 + +/*****************************************************************************/ + +void ec_fsm_soe_read_start(ec_fsm_soe_t *, ec_datagram_t *); +void ec_fsm_soe_read_request(ec_fsm_soe_t *, ec_datagram_t *); +void ec_fsm_soe_read_check(ec_fsm_soe_t *, ec_datagram_t *); +void ec_fsm_soe_read_response(ec_fsm_soe_t *, ec_datagram_t *); + +void ec_fsm_soe_write_start(ec_fsm_soe_t *, ec_datagram_t *); +void ec_fsm_soe_write_request(ec_fsm_soe_t *, ec_datagram_t *); +void ec_fsm_soe_write_check(ec_fsm_soe_t *, ec_datagram_t *); +void ec_fsm_soe_write_response(ec_fsm_soe_t *, ec_datagram_t *); + +void ec_fsm_soe_end(ec_fsm_soe_t *, ec_datagram_t *); +void ec_fsm_soe_error(ec_fsm_soe_t *, ec_datagram_t *); + +/*****************************************************************************/ + +extern const ec_code_msg_t soe_error_codes[]; + +/*****************************************************************************/ + +/** Outputs an SoE error code. +*/ +void ec_print_soe_error(const ec_slave_t *slave, uint16_t error_code) +{ + const ec_code_msg_t *error_msg; + + for (error_msg = soe_error_codes; error_msg->code; error_msg++) { + if (error_msg->code == error_code) { + EC_SLAVE_ERR(slave, "SoE error 0x%04X: \"%s\".\n", + error_msg->code, error_msg->message); + return; + } + } + + EC_SLAVE_ERR(slave, "Unknown SoE error 0x%04X.\n", error_code); +} + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_fsm_soe_init( + ec_fsm_soe_t *fsm /**< finite state machine */ + ) +{ + fsm->state = NULL; + fsm->datagram = NULL; + fsm->fragment_size = 0; +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_fsm_soe_clear( + ec_fsm_soe_t *fsm /**< finite state machine */ + ) +{ +} + +/*****************************************************************************/ + +/** Starts to transfer an IDN to/from a slave. + */ +void ec_fsm_soe_transfer( + ec_fsm_soe_t *fsm, /**< State machine. */ + ec_slave_t *slave, /**< EtherCAT slave. */ + ec_soe_request_t *request /**< SoE request. */ + ) +{ + fsm->slave = slave; + fsm->request = request; + + if (request->dir == EC_DIR_OUTPUT) { + fsm->state = ec_fsm_soe_write_start; + } else { + fsm->state = ec_fsm_soe_read_start; + } +} + +/*****************************************************************************/ + +/** Executes the current state of the state machine. + * + * \return 1 if the datagram was used, else 0. + */ +int ec_fsm_soe_exec( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + int datagram_used = 0; + + if (fsm->datagram && + (fsm->datagram->state == EC_DATAGRAM_INIT || + fsm->datagram->state == EC_DATAGRAM_QUEUED || + fsm->datagram->state == EC_DATAGRAM_SENT)) { + // datagram not received yet + return datagram_used; + } + + fsm->state(fsm, datagram); + + datagram_used = + fsm->state != ec_fsm_soe_end && fsm->state != ec_fsm_soe_error; + + if (datagram_used) { + fsm->datagram = datagram; + } else { + fsm->datagram = NULL; + } + + return datagram_used; +} + +/*****************************************************************************/ + +/** Returns, if the state machine terminated with success. + * + * \return non-zero if successful. + */ +int ec_fsm_soe_success(const ec_fsm_soe_t *fsm /**< Finite state machine */) +{ + return fsm->state == ec_fsm_soe_end; +} + +/*****************************************************************************/ + +/** Output information about a failed SoE transfer. + */ +void ec_fsm_soe_print_error(ec_fsm_soe_t *fsm /**< Finite state machine */) +{ + ec_soe_request_t *request = fsm->request; + + EC_SLAVE_ERR(fsm->slave, ""); + + if (request->dir == EC_DIR_OUTPUT) { + printk("Writing"); + } else { + printk("Reading"); + } + + printk(" IDN 0x%04X failed.\n", request->idn); +} + +/****************************************************************************** + * SoE read state machine + *****************************************************************************/ + +/** Prepare a read operation. + * + * \return 0 on success, otherwise a negative error code. + */ +int ec_fsm_soe_prepare_read( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + uint8_t *data; + ec_slave_t *slave = fsm->slave; + ec_master_t *master = slave->master; + ec_soe_request_t *request = fsm->request; + + data = ec_slave_mbox_prepare_send(slave, datagram, EC_MBOX_TYPE_SOE, + EC_SOE_SIZE); + if (IS_ERR(data)) { + return PTR_ERR(data); + } + + EC_WRITE_U8(data, OPCODE_READ_REQUEST | (request->drive_no & 0x07) << 5); + EC_WRITE_U8(data + 1, 1 << 6); // request value + EC_WRITE_U16(data + 2, request->idn); + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 0, "SSC read request:\n"); + ec_print_data(data, EC_SOE_SIZE); + } + + fsm->request->jiffies_sent = jiffies; + fsm->state = ec_fsm_soe_read_request; + + return 0; +} + +/*****************************************************************************/ + +/** SoE state: READ START. + */ +void ec_fsm_soe_read_start( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_soe_request_t *request = fsm->request; + + EC_SLAVE_DBG(slave, 1, "Reading IDN 0x%04X of drive %u.\n", request->idn, + request->drive_no); + + if (!(slave->sii.mailbox_protocols & EC_MBOX_SOE)) { + EC_SLAVE_ERR(slave, "Slave does not support SoE!\n"); + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + return; + } + + request->data_size = 0; + fsm->retries = EC_FSM_RETRIES; + + if (ec_fsm_soe_prepare_read(fsm, datagram)) { + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + } +} + +/*****************************************************************************/ + +/** SoE state: READ REQUEST. + */ +void ec_fsm_soe_read_request( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + unsigned long diff_ms; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + if (ec_fsm_soe_prepare_read(fsm, datagram)) { + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + } + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Failed to receive SoE read request: "); + ec_datagram_print_state(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + diff_ms = (jiffies - fsm->request->jiffies_sent) * 1000 / HZ; + + if (fsm->datagram->working_counter != 1) { + if (!fsm->datagram->working_counter) { + if (diff_ms < EC_SOE_RESPONSE_TIMEOUT) { + // no response; send request datagram again + if (ec_fsm_soe_prepare_read(fsm, datagram)) { + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + } + return; + } + } + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Reception of SoE read request" + " failed after %lu ms: ", diff_ms); + ec_datagram_print_wc_error(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + fsm->jiffies_start = fsm->datagram->jiffies_sent; + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_soe_read_check; +} + +/*****************************************************************************/ + +/** CoE state: READ CHECK. + */ +void ec_fsm_soe_read_check( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Failed to receive SoE mailbox check datagram: "); + ec_datagram_print_state(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Reception of SoE mailbox check" + " datagram failed: "); + ec_datagram_print_wc_error(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (fsm->datagram->jiffies_received - fsm->jiffies_start) * + 1000 / HZ; + if (diff_ms >= EC_SOE_RESPONSE_TIMEOUT) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Timeout after %lu ms while waiting for" + " read response.\n", diff_ms); + ec_fsm_soe_print_error(fsm); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_soe_read_response; +} + +/*****************************************************************************/ + +/** SoE state: READ RESPONSE. + */ +void ec_fsm_soe_read_response( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_master_t *master = slave->master; + uint8_t *data, mbox_prot, header, opcode, incomplete, error_flag, + value_included; + size_t rec_size, data_size; + ec_soe_request_t *req = fsm->request; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Failed to receive SoE read response datagram: "); + ec_datagram_print_state(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Reception of SoE read response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + return; + } + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 0, "SSC read response:\n"); + ec_print_data(data, rec_size); + } + + if (mbox_prot != EC_MBOX_TYPE_SOE) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + ec_fsm_soe_print_error(fsm); + return; + } + + if (rec_size < EC_SOE_SIZE) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Received currupted SoE read response" + " (%zu bytes)!\n", rec_size); + ec_print_data(data, rec_size); + ec_fsm_soe_print_error(fsm); + return; + } + + header = EC_READ_U8(data); + opcode = header & 0x7; + incomplete = (header >> 3) & 1; + error_flag = (header >> 4) & 1; + + if (opcode != OPCODE_READ_RESPONSE) { + EC_SLAVE_ERR(slave, "Received no read response (opcode %x).\n", + opcode); + ec_print_data(data, rec_size); + ec_fsm_soe_print_error(fsm); + fsm->state = ec_fsm_soe_error; + return; + } + + if (error_flag) { + req->error_code = EC_READ_U16(data + rec_size - 2); + EC_SLAVE_ERR(slave, "Received error response:\n"); + ec_print_soe_error(slave, req->error_code); + ec_fsm_soe_print_error(fsm); + fsm->state = ec_fsm_soe_error; + return; + } else { + req->error_code = 0x0000; + } + + value_included = (EC_READ_U8(data + 1) >> 6) & 1; + if (!value_included) { + EC_SLAVE_ERR(slave, "No value included!\n"); + ec_fsm_soe_print_error(fsm); + fsm->state = ec_fsm_soe_error; + return; + } + + data_size = rec_size - EC_SOE_SIZE; + if (ec_soe_request_append_data(req, + data + EC_SOE_SIZE, data_size)) { + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + return; + } + + if (incomplete) { + EC_SLAVE_DBG(slave, 1, "SoE data incomplete. Waiting for fragment" + " at offset %zu.\n", req->data_size); + fsm->jiffies_start = fsm->datagram->jiffies_sent; + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_soe_read_check; + } else { + if (master->debug_level) { + EC_SLAVE_DBG(slave, 0, "IDN data:\n"); + ec_print_data(req->data, req->data_size); + } + + fsm->state = ec_fsm_soe_end; // success + } +} + +/****************************************************************************** + * SoE write state machine + *****************************************************************************/ + +/** Write next fragment. + */ +void ec_fsm_soe_write_next_fragment( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_master_t *master = slave->master; + ec_soe_request_t *req = fsm->request; + uint8_t incomplete, *data; + size_t max_fragment_size, remaining_size; + uint16_t fragments_left; + + remaining_size = req->data_size - fsm->offset; + max_fragment_size = slave->configured_rx_mailbox_size - EC_SOE_HEADER_SIZE; + incomplete = remaining_size > max_fragment_size; + fsm->fragment_size = incomplete ? max_fragment_size : remaining_size; + fragments_left = remaining_size / fsm->fragment_size - 1; + if (remaining_size % fsm->fragment_size) { + fragments_left++; + } + + data = ec_slave_mbox_prepare_send(slave, datagram, EC_MBOX_TYPE_SOE, + EC_SOE_SIZE + fsm->fragment_size); + if (IS_ERR(data)) { + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + return; + } + + EC_WRITE_U8(data, OPCODE_WRITE_REQUEST | incomplete << 3 | + (req->drive_no & 0x07) << 5); + EC_WRITE_U8(data + 1, 1 << 6); // only value included + EC_WRITE_U16(data + 2, incomplete ? fragments_left : req->idn); + memcpy(data + EC_SOE_SIZE, req->data + fsm->offset, fsm->fragment_size); + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 0, "SSC write request:\n"); + ec_print_data(data, EC_SOE_SIZE + fsm->fragment_size); + } + + fsm->state = ec_fsm_soe_write_request; +} + +/*****************************************************************************/ + +/** SoE state: WRITE START. + */ +void ec_fsm_soe_write_start( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_soe_request_t *req = fsm->request; + + EC_SLAVE_DBG(slave, 1, "Writing IDN 0x%04X of drive %u (%zu byte).\n", + req->idn, req->drive_no, req->data_size); + + if (!(slave->sii.mailbox_protocols & EC_MBOX_SOE)) { + EC_SLAVE_ERR(slave, "Slave does not support SoE!\n"); + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + return; + } + + if (slave->configured_rx_mailbox_size <= EC_SOE_HEADER_SIZE) { + EC_SLAVE_ERR(slave, "Mailbox size (%u) too small for SoE write.\n", + slave->configured_rx_mailbox_size); + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + return; + } + + fsm->offset = 0; + fsm->retries = EC_FSM_RETRIES; + ec_fsm_soe_write_next_fragment(fsm, datagram); + req->jiffies_sent = jiffies; +} + +/*****************************************************************************/ + +/** SoE state: WRITE REQUEST. + */ +void ec_fsm_soe_write_request( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + unsigned long diff_ms; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_fsm_soe_write_next_fragment(fsm, datagram); + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Failed to receive SoE write request: "); + ec_datagram_print_state(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + diff_ms = (jiffies - fsm->request->jiffies_sent) * 1000 / HZ; + + if (fsm->datagram->working_counter != 1) { + if (!fsm->datagram->working_counter) { + if (diff_ms < EC_SOE_RESPONSE_TIMEOUT) { + // no response; send request datagram again + ec_fsm_soe_write_next_fragment(fsm, datagram); + return; + } + } + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Reception of SoE write request" + " failed after %lu ms: ", diff_ms); + ec_datagram_print_wc_error(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + // fragment successfully sent + fsm->offset += fsm->fragment_size; + + if (fsm->offset < fsm->request->data_size) { + // next fragment + fsm->retries = EC_FSM_RETRIES; + ec_fsm_soe_write_next_fragment(fsm, datagram); + fsm->request->jiffies_sent = jiffies; + } else { + // all fragments sent; query response + fsm->jiffies_start = fsm->datagram->jiffies_sent; + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_soe_write_check; + } +} + +/*****************************************************************************/ + +/** CoE state: WRITE CHECK. + */ +void ec_fsm_soe_write_check( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + return; + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Failed to receive SoE write request datagram: "); + ec_datagram_print_state(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Reception of SoE write request datagram: "); + ec_datagram_print_wc_error(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + if (!ec_slave_mbox_check(fsm->datagram)) { + unsigned long diff_ms = + (datagram->jiffies_received - fsm->jiffies_start) * 1000 / HZ; + if (diff_ms >= EC_SOE_RESPONSE_TIMEOUT) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Timeout after %lu ms while waiting" + " for write response.\n", diff_ms); + ec_fsm_soe_print_error(fsm); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + fsm->retries = EC_FSM_RETRIES; + fsm->state = ec_fsm_soe_write_response; +} + +/*****************************************************************************/ + +/** SoE state: WRITE RESPONSE. + */ +void ec_fsm_soe_write_response( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ + ec_slave_t *slave = fsm->slave; + ec_master_t *master = slave->master; + ec_soe_request_t *req = fsm->request; + uint8_t *data, mbox_prot, opcode, error_flag; + uint16_t idn; + size_t rec_size; + + if (fsm->datagram->state == EC_DATAGRAM_TIMED_OUT && fsm->retries--) { + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + return; // FIXME: request again? + } + + if (fsm->datagram->state != EC_DATAGRAM_RECEIVED) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Failed to receive SoE write" + " response datagram: "); + ec_datagram_print_state(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + if (fsm->datagram->working_counter != 1) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Reception of SoE write response failed: "); + ec_datagram_print_wc_error(fsm->datagram); + ec_fsm_soe_print_error(fsm); + return; + } + + data = ec_slave_mbox_fetch(slave, fsm->datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + fsm->state = ec_fsm_soe_error; + ec_fsm_soe_print_error(fsm); + return; + } + + if (master->debug_level) { + EC_SLAVE_DBG(slave, 0, "SSC write response:\n"); + ec_print_data(data, rec_size); + } + + if (mbox_prot != EC_MBOX_TYPE_SOE) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Received mailbox protocol 0x%02X as response.\n", + mbox_prot); + ec_fsm_soe_print_error(fsm); + return; + } + + if (rec_size < EC_SOE_SIZE) { + fsm->state = ec_fsm_soe_error; + EC_SLAVE_ERR(slave, "Received corrupted SoE write response" + " (%zu bytes)!\n", rec_size); + ec_print_data(data, rec_size); + ec_fsm_soe_print_error(fsm); + return; + } + + opcode = EC_READ_U8(data) & 0x7; + if (opcode != OPCODE_WRITE_RESPONSE) { + EC_SLAVE_ERR(slave, "Received no write response" + " (opcode %x).\n", opcode); + ec_print_data(data, rec_size); + ec_fsm_soe_print_error(fsm); + fsm->state = ec_fsm_soe_error; + return; + } + + idn = EC_READ_U16(data + 2); + if (idn != req->idn) { + EC_SLAVE_ERR(slave, "Received response for" + " wrong IDN 0x%04x.\n", idn); + ec_print_data(data, rec_size); + ec_fsm_soe_print_error(fsm); + fsm->state = ec_fsm_soe_error; + return; + } + + error_flag = (EC_READ_U8(data) >> 4) & 1; + if (error_flag) { + if (rec_size < EC_SOE_SIZE + 2) { + EC_SLAVE_ERR(slave, "Received corrupted error response" + " - error flag set, but received size is %zu.\n", + rec_size); + } else { + req->error_code = EC_READ_U16(data + EC_SOE_SIZE); + EC_SLAVE_ERR(slave, "Received error response:\n"); + ec_print_soe_error(slave, req->error_code); + } + ec_print_data(data, rec_size); + ec_fsm_soe_print_error(fsm); + fsm->state = ec_fsm_soe_error; + } else { + req->error_code = 0x0000; + fsm->state = ec_fsm_soe_end; // success + } +} + +/*****************************************************************************/ + +/** State: ERROR. + */ +void ec_fsm_soe_error( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +} + +/*****************************************************************************/ + +/** State: END. + */ +void ec_fsm_soe_end( + ec_fsm_soe_t *fsm, /**< finite state machine */ + ec_datagram_t *datagram /**< Datagram to use. */ + ) +{ +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/fsm_soe.h b/net/ethercat/master/fsm_soe.h new file mode 100644 index 000000000000..99aa204670cf --- /dev/null +++ b/net/ethercat/master/fsm_soe.h @@ -0,0 +1,75 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT CoE state machines. +*/ + +/*****************************************************************************/ + +#ifndef __EC_FSM_SOE_H__ +#define __EC_FSM_SOE_H__ + +#include "globals.h" +#include "datagram.h" +#include "slave.h" +#include "soe_request.h" + +/*****************************************************************************/ + +typedef struct ec_fsm_soe ec_fsm_soe_t; /**< \see ec_fsm_soe */ + +/** Finite state machines for the Sercos over EtherCAT protocol. + */ +struct ec_fsm_soe { + ec_slave_t *slave; /**< slave the FSM runs on */ + unsigned int retries; /**< retries upon datagram timeout */ + + void (*state)(ec_fsm_soe_t *, ec_datagram_t *); /**< CoE state function */ + ec_datagram_t *datagram; /**< Datagram used in the previous step. */ + unsigned long jiffies_start; /**< Timestamp. */ + ec_soe_request_t *request; /**< SoE request */ + off_t offset; /**< IDN data offset during fragmented write. */ + size_t fragment_size; /**< Size of the current fragment. */ +}; + +/*****************************************************************************/ + +void ec_fsm_soe_init(ec_fsm_soe_t *); +void ec_fsm_soe_clear(ec_fsm_soe_t *); + +void ec_fsm_soe_transfer(ec_fsm_soe_t *, ec_slave_t *, ec_soe_request_t *); + +int ec_fsm_soe_exec(ec_fsm_soe_t *, ec_datagram_t *); +int ec_fsm_soe_success(const ec_fsm_soe_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/globals.h b/net/ethercat/master/globals.h new file mode 100644 index 000000000000..840a160cf4ca --- /dev/null +++ b/net/ethercat/master/globals.h @@ -0,0 +1,317 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * Global definitions and macros. + */ + +/*****************************************************************************/ + +#ifndef __EC_MASTER_GLOBALS_H__ +#define __EC_MASTER_GLOBALS_H__ + +#include "../globals.h" +#include "../include/ecrt.h" + +/****************************************************************************** + * EtherCAT master + *****************************************************************************/ + +/** Datagram timeout in microseconds. */ +#define EC_IO_TIMEOUT 500 + +/** SDO injection timeout in microseconds. */ +#define EC_SDO_INJECTION_TIMEOUT 10000 + +/** Time to send a byte in nanoseconds. + * + * t_ns = 1 / (100 MBit/s / 8 bit/byte) = 80 ns/byte + */ +#define EC_BYTE_TRANSMISSION_TIME_NS 80 + +/** Number of state machine retries on datagram timeout. */ +#define EC_FSM_RETRIES 3 + +/** Seconds to wait before fetching SDO dictionary + after slave entered PREOP state. */ +#define EC_WAIT_SDO_DICT 3 + +/** Minimum size of a buffer used with ec_state_string(). */ +#define EC_STATE_STRING_SIZE 32 + +/** Maximum SII size in words, to avoid infinite reading. */ +#define EC_MAX_SII_SIZE 4096 + +/** Number of statistic rate intervals to maintain. */ +#define EC_RATE_COUNT 3 + +/****************************************************************************** + * EtherCAT protocol + *****************************************************************************/ + +/** Size of an EtherCAT frame header. */ +#define EC_FRAME_HEADER_SIZE 2 + +/** Size of an EtherCAT datagram header. */ +#define EC_DATAGRAM_HEADER_SIZE 10 + +/** Size of an EtherCAT datagram footer. */ +#define EC_DATAGRAM_FOOTER_SIZE 2 + +/** Size of the EtherCAT address field. */ +#define EC_ADDR_LEN 4 + +/** Resulting maximum data size of a single datagram in a frame. */ +#define EC_MAX_DATA_SIZE (ETH_DATA_LEN - EC_FRAME_HEADER_SIZE \ + - EC_DATAGRAM_HEADER_SIZE - EC_DATAGRAM_FOOTER_SIZE) + +/** Mailbox header size. */ +#define EC_MBOX_HEADER_SIZE 6 + +/** Word offset of first SII category. */ +#define EC_FIRST_SII_CATEGORY_OFFSET 0x40 + +/** Size of a sync manager configuration page. */ +#define EC_SYNC_PAGE_SIZE 8 + +/** Maximum number of FMMUs per slave. */ +#define EC_MAX_FMMUS 16 + +/** Size of an FMMU configuration page. */ +#define EC_FMMU_PAGE_SIZE 16 + +/** Number of DC sync signals. */ +#define EC_SYNC_SIGNAL_COUNT 2 + +/** Size of the datagram description string. + * + * This is also used as the maximum lenth of EoE device names. + **/ +#define EC_DATAGRAM_NAME_SIZE 20 + +/** Slave state mask. + * + * Apply this mask to a slave state byte to get the slave state without + * the error flag. + */ +#define EC_SLAVE_STATE_MASK 0x0F + +/** State of an EtherCAT slave. + */ +typedef enum { + EC_SLAVE_STATE_UNKNOWN = 0x00, + /**< unknown state */ + EC_SLAVE_STATE_INIT = 0x01, + /**< INIT state (no mailbox communication, no IO) */ + EC_SLAVE_STATE_PREOP = 0x02, + /**< PREOP state (mailbox communication, no IO) */ + EC_SLAVE_STATE_BOOT = 0x03, + /**< Bootstrap state (mailbox communication, firmware update) */ + EC_SLAVE_STATE_SAFEOP = 0x04, + /**< SAFEOP (mailbox communication and input update) */ + EC_SLAVE_STATE_OP = 0x08, + /**< OP (mailbox communication and input/output update) */ + EC_SLAVE_STATE_ACK_ERR = 0x10 + /**< Acknowledge/Error bit (no actual state) */ +} ec_slave_state_t; + +/** Supported mailbox protocols. + */ +enum { + EC_MBOX_AOE = 0x01, /**< ADS over EtherCAT */ + EC_MBOX_EOE = 0x02, /**< Ethernet over EtherCAT */ + EC_MBOX_COE = 0x04, /**< CANopen over EtherCAT */ + EC_MBOX_FOE = 0x08, /**< File-Access over EtherCAT */ + EC_MBOX_SOE = 0x10, /**< Servo-Profile over EtherCAT */ + EC_MBOX_VOE = 0x20 /**< Vendor specific */ +}; + +/** Slave information interface CANopen over EtherCAT details flags. + */ +typedef struct { + uint8_t enable_sdo : 1; /**< Enable SDO access. */ + uint8_t enable_sdo_info : 1; /**< SDO information service available. */ + uint8_t enable_pdo_assign : 1; /**< PDO mapping configurable. */ + uint8_t enable_pdo_configuration : 1; /**< PDO configuration possible. */ + uint8_t enable_upload_at_startup : 1; /**< ?. */ + uint8_t enable_sdo_complete_access : 1; /**< Complete access possible. */ +} ec_sii_coe_details_t; + +/** Slave information interface general flags. + */ +typedef struct { + uint8_t enable_safeop : 1; /**< ?. */ + uint8_t enable_not_lrw : 1; /**< Slave does not support LRW. */ +} ec_sii_general_flags_t; + +/** EtherCAT slave distributed clocks range. + */ +typedef enum { + EC_DC_32, /**< 32 bit. */ + EC_DC_64 /*< 64 bit for system time, system time offset and + port 0 receive time. */ +} ec_slave_dc_range_t; + +/** EtherCAT slave sync signal configuration. + */ +typedef struct { + uint32_t cycle_time; /**< Cycle time [ns]. */ + int32_t shift_time; /**< Shift time [ns]. */ +} ec_sync_signal_t; + +/** Access states for SDO entries. + * + * The access rights are managed per AL state. + */ +enum { + EC_SDO_ENTRY_ACCESS_PREOP, /**< Access rights in PREOP. */ + EC_SDO_ENTRY_ACCESS_SAFEOP, /**< Access rights in SAFEOP. */ + EC_SDO_ENTRY_ACCESS_OP, /**< Access rights in OP. */ + EC_SDO_ENTRY_ACCESS_COUNT /**< Number of states. */ +}; + +/** Master devices. + */ +typedef enum { + EC_DEVICE_MAIN, /**< Main device. */ + EC_DEVICE_BACKUP /**< Backup device */ +} ec_device_index_t; + +extern const char *ec_device_names[2]; // only main and backup! + +/*****************************************************************************/ + +/** Convenience macro for printing EtherCAT-specific information to syslog. + * + * This will print the message in \a fmt with a prefixed "EtherCAT: ". + * + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_INFO(fmt, args...) \ + printk(KERN_INFO "EtherCAT: " fmt, ##args) + +/** Convenience macro for printing EtherCAT-specific errors to syslog. + * + * This will print the message in \a fmt with a prefixed "EtherCAT ERROR: ". + * + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_ERR(fmt, args...) \ + printk(KERN_ERR "EtherCAT ERROR: " fmt, ##args) + +/** Convenience macro for printing EtherCAT-specific warnings to syslog. + * + * This will print the message in \a fmt with a prefixed "EtherCAT WARNING: ". + * + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_WARN(fmt, args...) \ + printk(KERN_WARNING "EtherCAT WARNING: " fmt, ##args) + +/** Convenience macro for printing EtherCAT debug messages to syslog. + * + * This will print the message in \a fmt with a prefixed "EtherCAT DEBUG: ". + * + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_DBG(fmt, args...) \ + printk(KERN_DEBUG "EtherCAT DEBUG: " fmt, ##args) + +/*****************************************************************************/ + +/** Absolute value. + */ +#define EC_ABS(X) ((X) >= 0 ? (X) : -(X)) + +/*****************************************************************************/ + +extern char *ec_master_version_str; + +/*****************************************************************************/ + +unsigned int ec_master_count(void); +void ec_print_data(const uint8_t *, size_t); +void ec_print_data_diff(const uint8_t *, const uint8_t *, size_t); +size_t ec_state_string(uint8_t, char *, uint8_t); +ssize_t ec_mac_print(const uint8_t *, char *); +int ec_mac_is_zero(const uint8_t *); + +ec_master_t *ecrt_request_master_err(unsigned int); + +/*****************************************************************************/ + +/** Code/Message pair. + * + * Some EtherCAT datagrams support reading a status code to display a certain + * message. This type allows to map a code to a message string. + */ +typedef struct { + uint32_t code; /**< Code. */ + const char *message; /**< Message belonging to \a code. */ +} ec_code_msg_t; + +/*****************************************************************************/ + +/** Generic request state. + * + * \attention If ever changing this, please be sure to adjust the \a + * state_table in master/sdo_request.c. + */ +typedef enum { + EC_INT_REQUEST_INIT, + EC_INT_REQUEST_QUEUED, + EC_INT_REQUEST_BUSY, + EC_INT_REQUEST_SUCCESS, + EC_INT_REQUEST_FAILURE +} ec_internal_request_state_t; + +/*****************************************************************************/ + +extern const ec_request_state_t ec_request_state_translation_table[]; + +/*****************************************************************************/ + +/** Origin type. + */ +typedef enum { + EC_ORIG_INTERNAL, /**< Internal. */ + EC_ORIG_EXTERNAL /**< External. */ +} ec_origin_t; + +/*****************************************************************************/ + +typedef struct ec_slave ec_slave_t; /**< \see ec_slave. */ + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/ioctl.c b/net/ethercat/master/ioctl.c new file mode 100644 index 000000000000..69fb35527a8a --- /dev/null +++ b/net/ethercat/master/ioctl.c @@ -0,0 +1,4646 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT master character device. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/vmalloc.h> + +#include "master.h" +#include "slave_config.h" +#include "voe_handler.h" +#include "ethernet.h" +#include "ioctl.h" + +/** Set to 1 to enable ioctl() latency tracing. + * + * Requires CPU timestamp counter! + */ +#define DEBUG_LATENCY 0 + +/** Optional compiler attributes fo ioctl() functions. + */ +#if 0 +#define ATTRIBUTES __attribute__ ((__noinline__)) +#else +#define ATTRIBUTES +#endif + +/*****************************************************************************/ + +/** Copies a string to an ioctl structure. + */ +static void ec_ioctl_strcpy( + char *target, /**< Target. */ + const char *source /**< Source. */ + ) +{ + if (source) { + strncpy(target, source, EC_IOCTL_STRING_SIZE); + target[EC_IOCTL_STRING_SIZE - 1] = 0; + } else { + target[0] = 0; + } +} + +/*****************************************************************************/ + +/** Get module information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_module( + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_module_t data; + + data.ioctl_version_magic = EC_IOCTL_VERSION_MAGIC; + data.master_count = ec_master_count(); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get master information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_master( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_master_t io; + unsigned int dev_idx, j; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + io.slave_count = master->slave_count; + io.config_count = ec_master_config_count(master); + io.domain_count = ec_master_domain_count(master); +#ifdef EC_EOE + io.eoe_handler_count = ec_master_eoe_handler_count(master); +#endif + io.phase = (uint8_t) master->phase; + io.active = (uint8_t) master->active; + io.scan_busy = master->scan_busy; + + up(&master->master_sem); + + if (down_interruptible(&master->device_sem)) { + return -EINTR; + } + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(master); dev_idx++) { + ec_device_t *device = &master->devices[dev_idx]; + + if (device->dev) { + memcpy(io.devices[dev_idx].address, device->dev->dev_addr, + ETH_ALEN); + } else { + memcpy(io.devices[dev_idx].address, master->macs[dev_idx], + ETH_ALEN); + } + io.devices[dev_idx].attached = device->dev ? 1 : 0; + io.devices[dev_idx].link_state = device->link_state ? 1 : 0; + io.devices[dev_idx].tx_count = device->tx_count; + io.devices[dev_idx].rx_count = device->rx_count; + io.devices[dev_idx].tx_bytes = device->tx_bytes; + io.devices[dev_idx].rx_bytes = device->rx_bytes; + io.devices[dev_idx].tx_errors = device->tx_errors; + for (j = 0; j < EC_RATE_COUNT; j++) { + io.devices[dev_idx].tx_frame_rates[j] = + device->tx_frame_rates[j]; + io.devices[dev_idx].rx_frame_rates[j] = + device->rx_frame_rates[j]; + io.devices[dev_idx].tx_byte_rates[j] = + device->tx_byte_rates[j]; + io.devices[dev_idx].rx_byte_rates[j] = + device->rx_byte_rates[j]; + } + } + io.num_devices = ec_master_num_devices(master); + + io.tx_count = master->device_stats.tx_count; + io.rx_count = master->device_stats.rx_count; + io.tx_bytes = master->device_stats.tx_bytes; + io.rx_bytes = master->device_stats.rx_bytes; + for (j = 0; j < EC_RATE_COUNT; j++) { + io.tx_frame_rates[j] = + master->device_stats.tx_frame_rates[j]; + io.rx_frame_rates[j] = + master->device_stats.rx_frame_rates[j]; + io.tx_byte_rates[j] = + master->device_stats.tx_byte_rates[j]; + io.rx_byte_rates[j] = + master->device_stats.rx_byte_rates[j]; + io.loss_rates[j] = + master->device_stats.loss_rates[j]; + } + + up(&master->device_sem); + + io.app_time = master->app_time; + io.dc_ref_time = master->dc_ref_time; + io.ref_clock = + master->dc_ref_clock ? master->dc_ref_clock->ring_position : 0xffff; + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Get slave information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_slave_t data; + const ec_slave_t *slave; + int i; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.position); + return -EINVAL; + } + + data.device_index = slave->device_index; + data.vendor_id = slave->sii.vendor_id; + data.product_code = slave->sii.product_code; + data.revision_number = slave->sii.revision_number; + data.serial_number = slave->sii.serial_number; + data.alias = slave->effective_alias; + data.boot_rx_mailbox_offset = slave->sii.boot_rx_mailbox_offset; + data.boot_rx_mailbox_size = slave->sii.boot_rx_mailbox_size; + data.boot_tx_mailbox_offset = slave->sii.boot_tx_mailbox_offset; + data.boot_tx_mailbox_size = slave->sii.boot_tx_mailbox_size; + data.std_rx_mailbox_offset = slave->sii.std_rx_mailbox_offset; + data.std_rx_mailbox_size = slave->sii.std_rx_mailbox_size; + data.std_tx_mailbox_offset = slave->sii.std_tx_mailbox_offset; + data.std_tx_mailbox_size = slave->sii.std_tx_mailbox_size; + data.mailbox_protocols = slave->sii.mailbox_protocols; + data.has_general_category = slave->sii.has_general; + data.coe_details = slave->sii.coe_details; + data.general_flags = slave->sii.general_flags; + data.current_on_ebus = slave->sii.current_on_ebus; + for (i = 0; i < EC_MAX_PORTS; i++) { + data.ports[i].desc = slave->ports[i].desc; + data.ports[i].link.link_up = slave->ports[i].link.link_up; + data.ports[i].link.loop_closed = slave->ports[i].link.loop_closed; + data.ports[i].link.signal_detected = + slave->ports[i].link.signal_detected; + data.ports[i].receive_time = slave->ports[i].receive_time; + if (slave->ports[i].next_slave) { + data.ports[i].next_slave = + slave->ports[i].next_slave->ring_position; + } else { + data.ports[i].next_slave = 0xffff; + } + data.ports[i].delay_to_next_dc = slave->ports[i].delay_to_next_dc; + } + data.fmmu_bit = slave->base_fmmu_bit_operation; + data.dc_supported = slave->base_dc_supported; + data.dc_range = slave->base_dc_range; + data.has_dc_system_time = slave->has_dc_system_time; + data.transmission_delay = slave->transmission_delay; + data.al_state = slave->current_state; + data.error_flag = slave->error_flag; + + data.sync_count = slave->sii.sync_count; + data.sdo_count = ec_slave_sdo_count(slave); + data.sii_nwords = slave->sii_nwords; + ec_ioctl_strcpy(data.group, slave->sii.group); + ec_ioctl_strcpy(data.image, slave->sii.image); + ec_ioctl_strcpy(data.order, slave->sii.order); + ec_ioctl_strcpy(data.name, slave->sii.name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave sync manager information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sync( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_slave_sync_t data; + const ec_slave_t *slave; + const ec_sync_t *sync; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (data.sync_index >= slave->sii.sync_count) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", + data.sync_index); + return -EINVAL; + } + + sync = &slave->sii.syncs[data.sync_index]; + + data.physical_start_address = sync->physical_start_address; + data.default_size = sync->default_length; + data.control_register = sync->control_register; + data.enable = sync->enable; + data.pdo_count = ec_pdo_list_count(&sync->pdos); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave sync manager PDO information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sync_pdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_slave_sync_pdo_t data; + const ec_slave_t *slave; + const ec_sync_t *sync; + const ec_pdo_t *pdo; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (data.sync_index >= slave->sii.sync_count) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", + data.sync_index); + return -EINVAL; + } + + sync = &slave->sii.syncs[data.sync_index]; + if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( + &sync->pdos, data.pdo_pos))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not contain a PDO with " + "position %u!\n", data.sync_index, data.pdo_pos); + return -EINVAL; + } + + data.index = pdo->index; + data.entry_count = ec_pdo_entry_count(pdo); + ec_ioctl_strcpy(data.name, pdo->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave sync manager PDO entry information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sync_pdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_slave_sync_pdo_entry_t data; + const ec_slave_t *slave; + const ec_sync_t *sync; + const ec_pdo_t *pdo; + const ec_pdo_entry_t *entry; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (data.sync_index >= slave->sii.sync_count) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", + data.sync_index); + return -EINVAL; + } + + sync = &slave->sii.syncs[data.sync_index]; + if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( + &sync->pdos, data.pdo_pos))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not contain a PDO with " + "position %u!\n", data.sync_index, data.pdo_pos); + return -EINVAL; + } + + if (!(entry = ec_pdo_find_entry_by_pos_const( + pdo, data.entry_pos))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "PDO 0x%04X does not contain an entry with " + "position %u!\n", data.pdo_pos, data.entry_pos); + return -EINVAL; + } + + data.index = entry->index; + data.subindex = entry->subindex; + data.bit_length = entry->bit_length; + ec_ioctl_strcpy(data.name, entry->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get domain information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_domain_t data; + const ec_domain_t *domain; + unsigned int dev_idx; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(domain = ec_master_find_domain_const(master, data.index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Domain %u does not exist!\n", data.index); + return -EINVAL; + } + + data.data_size = domain->data_size; + data.logical_base_address = domain->logical_base_address; + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + data.working_counter[dev_idx] = domain->working_counter[dev_idx]; + } + data.expected_working_counter = domain->expected_working_counter; + data.fmmu_count = ec_domain_fmmu_count(domain); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get domain FMMU information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_fmmu( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_domain_fmmu_t data; + const ec_domain_t *domain; + const ec_fmmu_config_t *fmmu; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Domain %u does not exist!\n", + data.domain_index); + return -EINVAL; + } + + if (!(fmmu = ec_domain_find_fmmu(domain, data.fmmu_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Domain %u has less than %u" + " fmmu configurations.\n", + data.domain_index, data.fmmu_index + 1); + return -EINVAL; + } + + data.slave_config_alias = fmmu->sc->alias; + data.slave_config_position = fmmu->sc->position; + data.sync_index = fmmu->sync_index; + data.dir = fmmu->dir; + data.logical_address = fmmu->logical_start_address; + data.data_size = fmmu->data_size; + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get domain data. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_data( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_domain_data_t data; + const ec_domain_t *domain; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Domain %u does not exist!\n", + data.domain_index); + return -EINVAL; + } + + if (domain->data_size != data.data_size) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Data size mismatch %u/%zu!\n", + data.data_size, domain->data_size); + return -EFAULT; + } + + if (copy_to_user((void __user *) data.target, domain->data, + domain->data_size)) { + up(&master->master_sem); + return -EFAULT; + } + + up(&master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** Set master debug level. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_master_debug( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + return ec_master_debug_level(master, (unsigned long) arg); +} + +/*****************************************************************************/ + +/** Issue a bus scan. + * + * \return Always zero (success). + */ +static ATTRIBUTES int ec_ioctl_master_rescan( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + master->fsm.rescan_required = 1; + return 0; +} + +/*****************************************************************************/ + +/** Set slave state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_state_t data; + ec_slave_t *slave; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + ec_slave_request_state(slave, data.al_state); + + up(&master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** Get slave SDO information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sdo_t data; + const ec_slave_t *slave; + const ec_sdo_t *sdo; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (!(sdo = ec_slave_get_sdo_by_pos_const( + slave, data.sdo_position))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "SDO %u does not exist!\n", data.sdo_position); + return -EINVAL; + } + + data.sdo_index = sdo->index; + data.max_subindex = sdo->max_subindex; + ec_ioctl_strcpy(data.name, sdo->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave SDO entry information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sdo_entry_t data; + const ec_slave_t *slave; + const ec_sdo_t *sdo; + const ec_sdo_entry_t *entry; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (data.sdo_spec <= 0) { + if (!(sdo = ec_slave_get_sdo_by_pos_const( + slave, -data.sdo_spec))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "SDO %u does not exist!\n", -data.sdo_spec); + return -EINVAL; + } + } else { + if (!(sdo = ec_slave_get_sdo_const( + slave, data.sdo_spec))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "SDO 0x%04X does not exist!\n", + data.sdo_spec); + return -EINVAL; + } + } + + if (!(entry = ec_sdo_get_entry_const( + sdo, data.sdo_entry_subindex))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "SDO entry 0x%04X:%02X does not exist!\n", + sdo->index, data.sdo_entry_subindex); + return -EINVAL; + } + + data.data_type = entry->data_type; + data.bit_length = entry->bit_length; + data.read_access[EC_SDO_ENTRY_ACCESS_PREOP] = + entry->read_access[EC_SDO_ENTRY_ACCESS_PREOP]; + data.read_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = + entry->read_access[EC_SDO_ENTRY_ACCESS_SAFEOP]; + data.read_access[EC_SDO_ENTRY_ACCESS_OP] = + entry->read_access[EC_SDO_ENTRY_ACCESS_OP]; + data.write_access[EC_SDO_ENTRY_ACCESS_PREOP] = + entry->write_access[EC_SDO_ENTRY_ACCESS_PREOP]; + data.write_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = + entry->write_access[EC_SDO_ENTRY_ACCESS_SAFEOP]; + data.write_access[EC_SDO_ENTRY_ACCESS_OP] = + entry->write_access[EC_SDO_ENTRY_ACCESS_OP]; + ec_ioctl_strcpy(data.description, entry->description); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Upload SDO. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sdo_upload( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sdo_upload_t data; + uint8_t *target; + int ret; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (!(target = kmalloc(data.target_size, GFP_KERNEL))) { + EC_MASTER_ERR(master, "Failed to allocate %zu bytes" + " for SDO upload.\n", data.target_size); + return -ENOMEM; + } + + ret = ecrt_master_sdo_upload(master, data.slave_position, + data.sdo_index, data.sdo_entry_subindex, target, + data.target_size, &data.data_size, &data.abort_code); + + if (!ret) { + if (copy_to_user((void __user *) data.target, + target, data.data_size)) { + kfree(target); + return -EFAULT; + } + } + + kfree(target); + + if (__copy_to_user((void __user *) arg, &data, sizeof(data))) { + return -EFAULT; + } + + return ret; +} + +/*****************************************************************************/ + +/** Download SDO. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sdo_download( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sdo_download_t data; + uint8_t *sdo_data; + int retval; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (!(sdo_data = kmalloc(data.data_size, GFP_KERNEL))) { + EC_MASTER_ERR(master, "Failed to allocate %zu bytes" + " for SDO download.\n", data.data_size); + return -ENOMEM; + } + + if (copy_from_user(sdo_data, (void __user *) data.data, data.data_size)) { + kfree(sdo_data); + return -EFAULT; + } + + if (data.complete_access) { + retval = ecrt_master_sdo_download_complete(master, data.slave_position, + data.sdo_index, sdo_data, data.data_size, &data.abort_code); + } else { + retval = ecrt_master_sdo_download(master, data.slave_position, + data.sdo_index, data.sdo_entry_subindex, sdo_data, + data.data_size, &data.abort_code); + } + + kfree(sdo_data); + + if (__copy_to_user((void __user *) arg, &data, sizeof(data))) { + retval = -EFAULT; + } + + return retval; +} + +/*****************************************************************************/ + +/** Read a slave's SII. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sii_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sii_t data; + const ec_slave_t *slave; + int retval; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (!data.nwords + || data.offset + data.nwords > slave->sii_nwords) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Invalid SII read offset/size %u/%u for slave SII" + " size %zu!\n", data.offset, data.nwords, slave->sii_nwords); + return -EINVAL; + } + + if (copy_to_user((void __user *) data.words, + slave->sii_words + data.offset, data.nwords * 2)) + retval = -EFAULT; + else + retval = 0; + + up(&master->master_sem); + return retval; +} + +/*****************************************************************************/ + +/** Write a slave's SII. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sii_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sii_t data; + ec_slave_t *slave; + unsigned int byte_size; + uint16_t *words; + ec_sii_write_request_t request; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (!data.nwords) { + return 0; + } + + byte_size = sizeof(uint16_t) * data.nwords; + if (!(words = kmalloc(byte_size, GFP_KERNEL))) { + EC_MASTER_ERR(master, "Failed to allocate %u bytes" + " for SII contents.\n", byte_size); + return -ENOMEM; + } + + if (copy_from_user(words, + (void __user *) data.words, byte_size)) { + kfree(words); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(words); + return -EINTR; + } + + if (!(slave = ec_master_find_slave( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + kfree(words); + return -EINVAL; + } + + // init SII write request + INIT_LIST_HEAD(&request.list); + request.slave = slave; + request.words = words; + request.offset = data.offset; + request.nwords = data.nwords; + request.state = EC_INT_REQUEST_QUEUED; + + // schedule SII write request. + list_add_tail(&request.list, &master->sii_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + kfree(words); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + kfree(words); + + return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; +} + +/*****************************************************************************/ + +/** Read a slave's registers. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_reg_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_reg_t io; + ec_slave_t *slave; + ec_reg_request_t request; + int ret; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + if (!io.size) { + return 0; + } + + // init register request + ret = ec_reg_request_init(&request, io.size); + if (ret) { + return ret; + } + + ecrt_reg_request_read(&request, io.address, io.size); + + if (down_interruptible(&master->master_sem)) { + ec_reg_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave( + master, 0, io.slave_position))) { + up(&master->master_sem); + ec_reg_request_clear(&request); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + io.slave_position); + return -EINVAL; + } + + // schedule request. + list_add_tail(&request.list, &slave->reg_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + ec_reg_request_clear(&request); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + if (request.state == EC_INT_REQUEST_SUCCESS) { + if (copy_to_user((void __user *) io.data, request.data, io.size)) { + return -EFAULT; + } + } + ec_reg_request_clear(&request); + + return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; +} + +/*****************************************************************************/ + +/** Write a slave's registers. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_reg_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_reg_t io; + ec_slave_t *slave; + ec_reg_request_t request; + int ret; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + if (!io.size) { + return 0; + } + + // init register request + ret = ec_reg_request_init(&request, io.size); + if (ret) { + return ret; + } + + if (copy_from_user(request.data, (void __user *) io.data, io.size)) { + ec_reg_request_clear(&request); + return -EFAULT; + } + + ecrt_reg_request_write(&request, io.address, io.size); + + if (down_interruptible(&master->master_sem)) { + ec_reg_request_clear(&request); + return -EINTR; + } + + if (io.emergency) { + request.ring_position = io.slave_position; + // schedule request. + list_add_tail(&request.list, &master->emerg_reg_requests); + } + else { + if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { + up(&master->master_sem); + ec_reg_request_clear(&request); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + io.slave_position); + return -EINVAL; + } + + // schedule request. + list_add_tail(&request.list, &slave->reg_requests); + } + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + ec_reg_request_clear(&request); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + ec_reg_request_clear(&request); + + return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; +} + +/*****************************************************************************/ + +/** Get slave configuration information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_t data; + const ec_slave_config_t *sc; + uint8_t i; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config_const( + master, data.config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + data.config_index); + return -EINVAL; + } + + data.alias = sc->alias; + data.position = sc->position; + data.vendor_id = sc->vendor_id; + data.product_code = sc->product_code; + for (i = 0; i < EC_MAX_SYNC_MANAGERS; i++) { + data.syncs[i].dir = sc->sync_configs[i].dir; + data.syncs[i].watchdog_mode = sc->sync_configs[i].watchdog_mode; + data.syncs[i].pdo_count = + ec_pdo_list_count(&sc->sync_configs[i].pdos); + } + data.watchdog_divider = sc->watchdog_divider; + data.watchdog_intervals = sc->watchdog_intervals; + data.sdo_count = ec_slave_config_sdo_count(sc); + data.idn_count = ec_slave_config_idn_count(sc); + data.slave_position = sc->slave ? sc->slave->ring_position : -1; + data.dc_assign_activate = sc->dc_assign_activate; + for (i = 0; i < EC_SYNC_SIGNAL_COUNT; i++) { + data.dc_sync[i] = sc->dc_sync[i]; + } + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave configuration PDO information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config_pdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_pdo_t data; + const ec_slave_config_t *sc; + const ec_pdo_t *pdo; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (data.sync_index >= EC_MAX_SYNC_MANAGERS) { + EC_MASTER_ERR(master, "Invalid sync manager index %u!\n", + data.sync_index); + return -EINVAL; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config_const( + master, data.config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + data.config_index); + return -EINVAL; + } + + if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( + &sc->sync_configs[data.sync_index].pdos, + data.pdo_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Invalid PDO position!\n"); + return -EINVAL; + } + + data.index = pdo->index; + data.entry_count = ec_pdo_entry_count(pdo); + ec_ioctl_strcpy(data.name, pdo->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave configuration PDO entry information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config_pdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_pdo_entry_t data; + const ec_slave_config_t *sc; + const ec_pdo_t *pdo; + const ec_pdo_entry_t *entry; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (data.sync_index >= EC_MAX_SYNC_MANAGERS) { + EC_MASTER_ERR(master, "Invalid sync manager index %u!\n", + data.sync_index); + return -EINVAL; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config_const( + master, data.config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + data.config_index); + return -EINVAL; + } + + if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( + &sc->sync_configs[data.sync_index].pdos, + data.pdo_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Invalid PDO position!\n"); + return -EINVAL; + } + + if (!(entry = ec_pdo_find_entry_by_pos_const( + pdo, data.entry_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Entry not found!\n"); + return -EINVAL; + } + + data.index = entry->index; + data.subindex = entry->subindex; + data.bit_length = entry->bit_length; + ec_ioctl_strcpy(data.name, entry->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave configuration SDO information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config_sdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_sdo_t *ioctl; + const ec_slave_config_t *sc; + const ec_sdo_request_t *req; + + if (!(ioctl = kmalloc(sizeof(*ioctl), GFP_KERNEL))) { + return -ENOMEM; + } + + if (copy_from_user(ioctl, (void __user *) arg, sizeof(*ioctl))) { + kfree(ioctl); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(ioctl); + return -EINTR; + } + + if (!(sc = ec_master_get_config_const( + master, ioctl->config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + ioctl->config_index); + kfree(ioctl); + return -EINVAL; + } + + if (!(req = ec_slave_config_get_sdo_by_pos_const( + sc, ioctl->sdo_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Invalid SDO position!\n"); + kfree(ioctl); + return -EINVAL; + } + + ioctl->index = req->index; + ioctl->subindex = req->subindex; + ioctl->size = req->data_size; + memcpy(ioctl->data, req->data, + min((u32) ioctl->size, (u32) EC_MAX_SDO_DATA_SIZE)); + ioctl->complete_access = req->complete_access; + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, ioctl, sizeof(*ioctl))) { + kfree(ioctl); + return -EFAULT; + } + + kfree(ioctl); + return 0; +} + +/*****************************************************************************/ + +/** Get slave configuration IDN information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config_idn( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_idn_t *ioctl; + const ec_slave_config_t *sc; + const ec_soe_request_t *req; + + if (!(ioctl = kmalloc(sizeof(*ioctl), GFP_KERNEL))) { + return -ENOMEM; + } + + if (copy_from_user(ioctl, (void __user *) arg, sizeof(*ioctl))) { + kfree(ioctl); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(ioctl); + return -EINTR; + } + + if (!(sc = ec_master_get_config_const( + master, ioctl->config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + ioctl->config_index); + kfree(ioctl); + return -EINVAL; + } + + if (!(req = ec_slave_config_get_idn_by_pos_const( + sc, ioctl->idn_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Invalid IDN position!\n"); + kfree(ioctl); + return -EINVAL; + } + + ioctl->drive_no = req->drive_no; + ioctl->idn = req->idn; + ioctl->state = req->state; + ioctl->size = req->data_size; + memcpy(ioctl->data, req->data, + min((u32) ioctl->size, (u32) EC_MAX_IDN_DATA_SIZE)); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, ioctl, sizeof(*ioctl))) { + kfree(ioctl); + return -EFAULT; + } + + kfree(ioctl); + return 0; +} + +/*****************************************************************************/ + +#ifdef EC_EOE + +/** Get EoE handler information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_eoe_handler( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_eoe_handler_t data; + const ec_eoe_t *eoe; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(eoe = ec_master_get_eoe_handler_const(master, data.eoe_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "EoE handler %u does not exist!\n", + data.eoe_index); + return -EINVAL; + } + + if (eoe->slave) { + data.slave_position = eoe->slave->ring_position; + } else { + data.slave_position = 0xffff; + } + snprintf(data.name, EC_DATAGRAM_NAME_SIZE, eoe->dev->name); + data.open = eoe->opened; + data.rx_bytes = eoe->stats.tx_bytes; + data.rx_rate = eoe->tx_rate; + data.tx_bytes = eoe->stats.rx_bytes; + data.tx_rate = eoe->tx_rate; + data.tx_queued_frames = eoe->tx_queued_frames; + data.tx_queue_size = eoe->tx_queue_size; + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +#endif + +/*****************************************************************************/ + +/** Request the master from userspace. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_request( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_master_t *m; + int ret = 0; + + m = ecrt_request_master_err(master->index); + if (IS_ERR(m)) { + ret = PTR_ERR(m); + } else { + ctx->requested = 1; + } + + return ret; +} + +/*****************************************************************************/ + +/** Create a domain. + * + * \return Domain index on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_create_domain( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_domain_t *domain; + + if (unlikely(!ctx->requested)) + return -EPERM; + + domain = ecrt_master_create_domain_err(master); + if (IS_ERR(domain)) + return PTR_ERR(domain); + + return domain->index; +} + +/*****************************************************************************/ + +/** Create a slave configuration. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_create_slave_config( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_t data; + ec_slave_config_t *sc, *entry; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + sc = ecrt_master_slave_config_err(master, data.alias, data.position, + data.vendor_id, data.product_code); + if (IS_ERR(sc)) + return PTR_ERR(sc); + + data.config_index = 0; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + list_for_each_entry(entry, &master->configs, list) { + if (entry == sc) + break; + data.config_index++; + } + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Select the DC reference clock. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_select_ref_clock( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + unsigned long config_index = (unsigned long) arg; + ec_slave_config_t *sc = NULL; + int ret = 0; + + if (unlikely(!ctx->requested)) { + ret = -EPERM; + goto out_return; + } + + if (down_interruptible(&master->master_sem)) { + ret = -EINTR; + goto out_return; + } + + if (config_index != 0xFFFFFFFF) { + if (!(sc = ec_master_get_config(master, config_index))) { + ret = -ENOENT; + goto out_up; + } + } + + ecrt_master_select_reference_clock(master, sc); + +out_up: + up(&master->master_sem); +out_return: + return ret; +} + +/*****************************************************************************/ + +/** Activates the master. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_activate( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_master_activate_t io; + ec_domain_t *domain; + off_t offset; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + io.process_data = NULL; + + /* Get the sum of the domains' process data sizes. */ + + ctx->process_data_size = 0; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + list_for_each_entry(domain, &master->domains, list) { + ctx->process_data_size += ecrt_domain_size(domain); + } + + up(&master->master_sem); + + if (ctx->process_data_size) { + ctx->process_data = vmalloc(ctx->process_data_size); + if (!ctx->process_data) { + ctx->process_data_size = 0; + return -ENOMEM; + } + + /* Set the memory as external process data memory for the + * domains. + */ + offset = 0; + list_for_each_entry(domain, &master->domains, list) { + ecrt_domain_external_memory(domain, + ctx->process_data + offset); + offset += ecrt_domain_size(domain); + } + +#ifdef EC_IOCTL_RTDM + /* RTDM uses a different approach for memory-mapping, which has to be + * initiated by the kernel. + */ + ret = ec_rtdm_mmap(ctx, &io.process_data); + if (ret < 0) { + EC_MASTER_ERR(master, "Failed to map process data" + " memory to user space (code %i).\n", ret); + return ret; + } +#endif + } + + io.process_data_size = ctx->process_data_size; + +#ifndef EC_IOCTL_RTDM + ecrt_master_callbacks(master, ec_master_internal_send_cb, + ec_master_internal_receive_cb, master); +#endif + + ret = ecrt_master_activate(master); + if (ret < 0) + return ret; + + if (copy_to_user((void __user *) arg, &io, + sizeof(ec_ioctl_master_activate_t))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Deactivates the master. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_deactivate( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) + return -EPERM; + + ecrt_master_deactivate(master); + return 0; +} + +/*****************************************************************************/ + +/** Set max. number of databytes in a cycle + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_set_send_interval( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + size_t send_interval; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&send_interval, (void __user *) arg, + sizeof(send_interval))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + ec_master_set_send_interval(master, send_interval); + + up(&master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** Send frames. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_send( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_send(master); + return 0; +} + +/*****************************************************************************/ + +/** Receive frames. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_receive( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_receive(master); + return 0; +} + +/*****************************************************************************/ + +/** Get the master state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_master_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_master_state_t data; + + ecrt_master_state(master, &data); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get the link state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_master_link_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_link_state_t ioctl; + ec_master_link_state_t state; + int ret; + + if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { + return -EFAULT; + } + + ret = ecrt_master_link_state(master, ioctl.dev_idx, &state); + if (ret < 0) { + return ret; + } + + if (copy_to_user((void __user *) ioctl.state, &state, sizeof(state))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Set the master DC application time. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_app_time( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + uint64_t time; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&time, (void __user *) arg, sizeof(time))) { + return -EFAULT; + } + + ecrt_master_application_time(master, time); + return 0; +} + +/*****************************************************************************/ + +/** Sync the reference clock. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_ref( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_sync_reference_clock(master); + return 0; +} + +/*****************************************************************************/ + +/** Sync the reference clock. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_ref_to( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + uint64_t time; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&time, (void __user *) arg, sizeof(time))) { + return -EFAULT; + } + + ecrt_master_sync_reference_clock_to(master, time); + return 0; +} + +/*****************************************************************************/ + +/** Sync the slave clocks. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_slaves( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_sync_slave_clocks(master); + return 0; +} + +/*****************************************************************************/ + +/** Get the system time of the reference clock. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_ref_clock_time( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + uint32_t time; + int ret; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ret = ecrt_master_reference_clock_time(master, &time); + if (ret) { + return ret; + } + + if (copy_to_user((void __user *) arg, &time, sizeof(time))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Queue the sync monitoring datagram. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_mon_queue( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_sync_monitor_queue(master); + return 0; +} + +/*****************************************************************************/ + +/** Processes the sync monitoring datagram. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_mon_process( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + uint32_t time_diff; + + if (unlikely(!ctx->requested)) + return -EPERM; + + time_diff = ecrt_master_sync_monitor_process(master); + + if (copy_to_user((void __user *) arg, &time_diff, sizeof(time_diff))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Reset configuration. + * + * \return Always zero (success). + */ +static ATTRIBUTES int ec_ioctl_reset( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + down(&master->master_sem); + ecrt_master_reset(master); + up(&master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** Configure a sync manager. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_sync( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_t data; + ec_slave_config_t *sc; + unsigned int i; + int ret = 0; + + if (unlikely(!ctx->requested)) { + ret = -EPERM; + goto out_return; + } + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + ret = -EFAULT; + goto out_return; + } + + if (down_interruptible(&master->master_sem)) { + ret = -EINTR; + goto out_return; + } + + if (!(sc = ec_master_get_config(master, data.config_index))) { + ret = -ENOENT; + goto out_up; + } + + for (i = 0; i < EC_MAX_SYNC_MANAGERS; i++) { + if (data.syncs[i].config_this) { + ret = ecrt_slave_config_sync_manager(sc, i, data.syncs[i].dir, + data.syncs[i].watchdog_mode); + if (ret) { + goto out_up; + } + } + } + +out_up: + up(&master->master_sem); +out_return: + return ret; +} + +/*****************************************************************************/ + +/** Configure a slave's watchdogs. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_watchdog( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_t data; + ec_slave_config_t *sc; + int ret = 0; + + if (unlikely(!ctx->requested)) { + ret = -EPERM; + goto out_return; + } + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + ret = -EFAULT; + goto out_return; + } + + if (down_interruptible(&master->master_sem)) { + ret = -EINTR; + goto out_return; + } + + if (!(sc = ec_master_get_config(master, data.config_index))) { + ret = -ENOENT; + goto out_up; + } + + ecrt_slave_config_watchdog(sc, + data.watchdog_divider, data.watchdog_intervals); + +out_up: + up(&master->master_sem); +out_return: + return ret; +} + +/*****************************************************************************/ + +/** Add a PDO to the assignment. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_add_pdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_pdo_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + return ecrt_slave_config_pdo_assign_add(sc, data.sync_index, data.index); +} + +/*****************************************************************************/ + +/** Clears the PDO assignment. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_clear_pdos( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_pdo_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + ecrt_slave_config_pdo_assign_clear(sc, data.sync_index); + return 0; +} + +/*****************************************************************************/ + +/** Add an entry to a PDO's mapping. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_add_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_add_pdo_entry_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + return ecrt_slave_config_pdo_mapping_add(sc, data.pdo_index, + data.entry_index, data.entry_subindex, data.entry_bit_length); +} + +/*****************************************************************************/ + +/** Clears the mapping of a PDO. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_clear_entries( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_pdo_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + ecrt_slave_config_pdo_mapping_clear(sc, data.index); + return 0; +} + +/*****************************************************************************/ + +/** Registers a PDO entry. + * + * \return Process data offset on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_reg_pdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_pdo_entry_t data; + ec_slave_config_t *sc; + ec_domain_t *domain; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + if (!(domain = ec_master_find_domain(master, data.domain_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc or domain could be invalidated */ + + ret = ecrt_slave_config_reg_pdo_entry(sc, data.entry_index, + data.entry_subindex, domain, &data.bit_position); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return ret; +} + +/*****************************************************************************/ + +/** Registers a PDO entry by its position. + * + * \return Process data offset on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_reg_pdo_pos( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_pdo_pos_t io; + ec_slave_config_t *sc; + ec_domain_t *domain; + int ret; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + if (!(sc = ec_master_get_config(master, io.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + if (!(domain = ec_master_find_domain(master, io.domain_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc or domain could be invalidated */ + + ret = ecrt_slave_config_reg_pdo_entry_pos(sc, io.sync_index, + io.pdo_pos, io.entry_pos, domain, &io.bit_position); + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) + return -EFAULT; + + return ret; +} + +/*****************************************************************************/ + +/** Sets the DC AssignActivate word and the sync signal times. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_dc( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + ecrt_slave_config_dc(sc, data.dc_assign_activate, + data.dc_sync[0].cycle_time, + data.dc_sync[0].shift_time, + data.dc_sync[1].cycle_time, + data.dc_sync[1].shift_time); + + up(&master->master_sem); + + return 0; +} + +/*****************************************************************************/ + +/** Configures an SDO. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_sdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_sdo_t data; + ec_slave_config_t *sc; + uint8_t *sdo_data = NULL; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (!data.size) + return -EINVAL; + + if (!(sdo_data = kmalloc(data.size, GFP_KERNEL))) { + return -ENOMEM; + } + + if (copy_from_user(sdo_data, (void __user *) data.data, data.size)) { + kfree(sdo_data); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(sdo_data); + return -EINTR; + } + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + kfree(sdo_data); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + if (data.complete_access) { + ret = ecrt_slave_config_complete_sdo(sc, + data.index, sdo_data, data.size); + } else { + ret = ecrt_slave_config_sdo(sc, data.index, data.subindex, sdo_data, + data.size); + } + kfree(sdo_data); + return ret; +} + +/*****************************************************************************/ + +/** Set the emergency ring buffer size. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_emerg_size( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_emerg_t io; + ec_slave_config_t *sc; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + if (!(sc = ec_master_get_config(master, io.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + ret = ecrt_slave_config_emerg_size(sc, io.size); + + up(&master->master_sem); + + return ret; +} + +/*****************************************************************************/ + +/** Get an emergency message from the ring. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_emerg_pop( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_emerg_t io; + ec_slave_config_t *sc; + u8 msg[EC_COE_EMERGENCY_MSG_SIZE]; + int ret; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because configuration will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + ret = ecrt_slave_config_emerg_pop(sc, msg); + if (ret < 0) { + return ret; + } + + if (copy_to_user((void __user *) io.target, msg, sizeof(msg))) { + return -EFAULT; + } + + return ret; +} + +/*****************************************************************************/ + +/** Clear the emergency ring. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_emerg_clear( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_emerg_t io; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because configuration will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + return ecrt_slave_config_emerg_clear(sc); +} + +/*****************************************************************************/ + +/** Get the number of emergency overruns. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_emerg_overruns( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_emerg_t io; + ec_slave_config_t *sc; + int ret; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because configuration will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + ret = ecrt_slave_config_emerg_overruns(sc); + if (ret < 0) { + return ret; + } + + io.overruns = ret; + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Create an SDO request. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_create_sdo_request( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + data.request_index = 0; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + sc = ec_master_get_config(master, data.config_index); + if (!sc) { + up(&master->master_sem); + return -ENOENT; + } + + list_for_each_entry(req, &sc->sdo_requests, list) { + data.request_index++; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + req = ecrt_slave_config_create_sdo_request_err(sc, data.sdo_index, + data.sdo_subindex, data.size); + if (IS_ERR(req)) + return PTR_ERR(req); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Create a register request. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_create_reg_request( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + io.request_index = 0; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + sc = ec_master_get_config(master, io.config_index); + if (!sc) { + up(&master->master_sem); + return -ENOENT; + } + + list_for_each_entry(reg, &sc->reg_requests, list) { + io.request_index++; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + reg = ecrt_slave_config_create_reg_request_err(sc, io.mem_size); + if (IS_ERR(reg)) { + return PTR_ERR(reg); + } + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Create a VoE handler. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_create_voe_handler( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + data.voe_index = 0; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + sc = ec_master_get_config(master, data.config_index); + if (!sc) { + up(&master->master_sem); + return -ENOENT; + } + + list_for_each_entry(voe, &sc->voe_handlers, list) { + data.voe_index++; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + voe = ecrt_slave_config_create_voe_handler_err(sc, data.size); + if (IS_ERR(voe)) + return PTR_ERR(voe); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get the slave configuration's state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_state_t data; + const ec_slave_config_t *sc; + ec_slave_config_state_t state; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because sc will not be deleted in the + * meantime. */ + + if (!(sc = ec_master_get_config_const(master, data.config_index))) { + return -ENOENT; + } + + ecrt_slave_config_state(sc, &state); + + if (copy_to_user((void __user *) data.state, &state, sizeof(state))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Configures an IDN. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_idn( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_idn_t ioctl; + ec_slave_config_t *sc; + uint8_t *data = NULL; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) + return -EFAULT; + + if (!ioctl.size) + return -EINVAL; + + if (!(data = kmalloc(ioctl.size, GFP_KERNEL))) { + return -ENOMEM; + } + + if (copy_from_user(data, (void __user *) ioctl.data, ioctl.size)) { + kfree(data); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(data); + return -EINTR; + } + + if (!(sc = ec_master_get_config(master, ioctl.config_index))) { + up(&master->master_sem); + kfree(data); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + ret = ecrt_slave_config_idn( + sc, ioctl.drive_no, ioctl.idn, ioctl.al_state, data, ioctl.size); + kfree(data); + return ret; +} + +/*****************************************************************************/ + +/** Gets the domain's data size. + * + * \return Domain size, or a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_size( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + const ec_domain_t *domain; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + list_for_each_entry(domain, &master->domains, list) { + if (domain->index == (unsigned long) arg) { + size_t size = ecrt_domain_size(domain); + up(&master->master_sem); + return size; + } + } + + up(&master->master_sem); + return -ENOENT; +} + +/*****************************************************************************/ + +/** Gets the domain's offset in the total process data. + * + * \return Domain offset, or a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_offset( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + int offset = 0; + const ec_domain_t *domain; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + list_for_each_entry(domain, &master->domains, list) { + if (domain->index == (unsigned long) arg) { + up(&master->master_sem); + return offset; + } + offset += ecrt_domain_size(domain); + } + + up(&master->master_sem); + return -ENOENT; +} + +/*****************************************************************************/ + +/** Process the domain. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_process( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_domain_t *domain; + + if (unlikely(!ctx->requested)) + return -EPERM; + + /* no locking of master_sem needed, because domain will not be deleted in + * the meantime. */ + + if (!(domain = ec_master_find_domain(master, (unsigned long) arg))) { + return -ENOENT; + } + + ecrt_domain_process(domain); + return 0; +} + +/*****************************************************************************/ + +/** Queue the domain. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_queue( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_domain_t *domain; + + if (unlikely(!ctx->requested)) + return -EPERM; + + /* no locking of master_sem needed, because domain will not be deleted in + * the meantime. */ + + if (!(domain = ec_master_find_domain(master, (unsigned long) arg))) { + return -ENOENT; + } + + ecrt_domain_queue(domain); + return 0; +} + +/*****************************************************************************/ + +/** Get the domain state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_domain_state_t data; + const ec_domain_t *domain; + ec_domain_state_t state; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because domain will not be deleted in + * the meantime. */ + + if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { + return -ENOENT; + } + + ecrt_domain_state(domain, &state); + + if (copy_to_user((void __user *) data.state, &state, sizeof(state))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Sets an SDO request's SDO index and subindex. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_index( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + ecrt_sdo_request_index(req, data.sdo_index, data.sdo_subindex); + return 0; +} + +/*****************************************************************************/ + +/** Sets an SDO request's timeout. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_timeout( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + ecrt_sdo_request_timeout(req, data.timeout); + return 0; +} + +/*****************************************************************************/ + +/** Gets an SDO request's state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + data.state = ecrt_sdo_request_state(req); + if (data.state == EC_REQUEST_SUCCESS && req->dir == EC_DIR_INPUT) + data.size = ecrt_sdo_request_data_size(req); + else + data.size = 0; + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Starts an SDO read operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + ecrt_sdo_request_read(req); + return 0; +} + +/*****************************************************************************/ + +/** Starts an SDO write operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (!data.size) { + EC_MASTER_ERR(master, "SDO download: Data size may not be zero!\n"); + return -EINVAL; + } + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + ret = ec_sdo_request_alloc(req, data.size); + if (ret) + return ret; + + if (copy_from_user(req->data, (void __user *) data.data, data.size)) + return -EFAULT; + + req->data_size = data.size; + ecrt_sdo_request_write(req); + return 0; +} + +/*****************************************************************************/ + +/** Read SDO data. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_data( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + if (copy_to_user((void __user *) data.data, ecrt_sdo_request_data(req), + ecrt_sdo_request_data_size(req))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Read register data. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_reg_request_data( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + if (io.mem_size <= 0) { + return 0; + } + + /* no locking of master_sem needed, because neither sc nor reg will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { + return -ENOENT; + } + + if (copy_to_user((void __user *) io.data, ecrt_reg_request_data(reg), + min(reg->mem_size, io.mem_size))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Gets an register request's state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_reg_request_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because neither sc nor reg will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { + return -ENOENT; + } + + io.state = ecrt_reg_request_state(reg); + io.new_data = io.state == EC_REQUEST_SUCCESS && reg->dir == EC_DIR_INPUT; + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Starts an register write operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_reg_request_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because neither sc nor reg will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { + return -ENOENT; + } + + if (io.transfer_size > reg->mem_size) { + return -EOVERFLOW; + } + + if (copy_from_user(reg->data, (void __user *) io.data, + io.transfer_size)) { + return -EFAULT; + } + + ecrt_reg_request_write(reg, io.address, io.transfer_size); + return 0; +} + +/*****************************************************************************/ + +/** Starts an register read operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_reg_request_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because neither sc nor reg will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { + return -ENOENT; + } + + if (io.transfer_size > reg->mem_size) { + return -EOVERFLOW; + } + + ecrt_reg_request_read(reg, io.address, io.transfer_size); + return 0; +} + +/*****************************************************************************/ + +/** Sets the VoE send header. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_send_header( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + uint32_t vendor_id; + uint16_t vendor_type; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (get_user(vendor_id, data.vendor_id)) + return -EFAULT; + + if (get_user(vendor_type, data.vendor_type)) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + ecrt_voe_handler_send_header(voe, vendor_id, vendor_type); + return 0; +} + +/*****************************************************************************/ + +/** Gets the received VoE header. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_rec_header( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + uint32_t vendor_id; + uint16_t vendor_type; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + ecrt_voe_handler_received_header(voe, &vendor_id, &vendor_type); + + if (likely(data.vendor_id)) + if (put_user(vendor_id, data.vendor_id)) + return -EFAULT; + + if (likely(data.vendor_type)) + if (put_user(vendor_type, data.vendor_type)) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Starts a VoE read operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + ecrt_voe_handler_read(voe); + return 0; +} + +/*****************************************************************************/ + +/** Starts a VoE read operation without sending a sync message first. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_read_nosync( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + ecrt_voe_handler_read_nosync(voe); + return 0; +} + +/*****************************************************************************/ + +/** Starts a VoE write operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + if (data.size) { + if (data.size > ec_voe_handler_mem_size(voe)) + return -EOVERFLOW; + + if (copy_from_user(ecrt_voe_handler_data(voe), + (void __user *) data.data, data.size)) + return -EFAULT; + } + + ecrt_voe_handler_write(voe, data.size); + return 0; +} + +/*****************************************************************************/ + +/** Executes the VoE state machine. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_exec( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + data.state = ecrt_voe_handler_execute(voe); + if (data.state == EC_REQUEST_SUCCESS && voe->dir == EC_DIR_INPUT) + data.size = ecrt_voe_handler_data_size(voe); + else + data.size = 0; + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Reads the received VoE data. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_data( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + if (copy_to_user((void __user *) data.data, ecrt_voe_handler_data(voe), + ecrt_voe_handler_data_size(voe))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Read a file from a slave via FoE. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_foe_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_foe_t io; + ec_foe_request_t request; + ec_slave_t *slave; + int ret; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + ec_foe_request_init(&request, io.file_name); + ret = ec_foe_request_alloc(&request, 10000); // FIXME + if (ret) { + ec_foe_request_clear(&request); + return ret; + } + + ec_foe_request_read(&request); + + if (down_interruptible(&master->master_sem)) { + ec_foe_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { + up(&master->master_sem); + ec_foe_request_clear(&request); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + io.slave_position); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling FoE read request.\n"); + + // schedule request. + list_add_tail(&request.list, &slave->foe_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + list_del(&request.list); + up(&master->master_sem); + ec_foe_request_clear(&request); + return -EINTR; + } + // request already processing: interrupt not possible. + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + io.result = request.result; + io.error_code = request.error_code; + + if (request.state != EC_INT_REQUEST_SUCCESS) { + io.data_size = 0; + ret = -EIO; + } else { + if (request.data_size > io.buffer_size) { + EC_SLAVE_ERR(slave, "%s(): Buffer too small.\n", __func__); + ec_foe_request_clear(&request); + return -EOVERFLOW; + } + io.data_size = request.data_size; + if (copy_to_user((void __user *) io.buffer, + request.buffer, io.data_size)) { + ec_foe_request_clear(&request); + return -EFAULT; + } + ret = 0; + } + + if (__copy_to_user((void __user *) arg, &io, sizeof(io))) { + ret = -EFAULT; + } + + ec_foe_request_clear(&request); + return ret; +} + +/*****************************************************************************/ + +/** Write a file to a slave via FoE + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_foe_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_foe_t io; + ec_foe_request_t request; + ec_slave_t *slave; + int ret; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + ec_foe_request_init(&request, io.file_name); + + ret = ec_foe_request_alloc(&request, io.buffer_size); + if (ret) { + ec_foe_request_clear(&request); + return ret; + } + + if (copy_from_user(request.buffer, + (void __user *) io.buffer, io.buffer_size)) { + ec_foe_request_clear(&request); + return -EFAULT; + } + + request.data_size = io.buffer_size; + ec_foe_request_write(&request); + + if (down_interruptible(&master->master_sem)) { + ec_foe_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + io.slave_position); + ec_foe_request_clear(&request); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling FoE write request.\n"); + + // schedule FoE write request. + list_add_tail(&request.list, &slave->foe_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + ec_foe_request_clear(&request); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + io.result = request.result; + io.error_code = request.error_code; + + ret = request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; + + if (__copy_to_user((void __user *) arg, &io, sizeof(io))) { + ret = -EFAULT; + } + + ec_foe_request_clear(&request); + return ret; +} + +/*****************************************************************************/ + +/** Read an SoE IDN. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_soe_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_soe_read_t ioctl; + u8 *data; + int retval; + + if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { + return -EFAULT; + } + + data = kmalloc(ioctl.mem_size, GFP_KERNEL); + if (!data) { + EC_MASTER_ERR(master, "Failed to allocate %zu bytes of IDN data.\n", + ioctl.mem_size); + return -ENOMEM; + } + + retval = ecrt_master_read_idn(master, ioctl.slave_position, + ioctl.drive_no, ioctl.idn, data, ioctl.mem_size, &ioctl.data_size, + &ioctl.error_code); + if (retval) { + kfree(data); + return retval; + } + + if (copy_to_user((void __user *) ioctl.data, + data, ioctl.data_size)) { + kfree(data); + return -EFAULT; + } + kfree(data); + + if (__copy_to_user((void __user *) arg, &ioctl, sizeof(ioctl))) { + retval = -EFAULT; + } + + EC_MASTER_DBG(master, 1, "Finished SoE read request.\n"); + return retval; +} + +/*****************************************************************************/ + +/** Write an IDN to a slave via SoE. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_soe_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_soe_write_t ioctl; + u8 *data; + int retval; + + if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { + return -EFAULT; + } + + data = kmalloc(ioctl.data_size, GFP_KERNEL); + if (!data) { + EC_MASTER_ERR(master, "Failed to allocate %zu bytes of IDN data.\n", + ioctl.data_size); + return -ENOMEM; + } + if (copy_from_user(data, (void __user *) ioctl.data, ioctl.data_size)) { + kfree(data); + return -EFAULT; + } + + retval = ecrt_master_write_idn(master, ioctl.slave_position, + ioctl.drive_no, ioctl.idn, data, ioctl.data_size, + &ioctl.error_code); + kfree(data); + if (retval) { + return retval; + } + + if (__copy_to_user((void __user *) arg, &ioctl, sizeof(ioctl))) { + retval = -EFAULT; + } + + EC_MASTER_DBG(master, 1, "Finished SoE write request.\n"); + return retval; +} + +/*****************************************************************************/ + +/** ioctl() function to use. + */ +#ifdef EC_IOCTL_RTDM +#define EC_IOCTL ec_ioctl_rtdm +#else +#define EC_IOCTL ec_ioctl +#endif + +/** Called when an ioctl() command is issued. + * + * \return ioctl() return code. + */ +long EC_IOCTL( + ec_master_t *master, /**< EtherCAT master. */ + ec_ioctl_context_t *ctx, /**< Device context. */ + unsigned int cmd, /**< ioctl() command identifier. */ + void *arg /**< ioctl() argument. */ + ) +{ +#if DEBUG_LATENCY + cycles_t a = get_cycles(), b; + unsigned int t; +#endif + int ret; + + switch (cmd) { + case EC_IOCTL_MODULE: + ret = ec_ioctl_module(arg); + break; + case EC_IOCTL_MASTER: + ret = ec_ioctl_master(master, arg); + break; + case EC_IOCTL_SLAVE: + ret = ec_ioctl_slave(master, arg); + break; + case EC_IOCTL_SLAVE_SYNC: + ret = ec_ioctl_slave_sync(master, arg); + break; + case EC_IOCTL_SLAVE_SYNC_PDO: + ret = ec_ioctl_slave_sync_pdo(master, arg); + break; + case EC_IOCTL_SLAVE_SYNC_PDO_ENTRY: + ret = ec_ioctl_slave_sync_pdo_entry(master, arg); + break; + case EC_IOCTL_DOMAIN: + ret = ec_ioctl_domain(master, arg); + break; + case EC_IOCTL_DOMAIN_FMMU: + ret = ec_ioctl_domain_fmmu(master, arg); + break; + case EC_IOCTL_DOMAIN_DATA: + ret = ec_ioctl_domain_data(master, arg); + break; + case EC_IOCTL_MASTER_DEBUG: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_master_debug(master, arg); + break; + case EC_IOCTL_MASTER_RESCAN: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_master_rescan(master, arg); + break; + case EC_IOCTL_SLAVE_STATE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_state(master, arg); + break; + case EC_IOCTL_SLAVE_SDO: + ret = ec_ioctl_slave_sdo(master, arg); + break; + case EC_IOCTL_SLAVE_SDO_ENTRY: + ret = ec_ioctl_slave_sdo_entry(master, arg); + break; + case EC_IOCTL_SLAVE_SDO_UPLOAD: + ret = ec_ioctl_slave_sdo_upload(master, arg); + break; + case EC_IOCTL_SLAVE_SDO_DOWNLOAD: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_sdo_download(master, arg); + break; + case EC_IOCTL_SLAVE_SII_READ: + ret = ec_ioctl_slave_sii_read(master, arg); + break; + case EC_IOCTL_SLAVE_SII_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_sii_write(master, arg); + break; + case EC_IOCTL_SLAVE_REG_READ: + ret = ec_ioctl_slave_reg_read(master, arg); + break; + case EC_IOCTL_SLAVE_REG_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_reg_write(master, arg); + break; + case EC_IOCTL_SLAVE_FOE_READ: + ret = ec_ioctl_slave_foe_read(master, arg); + break; + case EC_IOCTL_SLAVE_FOE_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_foe_write(master, arg); + break; + case EC_IOCTL_SLAVE_SOE_READ: + ret = ec_ioctl_slave_soe_read(master, arg); + break; + case EC_IOCTL_SLAVE_SOE_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_soe_write(master, arg); + break; + case EC_IOCTL_CONFIG: + ret = ec_ioctl_config(master, arg); + break; + case EC_IOCTL_CONFIG_PDO: + ret = ec_ioctl_config_pdo(master, arg); + break; + case EC_IOCTL_CONFIG_PDO_ENTRY: + ret = ec_ioctl_config_pdo_entry(master, arg); + break; + case EC_IOCTL_CONFIG_SDO: + ret = ec_ioctl_config_sdo(master, arg); + break; + case EC_IOCTL_CONFIG_IDN: + ret = ec_ioctl_config_idn(master, arg); + break; +#ifdef EC_EOE + case EC_IOCTL_EOE_HANDLER: + ret = ec_ioctl_eoe_handler(master, arg); + break; +#endif + case EC_IOCTL_REQUEST: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_request(master, arg, ctx); + break; + case EC_IOCTL_CREATE_DOMAIN: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_create_domain(master, arg, ctx); + break; + case EC_IOCTL_CREATE_SLAVE_CONFIG: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_create_slave_config(master, arg, ctx); + break; + case EC_IOCTL_SELECT_REF_CLOCK: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_select_ref_clock(master, arg, ctx); + break; + case EC_IOCTL_ACTIVATE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_activate(master, arg, ctx); + break; + case EC_IOCTL_DEACTIVATE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_deactivate(master, arg, ctx); + break; + case EC_IOCTL_SEND: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_send(master, arg, ctx); + break; + case EC_IOCTL_RECEIVE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_receive(master, arg, ctx); + break; + case EC_IOCTL_MASTER_STATE: + ret = ec_ioctl_master_state(master, arg, ctx); + break; + case EC_IOCTL_MASTER_LINK_STATE: + ret = ec_ioctl_master_link_state(master, arg, ctx); + break; + case EC_IOCTL_APP_TIME: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_app_time(master, arg, ctx); + break; + case EC_IOCTL_SYNC_REF: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_ref(master, arg, ctx); + break; + case EC_IOCTL_SYNC_REF_TO: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_ref_to(master, arg, ctx); + break; + case EC_IOCTL_SYNC_SLAVES: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_slaves(master, arg, ctx); + break; + case EC_IOCTL_REF_CLOCK_TIME: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_ref_clock_time(master, arg, ctx); + break; + case EC_IOCTL_SYNC_MON_QUEUE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_mon_queue(master, arg, ctx); + break; + case EC_IOCTL_SYNC_MON_PROCESS: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_mon_process(master, arg, ctx); + break; + case EC_IOCTL_RESET: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_reset(master, arg, ctx); + break; + case EC_IOCTL_SC_SYNC: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_sync(master, arg, ctx); + break; + case EC_IOCTL_SC_WATCHDOG: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_watchdog(master, arg, ctx); + break; + case EC_IOCTL_SC_ADD_PDO: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_add_pdo(master, arg, ctx); + break; + case EC_IOCTL_SC_CLEAR_PDOS: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_clear_pdos(master, arg, ctx); + break; + case EC_IOCTL_SC_ADD_ENTRY: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_add_entry(master, arg, ctx); + break; + case EC_IOCTL_SC_CLEAR_ENTRIES: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_clear_entries(master, arg, ctx); + break; + case EC_IOCTL_SC_REG_PDO_ENTRY: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_reg_pdo_entry(master, arg, ctx); + break; + case EC_IOCTL_SC_REG_PDO_POS: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_reg_pdo_pos(master, arg, ctx); + break; + case EC_IOCTL_SC_DC: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_dc(master, arg, ctx); + break; + case EC_IOCTL_SC_SDO: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_sdo(master, arg, ctx); + break; + case EC_IOCTL_SC_EMERG_SIZE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_emerg_size(master, arg, ctx); + break; + case EC_IOCTL_SC_EMERG_POP: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_emerg_pop(master, arg, ctx); + break; + case EC_IOCTL_SC_EMERG_CLEAR: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_emerg_clear(master, arg, ctx); + break; + case EC_IOCTL_SC_EMERG_OVERRUNS: + ret = ec_ioctl_sc_emerg_overruns(master, arg, ctx); + break; + case EC_IOCTL_SC_SDO_REQUEST: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_create_sdo_request(master, arg, ctx); + break; + case EC_IOCTL_SC_REG_REQUEST: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_create_reg_request(master, arg, ctx); + break; + case EC_IOCTL_SC_VOE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_create_voe_handler(master, arg, ctx); + break; + case EC_IOCTL_SC_STATE: + ret = ec_ioctl_sc_state(master, arg, ctx); + break; + case EC_IOCTL_SC_IDN: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_idn(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_SIZE: + ret = ec_ioctl_domain_size(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_OFFSET: + ret = ec_ioctl_domain_offset(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_PROCESS: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_domain_process(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_QUEUE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_domain_queue(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_STATE: + ret = ec_ioctl_domain_state(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_INDEX: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sdo_request_index(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_TIMEOUT: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sdo_request_timeout(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_STATE: + ret = ec_ioctl_sdo_request_state(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_READ: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sdo_request_read(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sdo_request_write(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_DATA: + ret = ec_ioctl_sdo_request_data(master, arg, ctx); + break; + case EC_IOCTL_REG_REQUEST_DATA: + ret = ec_ioctl_reg_request_data(master, arg, ctx); + break; + case EC_IOCTL_REG_REQUEST_STATE: + ret = ec_ioctl_reg_request_state(master, arg, ctx); + break; + case EC_IOCTL_REG_REQUEST_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_reg_request_write(master, arg, ctx); + break; + case EC_IOCTL_REG_REQUEST_READ: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_reg_request_read(master, arg, ctx); + break; + case EC_IOCTL_VOE_SEND_HEADER: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_send_header(master, arg, ctx); + break; + case EC_IOCTL_VOE_REC_HEADER: + ret = ec_ioctl_voe_rec_header(master, arg, ctx); + break; + case EC_IOCTL_VOE_READ: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_read(master, arg, ctx); + break; + case EC_IOCTL_VOE_READ_NOSYNC: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_read_nosync(master, arg, ctx); + break; + case EC_IOCTL_VOE_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_write(master, arg, ctx); + break; + case EC_IOCTL_VOE_EXEC: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_exec(master, arg, ctx); + break; + case EC_IOCTL_VOE_DATA: + ret = ec_ioctl_voe_data(master, arg, ctx); + break; + case EC_IOCTL_SET_SEND_INTERVAL: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_set_send_interval(master, arg, ctx); + break; + default: + ret = -ENOTTY; + break; + } + +#if DEBUG_LATENCY + b = get_cycles(); + t = (unsigned int) ((b - a) * 1000LL) / cpu_khz; + if (t > 50) { + EC_MASTER_WARN(master, "ioctl(0x%02x) took %u us.\n", + _IOC_NR(cmd), t); + } +#endif + + return ret; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/ioctl.h b/net/ethercat/master/ioctl.h new file mode 100644 index 000000000000..a1d597b72bdf --- /dev/null +++ b/net/ethercat/master/ioctl.h @@ -0,0 +1,782 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT master character device IOCTL commands. +*/ + +/*****************************************************************************/ + +#ifndef __EC_IOCTL_H__ +#define __EC_IOCTL_H__ + +#include <linux/ioctl.h> + +#include "globals.h" + +/*****************************************************************************/ + +/** \cond */ + +#define EC_IOCTL_TYPE 0xa4 + +#define EC_IO(nr) _IO(EC_IOCTL_TYPE, nr) +#define EC_IOR(nr, type) _IOR(EC_IOCTL_TYPE, nr, type) +#define EC_IOW(nr, type) _IOW(EC_IOCTL_TYPE, nr, type) +#define EC_IOWR(nr, type) _IOWR(EC_IOCTL_TYPE, nr, type) + +/** EtherCAT master ioctl() version magic. + * + * Increment this when changing the ioctl interface! + */ +#define EC_IOCTL_VERSION_MAGIC 30 + +// Command-line tool +#define EC_IOCTL_MODULE EC_IOR(0x00, ec_ioctl_module_t) +#define EC_IOCTL_MASTER EC_IOR(0x01, ec_ioctl_master_t) +#define EC_IOCTL_SLAVE EC_IOWR(0x02, ec_ioctl_slave_t) +#define EC_IOCTL_SLAVE_SYNC EC_IOWR(0x03, ec_ioctl_slave_sync_t) +#define EC_IOCTL_SLAVE_SYNC_PDO EC_IOWR(0x04, ec_ioctl_slave_sync_pdo_t) +#define EC_IOCTL_SLAVE_SYNC_PDO_ENTRY EC_IOWR(0x05, ec_ioctl_slave_sync_pdo_entry_t) +#define EC_IOCTL_DOMAIN EC_IOWR(0x06, ec_ioctl_domain_t) +#define EC_IOCTL_DOMAIN_FMMU EC_IOWR(0x07, ec_ioctl_domain_fmmu_t) +#define EC_IOCTL_DOMAIN_DATA EC_IOWR(0x08, ec_ioctl_domain_data_t) +#define EC_IOCTL_MASTER_DEBUG EC_IO(0x09) +#define EC_IOCTL_MASTER_RESCAN EC_IO(0x0a) +#define EC_IOCTL_SLAVE_STATE EC_IOW(0x0b, ec_ioctl_slave_state_t) +#define EC_IOCTL_SLAVE_SDO EC_IOWR(0x0c, ec_ioctl_slave_sdo_t) +#define EC_IOCTL_SLAVE_SDO_ENTRY EC_IOWR(0x0d, ec_ioctl_slave_sdo_entry_t) +#define EC_IOCTL_SLAVE_SDO_UPLOAD EC_IOWR(0x0e, ec_ioctl_slave_sdo_upload_t) +#define EC_IOCTL_SLAVE_SDO_DOWNLOAD EC_IOWR(0x0f, ec_ioctl_slave_sdo_download_t) +#define EC_IOCTL_SLAVE_SII_READ EC_IOWR(0x10, ec_ioctl_slave_sii_t) +#define EC_IOCTL_SLAVE_SII_WRITE EC_IOW(0x11, ec_ioctl_slave_sii_t) +#define EC_IOCTL_SLAVE_REG_READ EC_IOWR(0x12, ec_ioctl_slave_reg_t) +#define EC_IOCTL_SLAVE_REG_WRITE EC_IOW(0x13, ec_ioctl_slave_reg_t) +#define EC_IOCTL_SLAVE_FOE_READ EC_IOWR(0x14, ec_ioctl_slave_foe_t) +#define EC_IOCTL_SLAVE_FOE_WRITE EC_IOW(0x15, ec_ioctl_slave_foe_t) +#define EC_IOCTL_SLAVE_SOE_READ EC_IOWR(0x16, ec_ioctl_slave_soe_read_t) +#define EC_IOCTL_SLAVE_SOE_WRITE EC_IOWR(0x17, ec_ioctl_slave_soe_write_t) +#define EC_IOCTL_CONFIG EC_IOWR(0x18, ec_ioctl_config_t) +#define EC_IOCTL_CONFIG_PDO EC_IOWR(0x19, ec_ioctl_config_pdo_t) +#define EC_IOCTL_CONFIG_PDO_ENTRY EC_IOWR(0x1a, ec_ioctl_config_pdo_entry_t) +#define EC_IOCTL_CONFIG_SDO EC_IOWR(0x1b, ec_ioctl_config_sdo_t) +#define EC_IOCTL_CONFIG_IDN EC_IOWR(0x1c, ec_ioctl_config_idn_t) +#ifdef EC_EOE +#define EC_IOCTL_EOE_HANDLER EC_IOWR(0x1d, ec_ioctl_eoe_handler_t) +#endif + +// Application interface +#define EC_IOCTL_REQUEST EC_IO(0x1e) +#define EC_IOCTL_CREATE_DOMAIN EC_IO(0x1f) +#define EC_IOCTL_CREATE_SLAVE_CONFIG EC_IOWR(0x20, ec_ioctl_config_t) +#define EC_IOCTL_SELECT_REF_CLOCK EC_IOW(0x21, uint32_t) +#define EC_IOCTL_ACTIVATE EC_IOR(0x22, ec_ioctl_master_activate_t) +#define EC_IOCTL_DEACTIVATE EC_IO(0x23) +#define EC_IOCTL_SEND EC_IO(0x24) +#define EC_IOCTL_RECEIVE EC_IO(0x25) +#define EC_IOCTL_MASTER_STATE EC_IOR(0x26, ec_master_state_t) +#define EC_IOCTL_MASTER_LINK_STATE EC_IOWR(0x27, ec_ioctl_link_state_t) +#define EC_IOCTL_APP_TIME EC_IOW(0x28, uint64_t) +#define EC_IOCTL_SYNC_REF EC_IO(0x29) +#define EC_IOCTL_SYNC_REF_TO EC_IOW(0x2a, uint64_t) +#define EC_IOCTL_SYNC_SLAVES EC_IO(0x2b) +#define EC_IOCTL_REF_CLOCK_TIME EC_IOR(0x2c, uint32_t) +#define EC_IOCTL_SYNC_MON_QUEUE EC_IO(0x2d) +#define EC_IOCTL_SYNC_MON_PROCESS EC_IOR(0x2e, uint32_t) +#define EC_IOCTL_RESET EC_IO(0x2f) +#define EC_IOCTL_SC_SYNC EC_IOW(0x30, ec_ioctl_config_t) +#define EC_IOCTL_SC_WATCHDOG EC_IOW(0x31, ec_ioctl_config_t) +#define EC_IOCTL_SC_ADD_PDO EC_IOW(0x32, ec_ioctl_config_pdo_t) +#define EC_IOCTL_SC_CLEAR_PDOS EC_IOW(0x33, ec_ioctl_config_pdo_t) +#define EC_IOCTL_SC_ADD_ENTRY EC_IOW(0x34, ec_ioctl_add_pdo_entry_t) +#define EC_IOCTL_SC_CLEAR_ENTRIES EC_IOW(0x35, ec_ioctl_config_pdo_t) +#define EC_IOCTL_SC_REG_PDO_ENTRY EC_IOWR(0x36, ec_ioctl_reg_pdo_entry_t) +#define EC_IOCTL_SC_REG_PDO_POS EC_IOWR(0x37, ec_ioctl_reg_pdo_pos_t) +#define EC_IOCTL_SC_DC EC_IOW(0x38, ec_ioctl_config_t) +#define EC_IOCTL_SC_SDO EC_IOW(0x39, ec_ioctl_sc_sdo_t) +#define EC_IOCTL_SC_EMERG_SIZE EC_IOW(0x3a, ec_ioctl_sc_emerg_t) +#define EC_IOCTL_SC_EMERG_POP EC_IOWR(0x3b, ec_ioctl_sc_emerg_t) +#define EC_IOCTL_SC_EMERG_CLEAR EC_IOW(0x3c, ec_ioctl_sc_emerg_t) +#define EC_IOCTL_SC_EMERG_OVERRUNS EC_IOWR(0x3d, ec_ioctl_sc_emerg_t) +#define EC_IOCTL_SC_SDO_REQUEST EC_IOWR(0x3e, ec_ioctl_sdo_request_t) +#define EC_IOCTL_SC_REG_REQUEST EC_IOWR(0x3f, ec_ioctl_reg_request_t) +#define EC_IOCTL_SC_VOE EC_IOWR(0x40, ec_ioctl_voe_t) +#define EC_IOCTL_SC_STATE EC_IOWR(0x41, ec_ioctl_sc_state_t) +#define EC_IOCTL_SC_IDN EC_IOW(0x42, ec_ioctl_sc_idn_t) +#define EC_IOCTL_DOMAIN_SIZE EC_IO(0x43) +#define EC_IOCTL_DOMAIN_OFFSET EC_IO(0x44) +#define EC_IOCTL_DOMAIN_PROCESS EC_IO(0x45) +#define EC_IOCTL_DOMAIN_QUEUE EC_IO(0x46) +#define EC_IOCTL_DOMAIN_STATE EC_IOWR(0x47, ec_ioctl_domain_state_t) +#define EC_IOCTL_SDO_REQUEST_INDEX EC_IOWR(0x48, ec_ioctl_sdo_request_t) +#define EC_IOCTL_SDO_REQUEST_TIMEOUT EC_IOWR(0x49, ec_ioctl_sdo_request_t) +#define EC_IOCTL_SDO_REQUEST_STATE EC_IOWR(0x4a, ec_ioctl_sdo_request_t) +#define EC_IOCTL_SDO_REQUEST_READ EC_IOWR(0x4b, ec_ioctl_sdo_request_t) +#define EC_IOCTL_SDO_REQUEST_WRITE EC_IOWR(0x4c, ec_ioctl_sdo_request_t) +#define EC_IOCTL_SDO_REQUEST_DATA EC_IOWR(0x4d, ec_ioctl_sdo_request_t) +#define EC_IOCTL_REG_REQUEST_DATA EC_IOWR(0x4e, ec_ioctl_reg_request_t) +#define EC_IOCTL_REG_REQUEST_STATE EC_IOWR(0x4f, ec_ioctl_reg_request_t) +#define EC_IOCTL_REG_REQUEST_WRITE EC_IOWR(0x50, ec_ioctl_reg_request_t) +#define EC_IOCTL_REG_REQUEST_READ EC_IOWR(0x51, ec_ioctl_reg_request_t) +#define EC_IOCTL_VOE_SEND_HEADER EC_IOW(0x52, ec_ioctl_voe_t) +#define EC_IOCTL_VOE_REC_HEADER EC_IOWR(0x53, ec_ioctl_voe_t) +#define EC_IOCTL_VOE_READ EC_IOW(0x54, ec_ioctl_voe_t) +#define EC_IOCTL_VOE_READ_NOSYNC EC_IOW(0x55, ec_ioctl_voe_t) +#define EC_IOCTL_VOE_WRITE EC_IOWR(0x56, ec_ioctl_voe_t) +#define EC_IOCTL_VOE_EXEC EC_IOWR(0x57, ec_ioctl_voe_t) +#define EC_IOCTL_VOE_DATA EC_IOWR(0x58, ec_ioctl_voe_t) +#define EC_IOCTL_SET_SEND_INTERVAL EC_IOW(0x59, size_t) + +/*****************************************************************************/ + +#define EC_IOCTL_STRING_SIZE 64 + +/*****************************************************************************/ + +typedef struct { + uint32_t ioctl_version_magic; + uint32_t master_count; +} ec_ioctl_module_t; + +/*****************************************************************************/ + +typedef struct { + uint32_t slave_count; + uint32_t config_count; + uint32_t domain_count; +#ifdef EC_EOE + uint32_t eoe_handler_count; +#endif + uint8_t phase; + uint8_t active; + uint8_t scan_busy; + struct ec_ioctl_device { + uint8_t address[6]; + uint8_t attached; + uint8_t link_state; + uint64_t tx_count; + uint64_t rx_count; + uint64_t tx_bytes; + uint64_t rx_bytes; + uint64_t tx_errors; + int32_t tx_frame_rates[EC_RATE_COUNT]; + int32_t rx_frame_rates[EC_RATE_COUNT]; + int32_t tx_byte_rates[EC_RATE_COUNT]; + int32_t rx_byte_rates[EC_RATE_COUNT]; + } devices[EC_MAX_NUM_DEVICES]; + uint32_t num_devices; + uint64_t tx_count; + uint64_t rx_count; + uint64_t tx_bytes; + uint64_t rx_bytes; + int32_t tx_frame_rates[EC_RATE_COUNT]; + int32_t rx_frame_rates[EC_RATE_COUNT]; + int32_t tx_byte_rates[EC_RATE_COUNT]; + int32_t rx_byte_rates[EC_RATE_COUNT]; + int32_t loss_rates[EC_RATE_COUNT]; + uint64_t app_time; + uint64_t dc_ref_time; + uint16_t ref_clock; +} ec_ioctl_master_t; + +/*****************************************************************************/ + +typedef struct { + // input + uint16_t position; + + // outputs + unsigned int device_index; + uint32_t vendor_id; + uint32_t product_code; + uint32_t revision_number; + uint32_t serial_number; + uint16_t alias; + uint16_t boot_rx_mailbox_offset; + uint16_t boot_rx_mailbox_size; + uint16_t boot_tx_mailbox_offset; + uint16_t boot_tx_mailbox_size; + uint16_t std_rx_mailbox_offset; + uint16_t std_rx_mailbox_size; + uint16_t std_tx_mailbox_offset; + uint16_t std_tx_mailbox_size; + uint16_t mailbox_protocols; + uint8_t has_general_category; + ec_sii_coe_details_t coe_details; + ec_sii_general_flags_t general_flags; + int16_t current_on_ebus; + struct { + ec_slave_port_desc_t desc; + ec_slave_port_link_t link; + uint32_t receive_time; + uint16_t next_slave; + uint32_t delay_to_next_dc; + } ports[EC_MAX_PORTS]; + uint8_t fmmu_bit; + uint8_t dc_supported; + ec_slave_dc_range_t dc_range; + uint8_t has_dc_system_time; + uint32_t transmission_delay; + uint8_t al_state; + uint8_t error_flag; + uint8_t sync_count; + uint16_t sdo_count; + uint32_t sii_nwords; + char group[EC_IOCTL_STRING_SIZE]; + char image[EC_IOCTL_STRING_SIZE]; + char order[EC_IOCTL_STRING_SIZE]; + char name[EC_IOCTL_STRING_SIZE]; +} ec_ioctl_slave_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint32_t sync_index; + + // outputs + uint16_t physical_start_address; + uint16_t default_size; + uint8_t control_register; + uint8_t enable; + uint8_t pdo_count; +} ec_ioctl_slave_sync_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint32_t sync_index; + uint32_t pdo_pos; + + // outputs + uint16_t index; + uint8_t entry_count; + int8_t name[EC_IOCTL_STRING_SIZE]; +} ec_ioctl_slave_sync_pdo_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint32_t sync_index; + uint32_t pdo_pos; + uint32_t entry_pos; + + // outputs + uint16_t index; + uint8_t subindex; + uint8_t bit_length; + int8_t name[EC_IOCTL_STRING_SIZE]; +} ec_ioctl_slave_sync_pdo_entry_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t index; + + // outputs + uint32_t data_size; + uint32_t logical_base_address; + uint16_t working_counter[EC_MAX_NUM_DEVICES]; + uint16_t expected_working_counter; + uint32_t fmmu_count; +} ec_ioctl_domain_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t domain_index; + uint32_t fmmu_index; + + // outputs + uint16_t slave_config_alias; + uint16_t slave_config_position; + uint8_t sync_index; + ec_direction_t dir; + uint32_t logical_address; + uint32_t data_size; +} ec_ioctl_domain_fmmu_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t domain_index; + uint32_t data_size; + uint8_t *target; +} ec_ioctl_domain_data_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint8_t al_state; +} ec_ioctl_slave_state_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint16_t sdo_position; + + // outputs + uint16_t sdo_index; + uint8_t max_subindex; + int8_t name[EC_IOCTL_STRING_SIZE]; +} ec_ioctl_slave_sdo_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + int sdo_spec; // positive: index, negative: list position + uint8_t sdo_entry_subindex; + + // outputs + uint16_t data_type; + uint16_t bit_length; + uint8_t read_access[EC_SDO_ENTRY_ACCESS_COUNT]; + uint8_t write_access[EC_SDO_ENTRY_ACCESS_COUNT]; + int8_t description[EC_IOCTL_STRING_SIZE]; +} ec_ioctl_slave_sdo_entry_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint16_t sdo_index; + uint8_t sdo_entry_subindex; + size_t target_size; + uint8_t *target; + + // outputs + size_t data_size; + uint32_t abort_code; +} ec_ioctl_slave_sdo_upload_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint16_t sdo_index; + uint8_t sdo_entry_subindex; + uint8_t complete_access; + size_t data_size; + uint8_t *data; + + // outputs + uint32_t abort_code; +} ec_ioctl_slave_sdo_download_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint16_t offset; + uint32_t nwords; + uint16_t *words; +} ec_ioctl_slave_sii_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint8_t emergency; + uint16_t address; + size_t size; + uint8_t *data; +} ec_ioctl_slave_reg_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint16_t offset; + size_t buffer_size; + uint8_t *buffer; + + // outputs + size_t data_size; + uint32_t result; + uint32_t error_code; + char file_name[32]; +} ec_ioctl_slave_foe_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint8_t drive_no; + uint16_t idn; + size_t mem_size; + uint8_t *data; + + // outputs + size_t data_size; + uint16_t error_code; +} ec_ioctl_slave_soe_read_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint16_t slave_position; + uint8_t drive_no; + uint16_t idn; + size_t data_size; + uint8_t *data; + + // outputs + uint16_t error_code; +} ec_ioctl_slave_soe_write_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + + // outputs + uint16_t alias; + uint16_t position; + uint32_t vendor_id; + uint32_t product_code; + struct { + ec_direction_t dir; + ec_watchdog_mode_t watchdog_mode; + uint32_t pdo_count; + uint8_t config_this; + } syncs[EC_MAX_SYNC_MANAGERS]; + uint16_t watchdog_divider; + uint16_t watchdog_intervals; + uint32_t sdo_count; + uint32_t idn_count; + int32_t slave_position; + uint16_t dc_assign_activate; + ec_sync_signal_t dc_sync[EC_SYNC_SIGNAL_COUNT]; +} ec_ioctl_config_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + uint8_t sync_index; + uint16_t pdo_pos; + + // outputs + uint16_t index; + uint8_t entry_count; + int8_t name[EC_IOCTL_STRING_SIZE]; +} ec_ioctl_config_pdo_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + uint8_t sync_index; + uint16_t pdo_pos; + uint8_t entry_pos; + + // outputs + uint16_t index; + uint8_t subindex; + uint8_t bit_length; + int8_t name[EC_IOCTL_STRING_SIZE]; +} ec_ioctl_config_pdo_entry_t; + +/*****************************************************************************/ + +/** Maximum size for displayed SDO data. + * \todo Make this dynamic. + */ +#define EC_MAX_SDO_DATA_SIZE 1024 + +typedef struct { + // inputs + uint32_t config_index; + uint32_t sdo_pos; + + // outputs + uint16_t index; + uint8_t subindex; + size_t size; + uint8_t data[EC_MAX_SDO_DATA_SIZE]; + uint8_t complete_access; +} ec_ioctl_config_sdo_t; + +/*****************************************************************************/ + +/** Maximum size for displayed IDN data. + * \todo Make this dynamic. + */ +#define EC_MAX_IDN_DATA_SIZE 1024 + +typedef struct { + // inputs + uint32_t config_index; + uint32_t idn_pos; + + // outputs + uint8_t drive_no; + uint16_t idn; + ec_al_state_t state; + size_t size; + uint8_t data[EC_MAX_IDN_DATA_SIZE]; +} ec_ioctl_config_idn_t; + +/*****************************************************************************/ + +#ifdef EC_EOE + +typedef struct { + // input + uint16_t eoe_index; + + // outputs + char name[EC_DATAGRAM_NAME_SIZE]; + uint16_t slave_position; + uint8_t open; + uint32_t rx_bytes; + uint32_t rx_rate; + uint32_t tx_bytes; + uint32_t tx_rate; + uint32_t tx_queued_frames; + uint32_t tx_queue_size; +} ec_ioctl_eoe_handler_t; + +#endif + +/*****************************************************************************/ + +typedef struct { + // outputs + void *process_data; + size_t process_data_size; +} ec_ioctl_master_activate_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + uint16_t pdo_index; + uint16_t entry_index; + uint8_t entry_subindex; + uint8_t entry_bit_length; +} ec_ioctl_add_pdo_entry_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + uint16_t entry_index; + uint8_t entry_subindex; + uint32_t domain_index; + + // outputs + unsigned int bit_position; +} ec_ioctl_reg_pdo_entry_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + uint32_t sync_index; + uint32_t pdo_pos; + uint32_t entry_pos; + uint32_t domain_index; + + // outputs + unsigned int bit_position; +} ec_ioctl_reg_pdo_pos_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + uint16_t index; + uint8_t subindex; + const uint8_t *data; + size_t size; + uint8_t complete_access; +} ec_ioctl_sc_sdo_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + size_t size; + uint8_t *target; + + // outputs + int32_t overruns; +} ec_ioctl_sc_emerg_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + + // outputs + ec_slave_config_state_t *state; +} ec_ioctl_sc_state_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + uint8_t drive_no; + uint16_t idn; + ec_al_state_t al_state; + const uint8_t *data; + size_t size; +} ec_ioctl_sc_idn_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t domain_index; + + // outputs + ec_domain_state_t *state; +} ec_ioctl_domain_state_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + + // inputs/outputs + uint32_t request_index; + uint16_t sdo_index; + uint8_t sdo_subindex; + size_t size; + uint8_t *data; + uint32_t timeout; + ec_request_state_t state; +} ec_ioctl_sdo_request_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + size_t mem_size; + + // inputs/outputs + uint32_t request_index; + uint8_t *data; + ec_request_state_t state; + uint8_t new_data; + uint16_t address; + size_t transfer_size; +} ec_ioctl_reg_request_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t config_index; + + // inputs/outputs + uint32_t voe_index; + uint32_t *vendor_id; + uint16_t *vendor_type; + size_t size; + uint8_t *data; + ec_request_state_t state; +} ec_ioctl_voe_t; + +/*****************************************************************************/ + +typedef struct { + // inputs + uint32_t dev_idx; + + // outputs + ec_master_link_state_t *state; +} ec_ioctl_link_state_t; + +/*****************************************************************************/ + +#ifdef __KERNEL__ + +/** Context data structure for file handles. + */ +typedef struct { + unsigned int writable; /**< Device was opened with write permission. */ + unsigned int requested; /**< Master was requested via this file handle. */ + uint8_t *process_data; /**< Total process data area. */ + size_t process_data_size; /**< Size of the \a process_data. */ +} ec_ioctl_context_t; + +long ec_ioctl(ec_master_t *, ec_ioctl_context_t *, unsigned int, + void __user *); + +#ifdef EC_RTDM + +long ec_ioctl_rtdm(ec_master_t *, ec_ioctl_context_t *, unsigned int, + void __user *); +int ec_rtdm_mmap(ec_ioctl_context_t *, void **); + +#endif + +#endif + +/*****************************************************************************/ + +/** \endcond */ + +#endif diff --git a/net/ethercat/master/mailbox.c b/net/ethercat/master/mailbox.c new file mode 100644 index 000000000000..10f47698b69f --- /dev/null +++ b/net/ethercat/master/mailbox.c @@ -0,0 +1,210 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Mailbox functionality. +*/ + +/*****************************************************************************/ + +#include <linux/slab.h> +#include <linux/delay.h> + +#include "mailbox.h" +#include "datagram.h" +#include "master.h" + +/*****************************************************************************/ + +/** + Prepares a mailbox-send datagram. + \return Pointer to mailbox datagram data, or ERR_PTR() code. +*/ + +uint8_t *ec_slave_mbox_prepare_send(const ec_slave_t *slave, /**< slave */ + ec_datagram_t *datagram, /**< datagram */ + uint8_t type, /**< mailbox protocol */ + size_t size /**< size of the data */ + ) +{ + size_t total_size; + int ret; + + if (unlikely(!slave->sii.mailbox_protocols)) { + EC_SLAVE_ERR(slave, "Slave does not support mailbox" + " communication!\n"); + return ERR_PTR(-EPROTONOSUPPORT); + } + + total_size = EC_MBOX_HEADER_SIZE + size; + + if (unlikely(total_size > slave->configured_rx_mailbox_size)) { + EC_SLAVE_ERR(slave, "Data size (%zu) does not fit in mailbox (%u)!\n", + total_size, slave->configured_rx_mailbox_size); + return ERR_PTR(-EOVERFLOW); + } + + ret = ec_datagram_fpwr(datagram, slave->station_address, + slave->configured_rx_mailbox_offset, + slave->configured_rx_mailbox_size); + if (ret) + return ERR_PTR(ret); + + EC_WRITE_U16(datagram->data, size); // mailbox service data length + EC_WRITE_U16(datagram->data + 2, slave->station_address); // station addr. + EC_WRITE_U8 (datagram->data + 4, 0x00); // channel & priority + EC_WRITE_U8 (datagram->data + 5, type); // underlying protocol type + + return datagram->data + EC_MBOX_HEADER_SIZE; +} + +/*****************************************************************************/ + +/** + Prepares a datagram for checking the mailbox state. + \todo Determine sync manager used for receive mailbox + \return 0 in case of success, else < 0 +*/ + +int ec_slave_mbox_prepare_check(const ec_slave_t *slave, /**< slave */ + ec_datagram_t *datagram /**< datagram */ + ) +{ + int ret = ec_datagram_fprd(datagram, slave->station_address, 0x808, 8); + if (ret) + return ret; + + ec_datagram_zero(datagram); + return 0; +} + +/*****************************************************************************/ + +/** + Processes a mailbox state checking datagram. + \return 0 in case of success, else < 0 +*/ + +int ec_slave_mbox_check(const ec_datagram_t *datagram /**< datagram */) +{ + return EC_READ_U8(datagram->data + 5) & 8 ? 1 : 0; +} + +/*****************************************************************************/ + +/** + Prepares a datagram to fetch mailbox data. + \return 0 in case of success, else < 0 +*/ + +int ec_slave_mbox_prepare_fetch(const ec_slave_t *slave, /**< slave */ + ec_datagram_t *datagram /**< datagram */ + ) +{ + int ret = ec_datagram_fprd(datagram, slave->station_address, + slave->configured_tx_mailbox_offset, + slave->configured_tx_mailbox_size); + if (ret) + return ret; + + ec_datagram_zero(datagram); + return 0; +} + +/*****************************************************************************/ + +/** + Mailbox error codes. +*/ + +const ec_code_msg_t mbox_error_messages[] = { + {0x00000001, "MBXERR_SYNTAX"}, + {0x00000002, "MBXERR_UNSUPPORTEDPROTOCOL"}, + {0x00000003, "MBXERR_INVAILDCHANNEL"}, + {0x00000004, "MBXERR_SERVICENOTSUPPORTED"}, + {0x00000005, "MBXERR_INVALIDHEADER"}, + {0x00000006, "MBXERR_SIZETOOSHORT"}, + {0x00000007, "MBXERR_NOMOREMEMORY"}, + {0x00000008, "MBXERR_INVALIDSIZE"}, + {} +}; + +/*****************************************************************************/ + +/** Processes received mailbox data. + * + * \return Pointer to the received data, or ERR_PTR() code. + */ +uint8_t *ec_slave_mbox_fetch(const ec_slave_t *slave, /**< slave */ + const ec_datagram_t *datagram, /**< datagram */ + uint8_t *type, /**< expected mailbox protocol */ + size_t *size /**< size of the received data */ + ) +{ + size_t data_size; + + data_size = EC_READ_U16(datagram->data); + + if (data_size + EC_MBOX_HEADER_SIZE > slave->configured_tx_mailbox_size) { + EC_SLAVE_ERR(slave, "Corrupt mailbox response received!\n"); + ec_print_data(datagram->data, slave->configured_tx_mailbox_size); + return ERR_PTR(-EPROTO); + } + + *type = EC_READ_U8(datagram->data + 5) & 0x0F; + *size = data_size; + + if (*type == 0x00) { + const ec_code_msg_t *mbox_msg; + uint16_t code = EC_READ_U16(datagram->data + 8); + + EC_SLAVE_ERR(slave, "Mailbox error response received - "); + + for (mbox_msg = mbox_error_messages; mbox_msg->code; mbox_msg++) { + if (mbox_msg->code != code) + continue; + printk("Code 0x%04X: \"%s\".\n", + mbox_msg->code, mbox_msg->message); + break; + } + + if (!mbox_msg->code) + printk("Unknown error reply code 0x%04X.\n", code); + + if (slave->master->debug_level) + ec_print_data(datagram->data + EC_MBOX_HEADER_SIZE, data_size); + + return ERR_PTR(-EPROTO); + } + + return datagram->data + EC_MBOX_HEADER_SIZE; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/mailbox.h b/net/ethercat/master/mailbox.h new file mode 100644 index 000000000000..cdd2eb250a1c --- /dev/null +++ b/net/ethercat/master/mailbox.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Mailbox functionality. +*/ + +/*****************************************************************************/ + +#ifndef __EC_MAILBOX_H__ +#define __EC_MAILBOX_H__ + +#include "slave.h" + +/*****************************************************************************/ + +/** Size of the mailbox header. + */ +#define EC_MBOX_HEADER_SIZE 6 + +/*****************************************************************************/ + +uint8_t *ec_slave_mbox_prepare_send(const ec_slave_t *, ec_datagram_t *, + uint8_t, size_t); +int ec_slave_mbox_prepare_check(const ec_slave_t *, ec_datagram_t *); +int ec_slave_mbox_check(const ec_datagram_t *); +int ec_slave_mbox_prepare_fetch(const ec_slave_t *, ec_datagram_t *); +uint8_t *ec_slave_mbox_fetch(const ec_slave_t *, const ec_datagram_t *, + uint8_t *, size_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/master.c b/net/ethercat/master/master.c new file mode 100644 index 000000000000..574862d7d316 --- /dev/null +++ b/net/ethercat/master/master.c @@ -0,0 +1,3321 @@ +/****************************************************************************** + * + * Copyright (C) 2006-2020 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + * vim: expandtab + * + *****************************************************************************/ + +/** + \file + EtherCAT master methods. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/version.h> +#include <linux/hrtimer.h> + +#include "globals.h" +#include "slave.h" +#include "slave_config.h" +#include "device.h" +#include "datagram.h" + +#ifdef EC_EOE +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include <uapi/linux/sched/types.h> // struct sched_param +#include <linux/sched/types.h> // sched_setscheduler +#endif +#include "ethernet.h" +#endif + +#include "master.h" + +/*****************************************************************************/ + +/** Set to 1 to enable external datagram injection debugging. + */ +#define DEBUG_INJECT 0 + +/** Always output corrupted frames. + */ +#define FORCE_OUTPUT_CORRUPTED 0 + +#ifdef EC_HAVE_CYCLES + +/** Frame timeout in cycles. + */ +static cycles_t timeout_cycles; + +/** Timeout for external datagram injection [cycles]. + */ +static cycles_t ext_injection_timeout_cycles; + +#else + +/** Frame timeout in jiffies. + */ +static unsigned long timeout_jiffies; + +/** Timeout for external datagram injection [jiffies]. + */ +static unsigned long ext_injection_timeout_jiffies; + +#endif + +/** List of intervals for statistics [s]. + */ +const unsigned int rate_intervals[] = { + 1, 10, 60 +}; + +/*****************************************************************************/ + +void ec_master_clear_slave_configs(ec_master_t *); +void ec_master_clear_domains(ec_master_t *); +static int ec_master_idle_thread(void *); +static int ec_master_operation_thread(void *); +#ifdef EC_EOE +static int ec_master_eoe_thread(void *); +#endif +void ec_master_find_dc_ref_clock(ec_master_t *); +void ec_master_clear_device_stats(ec_master_t *); +void ec_master_update_device_stats(ec_master_t *); + +/*****************************************************************************/ + +/** Static variables initializer. +*/ +void ec_master_init_static(void) +{ +#ifdef EC_HAVE_CYCLES + timeout_cycles = (cycles_t) EC_IO_TIMEOUT /* us */ * (cpu_khz / 1000); + ext_injection_timeout_cycles = + (cycles_t) EC_SDO_INJECTION_TIMEOUT /* us */ * (cpu_khz / 1000); +#else + // one jiffy may always elapse between time measurement + timeout_jiffies = max(EC_IO_TIMEOUT * HZ / 1000000, 1); + ext_injection_timeout_jiffies = + max(EC_SDO_INJECTION_TIMEOUT * HZ / 1000000, 1); +#endif +} + +/*****************************************************************************/ + +/** + Master constructor. + \return 0 in case of success, else < 0 +*/ + +int ec_master_init(ec_master_t *master, /**< EtherCAT master */ + unsigned int index, /**< master index */ + const uint8_t *main_mac, /**< MAC address of main device */ + const uint8_t *backup_mac, /**< MAC address of backup device */ + dev_t device_number, /**< Character device number. */ + struct class *class, /**< Device class. */ + unsigned int debug_level /**< Debug level (module parameter). */ + ) +{ + int ret; + unsigned int dev_idx, i; + + master->index = index; + master->reserved = 0; + + sema_init(&master->master_sem, 1); + + for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_MAX_NUM_DEVICES; dev_idx++) { + master->macs[dev_idx] = NULL; + } + + master->macs[EC_DEVICE_MAIN] = main_mac; + +#if EC_MAX_NUM_DEVICES > 1 + master->macs[EC_DEVICE_BACKUP] = backup_mac; + master->num_devices = 1 + !ec_mac_is_zero(backup_mac); +#else + if (!ec_mac_is_zero(backup_mac)) { + EC_MASTER_WARN(master, "Ignoring backup MAC address!"); + } +#endif + + ec_master_clear_device_stats(master); + + sema_init(&master->device_sem, 1); + + master->phase = EC_ORPHANED; + master->active = 0; + master->config_changed = 0; + master->injection_seq_fsm = 0; + master->injection_seq_rt = 0; + + master->slaves = NULL; + master->slave_count = 0; + + INIT_LIST_HEAD(&master->configs); + INIT_LIST_HEAD(&master->domains); + + master->app_time = 0ULL; + master->dc_ref_time = 0ULL; + + master->scan_busy = 0; + master->allow_scan = 1; + sema_init(&master->scan_sem, 1); + init_waitqueue_head(&master->scan_queue); + + master->config_busy = 0; + sema_init(&master->config_sem, 1); + init_waitqueue_head(&master->config_queue); + + INIT_LIST_HEAD(&master->datagram_queue); + master->datagram_index = 0; + + INIT_LIST_HEAD(&master->ext_datagram_queue); + sema_init(&master->ext_queue_sem, 1); + + master->ext_ring_idx_rt = 0; + master->ext_ring_idx_fsm = 0; + + // init external datagram ring + for (i = 0; i < EC_EXT_RING_SIZE; i++) { + ec_datagram_t *datagram = &master->ext_datagram_ring[i]; + ec_datagram_init(datagram); + snprintf(datagram->name, EC_DATAGRAM_NAME_SIZE, "ext-%u", i); + } + + // send interval in IDLE phase + ec_master_set_send_interval(master, 1000000 / HZ); + + master->fsm_slave = NULL; + INIT_LIST_HEAD(&master->fsm_exec_list); + master->fsm_exec_count = 0U; + + master->debug_level = debug_level; + master->stats.timeouts = 0; + master->stats.corrupted = 0; + master->stats.unmatched = 0; + master->stats.output_jiffies = 0; + + master->thread = NULL; + +#ifdef EC_EOE + master->eoe_thread = NULL; + INIT_LIST_HEAD(&master->eoe_handlers); +#endif + + sema_init(&master->io_sem, 1); + master->send_cb = NULL; + master->receive_cb = NULL; + master->cb_data = NULL; + master->app_send_cb = NULL; + master->app_receive_cb = NULL; + master->app_cb_data = NULL; + + INIT_LIST_HEAD(&master->sii_requests); + INIT_LIST_HEAD(&master->emerg_reg_requests); + + init_waitqueue_head(&master->request_queue); + + // init devices + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + ret = ec_device_init(&master->devices[dev_idx], master); + if (ret < 0) { + goto out_clear_devices; + } + } + + // init state machine datagram + ec_datagram_init(&master->fsm_datagram); + snprintf(master->fsm_datagram.name, EC_DATAGRAM_NAME_SIZE, "master-fsm"); + ret = ec_datagram_prealloc(&master->fsm_datagram, EC_MAX_DATA_SIZE); + if (ret < 0) { + ec_datagram_clear(&master->fsm_datagram); + EC_MASTER_ERR(master, "Failed to allocate FSM datagram.\n"); + goto out_clear_devices; + } + + // create state machine object + ec_fsm_master_init(&master->fsm, master, &master->fsm_datagram); + + // alloc external datagram ring + for (i = 0; i < EC_EXT_RING_SIZE; i++) { + ec_datagram_t *datagram = &master->ext_datagram_ring[i]; + ret = ec_datagram_prealloc(datagram, EC_MAX_DATA_SIZE); + if (ret) { + EC_MASTER_ERR(master, "Failed to allocate external" + " datagram %u.\n", i); + goto out_clear_ext_datagrams; + } + } + + // init reference sync datagram + ec_datagram_init(&master->ref_sync_datagram); + snprintf(master->ref_sync_datagram.name, EC_DATAGRAM_NAME_SIZE, + "refsync"); + ret = ec_datagram_prealloc(&master->ref_sync_datagram, 4); + if (ret < 0) { + ec_datagram_clear(&master->ref_sync_datagram); + EC_MASTER_ERR(master, "Failed to allocate reference" + " synchronisation datagram.\n"); + goto out_clear_ext_datagrams; + } + + // init sync datagram + ec_datagram_init(&master->sync_datagram); + snprintf(master->sync_datagram.name, EC_DATAGRAM_NAME_SIZE, "sync"); + ret = ec_datagram_prealloc(&master->sync_datagram, 4); + if (ret < 0) { + ec_datagram_clear(&master->sync_datagram); + EC_MASTER_ERR(master, "Failed to allocate" + " synchronisation datagram.\n"); + goto out_clear_ref_sync; + } + + // init sync monitor datagram + ec_datagram_init(&master->sync_mon_datagram); + snprintf(master->sync_mon_datagram.name, EC_DATAGRAM_NAME_SIZE, + "syncmon"); + ret = ec_datagram_brd(&master->sync_mon_datagram, 0x092c, 4); + if (ret < 0) { + ec_datagram_clear(&master->sync_mon_datagram); + EC_MASTER_ERR(master, "Failed to allocate sync" + " monitoring datagram.\n"); + goto out_clear_sync; + } + + master->dc_ref_config = NULL; + master->dc_ref_clock = NULL; + + // init character device + ret = ec_cdev_init(&master->cdev, master, device_number); + if (ret) + goto out_clear_sync_mon; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27) + master->class_device = device_create(class, NULL, + MKDEV(MAJOR(device_number), master->index), NULL, + "EtherCAT%u", master->index); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) + master->class_device = device_create(class, NULL, + MKDEV(MAJOR(device_number), master->index), + "EtherCAT%u", master->index); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 15) + master->class_device = class_device_create(class, NULL, + MKDEV(MAJOR(device_number), master->index), NULL, + "EtherCAT%u", master->index); +#else + master->class_device = class_device_create(class, + MKDEV(MAJOR(device_number), master->index), NULL, + "EtherCAT%u", master->index); +#endif + if (IS_ERR(master->class_device)) { + EC_MASTER_ERR(master, "Failed to create class device!\n"); + ret = PTR_ERR(master->class_device); + goto out_clear_cdev; + } + +#ifdef EC_RTDM + // init RTDM device + ret = ec_rtdm_dev_init(&master->rtdm_dev, master); + if (ret) { + goto out_unregister_class_device; + } +#endif + + return 0; + +#ifdef EC_RTDM +out_unregister_class_device: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) + device_unregister(master->class_device); +#else + class_device_unregister(master->class_device); +#endif +#endif +out_clear_cdev: + ec_cdev_clear(&master->cdev); +out_clear_sync_mon: + ec_datagram_clear(&master->sync_mon_datagram); +out_clear_sync: + ec_datagram_clear(&master->sync_datagram); +out_clear_ref_sync: + ec_datagram_clear(&master->ref_sync_datagram); +out_clear_ext_datagrams: + for (i = 0; i < EC_EXT_RING_SIZE; i++) { + ec_datagram_clear(&master->ext_datagram_ring[i]); + } + ec_fsm_master_clear(&master->fsm); + ec_datagram_clear(&master->fsm_datagram); +out_clear_devices: + for (; dev_idx > 0; dev_idx--) { + ec_device_clear(&master->devices[dev_idx - 1]); + } + return ret; +} + +/*****************************************************************************/ + +/** Destructor. +*/ +void ec_master_clear( + ec_master_t *master /**< EtherCAT master */ + ) +{ + unsigned int dev_idx, i; + +#ifdef EC_RTDM + ec_rtdm_dev_clear(&master->rtdm_dev); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) + device_unregister(master->class_device); +#else + class_device_unregister(master->class_device); +#endif + + ec_cdev_clear(&master->cdev); + +#ifdef EC_EOE + ec_master_clear_eoe_handlers(master); +#endif + ec_master_clear_domains(master); + ec_master_clear_slave_configs(master); + ec_master_clear_slaves(master); + + ec_datagram_clear(&master->sync_mon_datagram); + ec_datagram_clear(&master->sync_datagram); + ec_datagram_clear(&master->ref_sync_datagram); + + for (i = 0; i < EC_EXT_RING_SIZE; i++) { + ec_datagram_clear(&master->ext_datagram_ring[i]); + } + + ec_fsm_master_clear(&master->fsm); + ec_datagram_clear(&master->fsm_datagram); + + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + ec_device_clear(&master->devices[dev_idx]); + } +} + +/*****************************************************************************/ + +#ifdef EC_EOE +/** Clear and free all EoE handlers. + */ +void ec_master_clear_eoe_handlers( + ec_master_t *master /**< EtherCAT master */ + ) +{ + ec_eoe_t *eoe, *next; + + list_for_each_entry_safe(eoe, next, &master->eoe_handlers, list) { + list_del(&eoe->list); + ec_eoe_clear(eoe); + kfree(eoe); + } +} +#endif + +/*****************************************************************************/ + +/** Clear all slave configurations. + */ +void ec_master_clear_slave_configs(ec_master_t *master) +{ + ec_slave_config_t *sc, *next; + + master->dc_ref_config = NULL; + master->fsm.sdo_request = NULL; // mark sdo_request as invalid + + list_for_each_entry_safe(sc, next, &master->configs, list) { + list_del(&sc->list); + ec_slave_config_clear(sc); + kfree(sc); + } +} + +/*****************************************************************************/ + +/** Clear all slaves. + */ +void ec_master_clear_slaves(ec_master_t *master) +{ + ec_slave_t *slave; + + master->dc_ref_clock = NULL; + + // External requests are obsolete, so we wake pending waiters and remove + // them from the list. + + while (!list_empty(&master->sii_requests)) { + ec_sii_write_request_t *request = + list_entry(master->sii_requests.next, + ec_sii_write_request_t, list); + list_del_init(&request->list); // dequeue + EC_MASTER_WARN(master, "Discarding SII request, slave %u about" + " to be deleted.\n", request->slave->ring_position); + request->state = EC_INT_REQUEST_FAILURE; + wake_up_all(&master->request_queue); + } + + master->fsm_slave = NULL; + INIT_LIST_HEAD(&master->fsm_exec_list); + master->fsm_exec_count = 0; + + for (slave = master->slaves; + slave < master->slaves + master->slave_count; + slave++) { + ec_slave_clear(slave); + } + + if (master->slaves) { + kfree(master->slaves); + master->slaves = NULL; + } + + master->slave_count = 0; +} + +/*****************************************************************************/ + +/** Clear all domains. + */ +void ec_master_clear_domains(ec_master_t *master) +{ + ec_domain_t *domain, *next; + + list_for_each_entry_safe(domain, next, &master->domains, list) { + list_del(&domain->list); + ec_domain_clear(domain); + kfree(domain); + } +} + +/*****************************************************************************/ + +/** Clear the configuration applied by the application. + */ +void ec_master_clear_config( + ec_master_t *master /**< EtherCAT master. */ + ) +{ + down(&master->master_sem); + ec_master_clear_domains(master); + ec_master_clear_slave_configs(master); + up(&master->master_sem); +} + +/*****************************************************************************/ + +/** Internal sending callback. + */ +void ec_master_internal_send_cb( + void *cb_data /**< Callback data. */ + ) +{ + ec_master_t *master = (ec_master_t *) cb_data; + down(&master->io_sem); + ecrt_master_send_ext(master); + up(&master->io_sem); +} + +/*****************************************************************************/ + +/** Internal receiving callback. + */ +void ec_master_internal_receive_cb( + void *cb_data /**< Callback data. */ + ) +{ + ec_master_t *master = (ec_master_t *) cb_data; + down(&master->io_sem); + ecrt_master_receive(master); + up(&master->io_sem); +} + +/*****************************************************************************/ + +/** Starts the master thread. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_master_thread_start( + ec_master_t *master, /**< EtherCAT master */ + int (*thread_func)(void *), /**< thread function to start */ + const char *name /**< Thread name. */ + ) +{ + EC_MASTER_INFO(master, "Starting %s thread.\n", name); + master->thread = kthread_run(thread_func, master, name); + if (IS_ERR(master->thread)) { + int err = (int) PTR_ERR(master->thread); + EC_MASTER_ERR(master, "Failed to start master thread (error %i)!\n", + err); + master->thread = NULL; + return err; + } + + return 0; +} + +/*****************************************************************************/ + +/** Stops the master thread. + */ +void ec_master_thread_stop( + ec_master_t *master /**< EtherCAT master */ + ) +{ + unsigned long sleep_jiffies; + + if (!master->thread) { + EC_MASTER_WARN(master, "%s(): Already finished!\n", __func__); + return; + } + + EC_MASTER_DBG(master, 1, "Stopping master thread.\n"); + + kthread_stop(master->thread); + master->thread = NULL; + EC_MASTER_INFO(master, "Master thread exited.\n"); + + if (master->fsm_datagram.state != EC_DATAGRAM_SENT) { + return; + } + + // wait for FSM datagram + sleep_jiffies = max(HZ / 100, 1); // 10 ms, at least 1 jiffy + schedule_timeout(sleep_jiffies); +} + +/*****************************************************************************/ + +/** Transition function from ORPHANED to IDLE phase. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_master_enter_idle_phase( + ec_master_t *master /**< EtherCAT master */ + ) +{ + int ret; + ec_device_index_t dev_idx; + + EC_MASTER_DBG(master, 1, "ORPHANED -> IDLE.\n"); + + master->send_cb = ec_master_internal_send_cb; + master->receive_cb = ec_master_internal_receive_cb; + master->cb_data = master; + + master->phase = EC_IDLE; + + // reset number of responding slaves to trigger scanning + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + master->fsm.slaves_responding[dev_idx] = 0; + } + + ret = ec_master_thread_start(master, ec_master_idle_thread, + "EtherCAT-IDLE"); + if (ret) + master->phase = EC_ORPHANED; + + return ret; +} + +/*****************************************************************************/ + +/** Transition function from IDLE to ORPHANED phase. + */ +void ec_master_leave_idle_phase(ec_master_t *master /**< EtherCAT master */) +{ + EC_MASTER_DBG(master, 1, "IDLE -> ORPHANED.\n"); + + master->phase = EC_ORPHANED; + +#ifdef EC_EOE + ec_master_eoe_stop(master); +#endif + ec_master_thread_stop(master); + + down(&master->master_sem); + ec_master_clear_slaves(master); + up(&master->master_sem); + + ec_fsm_master_reset(&master->fsm); +} + +/*****************************************************************************/ + +/** Transition function from IDLE to OPERATION phase. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_master_enter_operation_phase( + ec_master_t *master /**< EtherCAT master */ + ) +{ + int ret = 0; + ec_slave_t *slave; +#ifdef EC_EOE + ec_eoe_t *eoe; +#endif + + EC_MASTER_DBG(master, 1, "IDLE -> OPERATION.\n"); + + down(&master->config_sem); + if (master->config_busy) { + up(&master->config_sem); + + // wait for slave configuration to complete + ret = wait_event_interruptible(master->config_queue, + !master->config_busy); + if (ret) { + EC_MASTER_INFO(master, "Finishing slave configuration" + " interrupted by signal.\n"); + goto out_return; + } + + EC_MASTER_DBG(master, 1, "Waiting for pending slave" + " configuration returned.\n"); + } else { + up(&master->config_sem); + } + + down(&master->scan_sem); + master->allow_scan = 0; // 'lock' the slave list + if (!master->scan_busy) { + up(&master->scan_sem); + } else { + up(&master->scan_sem); + + // wait for slave scan to complete + ret = wait_event_interruptible(master->scan_queue, + !master->scan_busy); + if (ret) { + EC_MASTER_INFO(master, "Waiting for slave scan" + " interrupted by signal.\n"); + goto out_allow; + } + + EC_MASTER_DBG(master, 1, "Waiting for pending" + " slave scan returned.\n"); + } + + // set states for all slaves + for (slave = master->slaves; + slave < master->slaves + master->slave_count; + slave++) { + ec_slave_request_state(slave, EC_SLAVE_STATE_PREOP); + } + +#ifdef EC_EOE + // ... but set EoE slaves to OP + list_for_each_entry(eoe, &master->eoe_handlers, list) { + if (ec_eoe_is_open(eoe)) + ec_slave_request_state(eoe->slave, EC_SLAVE_STATE_OP); + } +#endif + + master->phase = EC_OPERATION; + master->app_send_cb = NULL; + master->app_receive_cb = NULL; + master->app_cb_data = NULL; + return ret; + +out_allow: + master->allow_scan = 1; +out_return: + return ret; +} + +/*****************************************************************************/ + +/** Transition function from OPERATION to IDLE phase. + */ +void ec_master_leave_operation_phase( + ec_master_t *master /**< EtherCAT master */ + ) +{ + if (master->active) { + ecrt_master_deactivate(master); // also clears config + } else { + ec_master_clear_config(master); + } + + /* Re-allow scanning for IDLE phase. */ + master->allow_scan = 1; + + EC_MASTER_DBG(master, 1, "OPERATION -> IDLE.\n"); + + master->phase = EC_IDLE; +} + +/*****************************************************************************/ + +/** Injects external datagrams that fit into the datagram queue. + */ +void ec_master_inject_external_datagrams( + ec_master_t *master /**< EtherCAT master */ + ) +{ + ec_datagram_t *datagram; + size_t queue_size = 0, new_queue_size = 0; +#if DEBUG_INJECT + unsigned int datagram_count = 0; +#endif + + if (master->ext_ring_idx_rt == master->ext_ring_idx_fsm) { + // nothing to inject + return; + } + + list_for_each_entry(datagram, &master->datagram_queue, queue) { + if (datagram->state == EC_DATAGRAM_QUEUED) { + queue_size += datagram->data_size; + } + } + +#if DEBUG_INJECT + EC_MASTER_DBG(master, 1, "Injecting datagrams, queue_size=%zu\n", + queue_size); +#endif + + while (master->ext_ring_idx_rt != master->ext_ring_idx_fsm) { + datagram = &master->ext_datagram_ring[master->ext_ring_idx_rt]; + + if (datagram->state != EC_DATAGRAM_INIT) { + // skip datagram + master->ext_ring_idx_rt = + (master->ext_ring_idx_rt + 1) % EC_EXT_RING_SIZE; + continue; + } + + new_queue_size = queue_size + datagram->data_size; + if (new_queue_size <= master->max_queue_size) { +#if DEBUG_INJECT + EC_MASTER_DBG(master, 1, "Injecting datagram %s" + " size=%zu, queue_size=%zu\n", datagram->name, + datagram->data_size, new_queue_size); + datagram_count++; +#endif +#ifdef EC_HAVE_CYCLES + datagram->cycles_sent = 0; +#endif + datagram->jiffies_sent = 0; + ec_master_queue_datagram(master, datagram); + queue_size = new_queue_size; + } + else if (datagram->data_size > master->max_queue_size) { + datagram->state = EC_DATAGRAM_ERROR; + EC_MASTER_ERR(master, "External datagram %s is too large," + " size=%zu, max_queue_size=%zu\n", + datagram->name, datagram->data_size, + master->max_queue_size); + } + else { // datagram does not fit in the current cycle +#ifdef EC_HAVE_CYCLES + cycles_t cycles_now = get_cycles(); + + if (cycles_now - datagram->cycles_sent + > ext_injection_timeout_cycles) +#else + if (jiffies - datagram->jiffies_sent + > ext_injection_timeout_jiffies) +#endif + { +#if defined EC_RT_SYSLOG || DEBUG_INJECT + unsigned int time_us; +#endif + + datagram->state = EC_DATAGRAM_ERROR; + +#if defined EC_RT_SYSLOG || DEBUG_INJECT +#ifdef EC_HAVE_CYCLES + time_us = (unsigned int) + ((cycles_now - datagram->cycles_sent) * 1000LL) + / cpu_khz; +#else + time_us = (unsigned int) + ((jiffies - datagram->jiffies_sent) * 1000000 / HZ); +#endif + EC_MASTER_ERR(master, "Timeout %u us: Injecting" + " external datagram %s size=%zu," + " max_queue_size=%zu\n", time_us, datagram->name, + datagram->data_size, master->max_queue_size); +#endif + } + else { +#if DEBUG_INJECT + EC_MASTER_DBG(master, 1, "Deferred injecting" + " external datagram %s size=%u, queue_size=%u\n", + datagram->name, datagram->data_size, queue_size); +#endif + break; + } + } + + master->ext_ring_idx_rt = + (master->ext_ring_idx_rt + 1) % EC_EXT_RING_SIZE; + } + +#if DEBUG_INJECT + EC_MASTER_DBG(master, 1, "Injected %u datagrams.\n", datagram_count); +#endif +} + +/*****************************************************************************/ + +/** Sets the expected interval between calls to ecrt_master_send + * and calculates the maximum amount of data to queue. + */ +void ec_master_set_send_interval( + ec_master_t *master, /**< EtherCAT master */ + unsigned int send_interval /**< Send interval */ + ) +{ + master->send_interval = send_interval; + master->max_queue_size = + (send_interval * 1000) / EC_BYTE_TRANSMISSION_TIME_NS; + master->max_queue_size -= master->max_queue_size / 10; +} + +/*****************************************************************************/ + +/** Searches for a free datagram in the external datagram ring. + * + * \return Next free datagram, or NULL. + */ +ec_datagram_t *ec_master_get_external_datagram( + ec_master_t *master /**< EtherCAT master */ + ) +{ + if ((master->ext_ring_idx_fsm + 1) % EC_EXT_RING_SIZE != + master->ext_ring_idx_rt) { + ec_datagram_t *datagram = + &master->ext_datagram_ring[master->ext_ring_idx_fsm]; + return datagram; + } + else { + return NULL; + } +} + +/*****************************************************************************/ + +/** Places a datagram in the datagram queue. + */ +void ec_master_queue_datagram( + ec_master_t *master, /**< EtherCAT master */ + ec_datagram_t *datagram /**< datagram */ + ) +{ + ec_datagram_t *queued_datagram; + + /* It is possible, that a datagram in the queue is re-initialized with the + * ec_datagram_<type>() methods and then shall be queued with this method. + * In that case, the state is already reset to EC_DATAGRAM_INIT. Check if + * the datagram is queued to avoid duplicate queuing (which results in an + * infinite loop!). Set the state to EC_DATAGRAM_QUEUED again, probably + * causing an unmatched datagram. */ + list_for_each_entry(queued_datagram, &master->datagram_queue, queue) { + if (queued_datagram == datagram) { + datagram->skip_count++; +#ifdef EC_RT_SYSLOG + EC_MASTER_DBG(master, 1, + "Datagram %p already queued (skipping).\n", datagram); +#endif + datagram->state = EC_DATAGRAM_QUEUED; + return; + } + } + + list_add_tail(&datagram->queue, &master->datagram_queue); + datagram->state = EC_DATAGRAM_QUEUED; +} + +/*****************************************************************************/ + +/** Places a datagram in the non-application datagram queue. + */ +void ec_master_queue_datagram_ext( + ec_master_t *master, /**< EtherCAT master */ + ec_datagram_t *datagram /**< datagram */ + ) +{ + down(&master->ext_queue_sem); + list_add_tail(&datagram->queue, &master->ext_datagram_queue); + up(&master->ext_queue_sem); +} + +/*****************************************************************************/ + +/** Sends the datagrams in the queue for a certain device. + * + */ +void ec_master_send_datagrams( + ec_master_t *master, /**< EtherCAT master */ + ec_device_index_t device_index /**< Device index. */ + ) +{ + ec_datagram_t *datagram, *next; + size_t datagram_size; + uint8_t *frame_data, *cur_data = NULL; + void *follows_word; +#ifdef EC_HAVE_CYCLES + cycles_t cycles_start, cycles_sent, cycles_end; +#endif + unsigned long jiffies_sent; + unsigned int frame_count, more_datagrams_waiting; + struct list_head sent_datagrams; + +#ifdef EC_HAVE_CYCLES + cycles_start = get_cycles(); +#endif + frame_count = 0; + INIT_LIST_HEAD(&sent_datagrams); + + EC_MASTER_DBG(master, 2, "%s(device_index = %u)\n", + __func__, device_index); + + do { + frame_data = NULL; + follows_word = NULL; + more_datagrams_waiting = 0; + + // fill current frame with datagrams + list_for_each_entry(datagram, &master->datagram_queue, queue) { + if (datagram->state != EC_DATAGRAM_QUEUED || + datagram->device_index != device_index) { + continue; + } + + if (!frame_data) { + // fetch pointer to transmit socket buffer + frame_data = + ec_device_tx_data(&master->devices[device_index]); + cur_data = frame_data + EC_FRAME_HEADER_SIZE; + } + + // does the current datagram fit in the frame? + datagram_size = EC_DATAGRAM_HEADER_SIZE + datagram->data_size + + EC_DATAGRAM_FOOTER_SIZE; + if (cur_data - frame_data + datagram_size > ETH_DATA_LEN) { + more_datagrams_waiting = 1; + break; + } + + list_add_tail(&datagram->sent, &sent_datagrams); + datagram->index = master->datagram_index++; + + EC_MASTER_DBG(master, 2, "Adding datagram 0x%02X\n", + datagram->index); + + // set "datagram following" flag in previous datagram + if (follows_word) { + EC_WRITE_U16(follows_word, + EC_READ_U16(follows_word) | 0x8000); + } + + // EtherCAT datagram header + EC_WRITE_U8 (cur_data, datagram->type); + EC_WRITE_U8 (cur_data + 1, datagram->index); + memcpy(cur_data + 2, datagram->address, EC_ADDR_LEN); + EC_WRITE_U16(cur_data + 6, datagram->data_size & 0x7FF); + EC_WRITE_U16(cur_data + 8, 0x0000); + follows_word = cur_data + 6; + cur_data += EC_DATAGRAM_HEADER_SIZE; + + // EtherCAT datagram data + memcpy(cur_data, datagram->data, datagram->data_size); + cur_data += datagram->data_size; + + // EtherCAT datagram footer + EC_WRITE_U16(cur_data, 0x0000); // reset working counter + cur_data += EC_DATAGRAM_FOOTER_SIZE; + } + + if (list_empty(&sent_datagrams)) { + EC_MASTER_DBG(master, 2, "nothing to send.\n"); + break; + } + + // EtherCAT frame header + EC_WRITE_U16(frame_data, ((cur_data - frame_data + - EC_FRAME_HEADER_SIZE) & 0x7FF) | 0x1000); + + // pad frame + while (cur_data - frame_data < ETH_ZLEN - ETH_HLEN) + EC_WRITE_U8(cur_data++, 0x00); + + EC_MASTER_DBG(master, 2, "frame size: %zu\n", cur_data - frame_data); + + // send frame + ec_device_send(&master->devices[device_index], + cur_data - frame_data); +#ifdef EC_HAVE_CYCLES + cycles_sent = get_cycles(); +#endif + jiffies_sent = jiffies; + + // set datagram states and sending timestamps + list_for_each_entry_safe(datagram, next, &sent_datagrams, sent) { + datagram->state = EC_DATAGRAM_SENT; +#ifdef EC_HAVE_CYCLES + datagram->cycles_sent = cycles_sent; +#endif + datagram->jiffies_sent = jiffies_sent; + list_del_init(&datagram->sent); // empty list of sent datagrams + } + + frame_count++; + } + while (more_datagrams_waiting); + +#ifdef EC_HAVE_CYCLES + if (unlikely(master->debug_level > 1)) { + cycles_end = get_cycles(); + EC_MASTER_DBG(master, 0, "%s()" + " sent %u frames in %uus.\n", __func__, frame_count, + (unsigned int) (cycles_end - cycles_start) * 1000 / cpu_khz); + } +#endif +} + +/*****************************************************************************/ + +/** Processes a received frame. + * + * This function is called by the network driver for every received frame. + * + * \return 0 in case of success, else < 0 + */ +void ec_master_receive_datagrams( + ec_master_t *master, /**< EtherCAT master */ + ec_device_t *device, /**< EtherCAT device */ + const uint8_t *frame_data, /**< frame data */ + size_t size /**< size of the received data */ + ) +{ + size_t frame_size, data_size; + uint8_t datagram_type, datagram_index; + unsigned int cmd_follows, matched; + const uint8_t *cur_data; + ec_datagram_t *datagram; + + if (unlikely(size < EC_FRAME_HEADER_SIZE)) { + if (master->debug_level || FORCE_OUTPUT_CORRUPTED) { + EC_MASTER_DBG(master, 0, "Corrupted frame received" + " on %s (size %zu < %u byte):\n", + device->dev->name, size, EC_FRAME_HEADER_SIZE); + ec_print_data(frame_data, size); + } + master->stats.corrupted++; +#ifdef EC_RT_SYSLOG + ec_master_output_stats(master); +#endif + return; + } + + cur_data = frame_data; + + // check length of entire frame + frame_size = EC_READ_U16(cur_data) & 0x07FF; + cur_data += EC_FRAME_HEADER_SIZE; + + if (unlikely(frame_size > size)) { + if (master->debug_level || FORCE_OUTPUT_CORRUPTED) { + EC_MASTER_DBG(master, 0, "Corrupted frame received" + " on %s (invalid frame size %zu for " + "received size %zu):\n", device->dev->name, + frame_size, size); + ec_print_data(frame_data, size); + } + master->stats.corrupted++; +#ifdef EC_RT_SYSLOG + ec_master_output_stats(master); +#endif + return; + } + + cmd_follows = 1; + while (cmd_follows) { + // process datagram header + datagram_type = EC_READ_U8 (cur_data); + datagram_index = EC_READ_U8 (cur_data + 1); + data_size = EC_READ_U16(cur_data + 6) & 0x07FF; + cmd_follows = EC_READ_U16(cur_data + 6) & 0x8000; + cur_data += EC_DATAGRAM_HEADER_SIZE; + + if (unlikely(cur_data - frame_data + + data_size + EC_DATAGRAM_FOOTER_SIZE > size)) { + if (master->debug_level || FORCE_OUTPUT_CORRUPTED) { + EC_MASTER_DBG(master, 0, "Corrupted frame received" + " on %s (invalid data size %zu):\n", + device->dev->name, data_size); + ec_print_data(frame_data, size); + } + master->stats.corrupted++; +#ifdef EC_RT_SYSLOG + ec_master_output_stats(master); +#endif + return; + } + + // search for matching datagram in the queue + matched = 0; + list_for_each_entry(datagram, &master->datagram_queue, queue) { + if (datagram->index == datagram_index + && datagram->state == EC_DATAGRAM_SENT + && datagram->type == datagram_type + && datagram->data_size == data_size) { + matched = 1; + break; + } + } + + // no matching datagram was found + if (!matched) { + master->stats.unmatched++; +#ifdef EC_RT_SYSLOG + ec_master_output_stats(master); +#endif + + if (unlikely(master->debug_level > 0)) { + EC_MASTER_DBG(master, 0, "UNMATCHED datagram:\n"); + ec_print_data(cur_data - EC_DATAGRAM_HEADER_SIZE, + EC_DATAGRAM_HEADER_SIZE + data_size + + EC_DATAGRAM_FOOTER_SIZE); +#ifdef EC_DEBUG_RING + ec_device_debug_ring_print(&master->devices[EC_DEVICE_MAIN]); +#endif + } + + cur_data += data_size + EC_DATAGRAM_FOOTER_SIZE; + continue; + } + + if (datagram->type != EC_DATAGRAM_APWR && + datagram->type != EC_DATAGRAM_FPWR && + datagram->type != EC_DATAGRAM_BWR && + datagram->type != EC_DATAGRAM_LWR) { + // copy received data into the datagram memory, + // if something has been read + memcpy(datagram->data, cur_data, data_size); + } + cur_data += data_size; + + // set the datagram's working counter + datagram->working_counter = EC_READ_U16(cur_data); + cur_data += EC_DATAGRAM_FOOTER_SIZE; + + // dequeue the received datagram + datagram->state = EC_DATAGRAM_RECEIVED; +#ifdef EC_HAVE_CYCLES + datagram->cycles_received = + master->devices[EC_DEVICE_MAIN].cycles_poll; +#endif + datagram->jiffies_received = + master->devices[EC_DEVICE_MAIN].jiffies_poll; + list_del_init(&datagram->queue); + } +} + +/*****************************************************************************/ + +/** Output master statistics. + * + * This function outputs statistical data on demand, but not more often than + * necessary. The output happens at most once a second. + */ +void ec_master_output_stats(ec_master_t *master /**< EtherCAT master */) +{ + if (unlikely(jiffies - master->stats.output_jiffies >= HZ)) { + master->stats.output_jiffies = jiffies; + + if (master->stats.timeouts) { + EC_MASTER_WARN(master, "%u datagram%s TIMED OUT!\n", + master->stats.timeouts, + master->stats.timeouts == 1 ? "" : "s"); + master->stats.timeouts = 0; + } + if (master->stats.corrupted) { + EC_MASTER_WARN(master, "%u frame%s CORRUPTED!\n", + master->stats.corrupted, + master->stats.corrupted == 1 ? "" : "s"); + master->stats.corrupted = 0; + } + if (master->stats.unmatched) { + EC_MASTER_WARN(master, "%u datagram%s UNMATCHED!\n", + master->stats.unmatched, + master->stats.unmatched == 1 ? "" : "s"); + master->stats.unmatched = 0; + } + } +} + +/*****************************************************************************/ + +/** Clears the common device statistics. + */ +void ec_master_clear_device_stats( + ec_master_t *master /**< EtherCAT master */ + ) +{ + unsigned int i; + + // zero frame statistics + master->device_stats.tx_count = 0; + master->device_stats.last_tx_count = 0; + master->device_stats.rx_count = 0; + master->device_stats.last_rx_count = 0; + master->device_stats.tx_bytes = 0; + master->device_stats.last_tx_bytes = 0; + master->device_stats.rx_bytes = 0; + master->device_stats.last_rx_bytes = 0; + master->device_stats.last_loss = 0; + + for (i = 0; i < EC_RATE_COUNT; i++) { + master->device_stats.tx_frame_rates[i] = 0; + master->device_stats.rx_frame_rates[i] = 0; + master->device_stats.tx_byte_rates[i] = 0; + master->device_stats.rx_byte_rates[i] = 0; + master->device_stats.loss_rates[i] = 0; + } + + master->device_stats.jiffies = 0; +} + +/*****************************************************************************/ + +/** Updates the common device statistics. + */ +void ec_master_update_device_stats( + ec_master_t *master /**< EtherCAT master */ + ) +{ + ec_device_stats_t *s = &master->device_stats; + s32 tx_frame_rate, rx_frame_rate, tx_byte_rate, rx_byte_rate, loss_rate; + u64 loss; + unsigned int i, dev_idx; + + // frame statistics + if (likely(jiffies - s->jiffies < HZ)) { + return; + } + + tx_frame_rate = (s->tx_count - s->last_tx_count) * 1000; + rx_frame_rate = (s->rx_count - s->last_rx_count) * 1000; + tx_byte_rate = s->tx_bytes - s->last_tx_bytes; + rx_byte_rate = s->rx_bytes - s->last_rx_bytes; + loss = s->tx_count - s->rx_count; + loss_rate = (loss - s->last_loss) * 1000; + + /* Low-pass filter: + * Y_n = y_(n - 1) + T / tau * (x - y_(n - 1)) | T = 1 + * -> Y_n += (x - y_(n - 1)) / tau + */ + for (i = 0; i < EC_RATE_COUNT; i++) { + s32 n = rate_intervals[i]; + s->tx_frame_rates[i] += (tx_frame_rate - s->tx_frame_rates[i]) / n; + s->rx_frame_rates[i] += (rx_frame_rate - s->rx_frame_rates[i]) / n; + s->tx_byte_rates[i] += (tx_byte_rate - s->tx_byte_rates[i]) / n; + s->rx_byte_rates[i] += (rx_byte_rate - s->rx_byte_rates[i]) / n; + s->loss_rates[i] += (loss_rate - s->loss_rates[i]) / n; + } + + s->last_tx_count = s->tx_count; + s->last_rx_count = s->rx_count; + s->last_tx_bytes = s->tx_bytes; + s->last_rx_bytes = s->rx_bytes; + s->last_loss = loss; + + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + ec_device_update_stats(&master->devices[dev_idx]); + } + + s->jiffies = jiffies; +} + +/*****************************************************************************/ + +#ifdef EC_USE_HRTIMER + +/* + * Sleep related functions: + */ +static enum hrtimer_restart ec_master_nanosleep_wakeup(struct hrtimer *timer) +{ + struct hrtimer_sleeper *t = + container_of(timer, struct hrtimer_sleeper, timer); + struct task_struct *task = t->task; + + t->task = NULL; + if (task) + wake_up_process(task); + + return HRTIMER_NORESTART; +} + +/*****************************************************************************/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + +/* compatibility with new hrtimer interface */ +static inline ktime_t hrtimer_get_expires(const struct hrtimer *timer) +{ + return timer->expires; +} + +/*****************************************************************************/ + +static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time) +{ + timer->expires = time; +} + +#endif + +/*****************************************************************************/ + +void ec_master_nanosleep(const unsigned long nsecs) +{ + struct hrtimer_sleeper t; + enum hrtimer_mode mode = HRTIMER_MODE_REL; + + hrtimer_init(&t.timer, CLOCK_MONOTONIC, mode); + t.timer.function = ec_master_nanosleep_wakeup; + t.task = current; +#ifdef CONFIG_HIGH_RES_TIMERS +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24) + t.timer.cb_mode = HRTIMER_CB_IRQSAFE_NO_RESTART; +#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 26) + t.timer.cb_mode = HRTIMER_CB_IRQSAFE_NO_SOFTIRQ; +#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 28) + t.timer.cb_mode = HRTIMER_CB_IRQSAFE_UNLOCKED; +#endif +#endif + hrtimer_set_expires(&t.timer, ktime_set(0, nsecs)); + + do { + set_current_state(TASK_INTERRUPTIBLE); + hrtimer_start(&t.timer, hrtimer_get_expires(&t.timer), mode); + + if (likely(t.task)) + schedule(); + + hrtimer_cancel(&t.timer); + mode = HRTIMER_MODE_ABS; + + } while (t.task && !signal_pending(current)); +} + +#endif // EC_USE_HRTIMER + +/*****************************************************************************/ + +/* compatibility for priority changes */ +static inline void set_normal_priority(struct task_struct *p, int nice) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + sched_set_normal(p, nice); +#else + struct sched_param param = { .sched_priority = 0 }; + sched_setscheduler(p, SCHED_NORMAL, ¶m); + set_user_nice(p, nice); +#endif +} + +/*****************************************************************************/ + +/** Execute slave FSMs. + */ +void ec_master_exec_slave_fsms( + ec_master_t *master /**< EtherCAT master. */ + ) +{ + ec_datagram_t *datagram; + ec_fsm_slave_t *fsm, *next; + unsigned int count = 0; + + list_for_each_entry_safe(fsm, next, &master->fsm_exec_list, list) { + if (!fsm->datagram) { + EC_MASTER_WARN(master, "Slave %u FSM has zero datagram." + "This is a bug!\n", fsm->slave->ring_position); + list_del_init(&fsm->list); + master->fsm_exec_count--; + return; + } + + if (fsm->datagram->state == EC_DATAGRAM_INIT || + fsm->datagram->state == EC_DATAGRAM_QUEUED || + fsm->datagram->state == EC_DATAGRAM_SENT) { + // previous datagram was not sent or received yet. + // wait until next thread execution + return; + } + + datagram = ec_master_get_external_datagram(master); + if (!datagram) { + // no free datagrams at the moment + EC_MASTER_WARN(master, "No free datagram during" + " slave FSM execution. This is a bug!\n"); + continue; + } + +#if DEBUG_INJECT + EC_MASTER_DBG(master, 1, "Executing slave %u FSM.\n", + fsm->slave->ring_position); +#endif + if (ec_fsm_slave_exec(fsm, datagram)) { + // FSM consumed datagram +#if DEBUG_INJECT + EC_MASTER_DBG(master, 1, "FSM consumed datagram %s\n", + datagram->name); +#endif + master->ext_ring_idx_fsm = + (master->ext_ring_idx_fsm + 1) % EC_EXT_RING_SIZE; + } + else { + // FSM finished + list_del_init(&fsm->list); + master->fsm_exec_count--; +#if DEBUG_INJECT + EC_MASTER_DBG(master, 1, "FSM finished. %u remaining.\n", + master->fsm_exec_count); +#endif + } + } + + while (master->fsm_exec_count < EC_EXT_RING_SIZE / 2 + && count < master->slave_count) { + + if (ec_fsm_slave_is_ready(&master->fsm_slave->fsm)) { + datagram = ec_master_get_external_datagram(master); + + if (ec_fsm_slave_exec(&master->fsm_slave->fsm, datagram)) { + master->ext_ring_idx_fsm = + (master->ext_ring_idx_fsm + 1) % EC_EXT_RING_SIZE; + list_add_tail(&master->fsm_slave->fsm.list, + &master->fsm_exec_list); + master->fsm_exec_count++; +#if DEBUG_INJECT + EC_MASTER_DBG(master, 1, "New slave %u FSM" + " consumed datagram %s, now %u FSMs in list.\n", + master->fsm_slave->ring_position, datagram->name, + master->fsm_exec_count); +#endif + } + } + + master->fsm_slave++; + if (master->fsm_slave >= master->slaves + master->slave_count) { + master->fsm_slave = master->slaves; + } + count++; + } +} + +/*****************************************************************************/ + +/** Master kernel thread function for IDLE phase. + */ +static int ec_master_idle_thread(void *priv_data) +{ + ec_master_t *master = (ec_master_t *) priv_data; + int fsm_exec; +#ifdef EC_USE_HRTIMER + size_t sent_bytes; +#endif + + // send interval in IDLE phase + ec_master_set_send_interval(master, 1000000 / HZ); + + EC_MASTER_DBG(master, 1, "Idle thread running with send interval = %u us," + " max data size=%zu\n", master->send_interval, + master->max_queue_size); + + while (!kthread_should_stop()) { + ec_datagram_output_stats(&master->fsm_datagram); + + // receive + down(&master->io_sem); + ecrt_master_receive(master); + up(&master->io_sem); + + // execute master & slave state machines + if (down_interruptible(&master->master_sem)) { + break; + } + + fsm_exec = ec_fsm_master_exec(&master->fsm); + + ec_master_exec_slave_fsms(master); + + up(&master->master_sem); + + // queue and send + down(&master->io_sem); + if (fsm_exec) { + ec_master_queue_datagram(master, &master->fsm_datagram); + } + ecrt_master_send(master); +#ifdef EC_USE_HRTIMER + sent_bytes = master->devices[EC_DEVICE_MAIN].tx_skb[ + master->devices[EC_DEVICE_MAIN].tx_ring_index]->len; +#endif + up(&master->io_sem); + + if (ec_fsm_master_idle(&master->fsm)) { +#ifdef EC_USE_HRTIMER + ec_master_nanosleep(master->send_interval * 1000); +#else + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); +#endif + } else { +#ifdef EC_USE_HRTIMER + ec_master_nanosleep(sent_bytes * EC_BYTE_TRANSMISSION_TIME_NS); +#else + schedule(); +#endif + } + } + + EC_MASTER_DBG(master, 1, "Master IDLE thread exiting...\n"); + + return 0; +} + +/*****************************************************************************/ + +/** Master kernel thread function for OPERATION phase. + */ +static int ec_master_operation_thread(void *priv_data) +{ + ec_master_t *master = (ec_master_t *) priv_data; + + EC_MASTER_DBG(master, 1, "Operation thread running" + " with fsm interval = %u us, max data size=%zu\n", + master->send_interval, master->max_queue_size); + + while (!kthread_should_stop()) { + ec_datagram_output_stats(&master->fsm_datagram); + + if (master->injection_seq_rt == master->injection_seq_fsm) { + // output statistics + ec_master_output_stats(master); + + // execute master & slave state machines + if (down_interruptible(&master->master_sem)) { + break; + } + + if (ec_fsm_master_exec(&master->fsm)) { + // Inject datagrams (let the RT thread queue them, see + // ecrt_master_send()) + master->injection_seq_fsm++; + } + + ec_master_exec_slave_fsms(master); + + up(&master->master_sem); + } + +#ifdef EC_USE_HRTIMER + // the op thread should not work faster than the sending RT thread + ec_master_nanosleep(master->send_interval * 1000); +#else + if (ec_fsm_master_idle(&master->fsm)) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } + else { + schedule(); + } +#endif + } + + EC_MASTER_DBG(master, 1, "Master OP thread exiting...\n"); + return 0; +} + +/*****************************************************************************/ + +#ifdef EC_EOE +/** Starts Ethernet over EtherCAT processing on demand. + */ +void ec_master_eoe_start(ec_master_t *master /**< EtherCAT master */) +{ + if (master->eoe_thread) { + EC_MASTER_WARN(master, "EoE already running!\n"); + return; + } + + if (list_empty(&master->eoe_handlers)) + return; + + if (!master->send_cb || !master->receive_cb) { + EC_MASTER_WARN(master, "No EoE processing" + " because of missing callbacks!\n"); + return; + } + + EC_MASTER_INFO(master, "Starting EoE thread.\n"); + master->eoe_thread = kthread_run(ec_master_eoe_thread, master, + "EtherCAT-EoE"); + if (IS_ERR(master->eoe_thread)) { + int err = (int) PTR_ERR(master->eoe_thread); + EC_MASTER_ERR(master, "Failed to start EoE thread (error %i)!\n", + err); + master->eoe_thread = NULL; + return; + } + + set_normal_priority(master->eoe_thread, 0); +} + +/*****************************************************************************/ + +/** Stops the Ethernet over EtherCAT processing. + */ +void ec_master_eoe_stop(ec_master_t *master /**< EtherCAT master */) +{ + if (master->eoe_thread) { + EC_MASTER_INFO(master, "Stopping EoE thread.\n"); + + kthread_stop(master->eoe_thread); + master->eoe_thread = NULL; + EC_MASTER_INFO(master, "EoE thread exited.\n"); + } +} + +/*****************************************************************************/ + +/** Does the Ethernet over EtherCAT processing. + */ +static int ec_master_eoe_thread(void *priv_data) +{ + ec_master_t *master = (ec_master_t *) priv_data; + ec_eoe_t *eoe; + unsigned int none_open, sth_to_send, all_idle; + + EC_MASTER_DBG(master, 1, "EoE thread running.\n"); + + while (!kthread_should_stop()) { + none_open = 1; + all_idle = 1; + + list_for_each_entry(eoe, &master->eoe_handlers, list) { + if (ec_eoe_is_open(eoe)) { + none_open = 0; + break; + } + } + if (none_open) + goto schedule; + + // receive datagrams + master->receive_cb(master->cb_data); + + // actual EoE processing + sth_to_send = 0; + list_for_each_entry(eoe, &master->eoe_handlers, list) { + ec_eoe_run(eoe); + if (eoe->queue_datagram) { + sth_to_send = 1; + } + if (!ec_eoe_is_idle(eoe)) { + all_idle = 0; + } + } + + if (sth_to_send) { + list_for_each_entry(eoe, &master->eoe_handlers, list) { + ec_eoe_queue(eoe); + } + // (try to) send datagrams + down(&master->ext_queue_sem); + master->send_cb(master->cb_data); + up(&master->ext_queue_sem); + } + +schedule: + if (all_idle) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } else { + schedule(); + } + } + + EC_MASTER_DBG(master, 1, "EoE thread exiting...\n"); + return 0; +} +#endif + +/*****************************************************************************/ + +/** Attaches the slave configurations to the slaves. + */ +void ec_master_attach_slave_configs( + ec_master_t *master /**< EtherCAT master. */ + ) +{ + ec_slave_config_t *sc; + + list_for_each_entry(sc, &master->configs, list) { + ec_slave_config_attach(sc); + } +} + +/*****************************************************************************/ + +/** Common implementation for ec_master_find_slave() + * and ec_master_find_slave_const(). + */ +#define EC_FIND_SLAVE \ + do { \ + if (alias) { \ + for (; slave < master->slaves + master->slave_count; \ + slave++) { \ + if (slave->effective_alias == alias) \ + break; \ + } \ + if (slave == master->slaves + master->slave_count) \ + return NULL; \ + } \ + \ + slave += position; \ + if (slave < master->slaves + master->slave_count) { \ + return slave; \ + } else { \ + return NULL; \ + } \ + } while (0) + +/** Finds a slave in the bus, given the alias and position. + * + * \return Search result, or NULL. + */ +ec_slave_t *ec_master_find_slave( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t alias, /**< Slave alias. */ + uint16_t position /**< Slave position. */ + ) +{ + ec_slave_t *slave = master->slaves; + EC_FIND_SLAVE; +} + +/** Finds a slave in the bus, given the alias and position. + * + * Const version. + * + * \return Search result, or NULL. + */ +const ec_slave_t *ec_master_find_slave_const( + const ec_master_t *master, /**< EtherCAT master. */ + uint16_t alias, /**< Slave alias. */ + uint16_t position /**< Slave position. */ + ) +{ + const ec_slave_t *slave = master->slaves; + EC_FIND_SLAVE; +} + +/*****************************************************************************/ + +/** Get the number of slave configurations provided by the application. + * + * \return Number of configurations. + */ +unsigned int ec_master_config_count( + const ec_master_t *master /**< EtherCAT master. */ + ) +{ + const ec_slave_config_t *sc; + unsigned int count = 0; + + list_for_each_entry(sc, &master->configs, list) { + count++; + } + + return count; +} + +/*****************************************************************************/ + +/** Common implementation for ec_master_get_config() + * and ec_master_get_config_const(). + */ +#define EC_FIND_CONFIG \ + do { \ + list_for_each_entry(sc, &master->configs, list) { \ + if (pos--) \ + continue; \ + return sc; \ + } \ + return NULL; \ + } while (0) + +/** Get a slave configuration via its position in the list. + * + * \return Slave configuration or \a NULL. + */ +ec_slave_config_t *ec_master_get_config( + const ec_master_t *master, /**< EtherCAT master. */ + unsigned int pos /**< List position. */ + ) +{ + ec_slave_config_t *sc; + EC_FIND_CONFIG; +} + +/** Get a slave configuration via its position in the list. + * + * Const version. + * + * \return Slave configuration or \a NULL. + */ +const ec_slave_config_t *ec_master_get_config_const( + const ec_master_t *master, /**< EtherCAT master. */ + unsigned int pos /**< List position. */ + ) +{ + const ec_slave_config_t *sc; + EC_FIND_CONFIG; +} + +/*****************************************************************************/ + +/** Get the number of domains. + * + * \return Number of domains. + */ +unsigned int ec_master_domain_count( + const ec_master_t *master /**< EtherCAT master. */ + ) +{ + const ec_domain_t *domain; + unsigned int count = 0; + + list_for_each_entry(domain, &master->domains, list) { + count++; + } + + return count; +} + +/*****************************************************************************/ + +/** Common implementation for ec_master_find_domain() and + * ec_master_find_domain_const(). + */ +#define EC_FIND_DOMAIN \ + do { \ + list_for_each_entry(domain, &master->domains, list) { \ + if (index--) \ + continue; \ + return domain; \ + } \ + \ + return NULL; \ + } while (0) + +/** Get a domain via its position in the list. + * + * \return Domain pointer, or \a NULL if not found. + */ +ec_domain_t *ec_master_find_domain( + ec_master_t *master, /**< EtherCAT master. */ + unsigned int index /**< Domain index. */ + ) +{ + ec_domain_t *domain; + EC_FIND_DOMAIN; +} + +/** Get a domain via its position in the list. + * + * Const version. + * + * \return Domain pointer, or \a NULL if not found. + */ +const ec_domain_t *ec_master_find_domain_const( + const ec_master_t *master, /**< EtherCAT master. */ + unsigned int index /**< Domain index. */ + ) +{ + const ec_domain_t *domain; + EC_FIND_DOMAIN; +} + +/*****************************************************************************/ + +#ifdef EC_EOE + +/** Get the number of EoE handlers. + * + * \return Number of EoE handlers. + */ +uint16_t ec_master_eoe_handler_count( + const ec_master_t *master /**< EtherCAT master. */ + ) +{ + const ec_eoe_t *eoe; + unsigned int count = 0; + + list_for_each_entry(eoe, &master->eoe_handlers, list) { + count++; + } + + return count; +} + +/*****************************************************************************/ + +/** Get an EoE handler via its position in the list. + * + * Const version. + * + * \return EoE handler pointer, or \a NULL if not found. + */ +const ec_eoe_t *ec_master_get_eoe_handler_const( + const ec_master_t *master, /**< EtherCAT master. */ + uint16_t index /**< EoE handler index. */ + ) +{ + const ec_eoe_t *eoe; + + list_for_each_entry(eoe, &master->eoe_handlers, list) { + if (index--) + continue; + return eoe; + } + + return NULL; +} + +#endif + +/*****************************************************************************/ + +/** Set the debug level. + * + * \retval 0 Success. + * \retval -EINVAL Invalid debug level. + */ +int ec_master_debug_level( + ec_master_t *master, /**< EtherCAT master. */ + unsigned int level /**< Debug level. May be 0, 1 or 2. */ + ) +{ + if (level > 2) { + EC_MASTER_ERR(master, "Invalid debug level %u!\n", level); + return -EINVAL; + } + + if (level != master->debug_level) { + master->debug_level = level; + EC_MASTER_INFO(master, "Master debug level set to %u.\n", + master->debug_level); + } + + return 0; +} + +/*****************************************************************************/ + +/** Finds the DC reference clock. + */ +void ec_master_find_dc_ref_clock( + ec_master_t *master /**< EtherCAT master. */ + ) +{ + ec_slave_t *slave, *ref = NULL; + + if (master->dc_ref_config) { + // Use application-selected reference clock + slave = master->dc_ref_config->slave; + + if (slave) { + if (slave->base_dc_supported && slave->has_dc_system_time) { + ref = slave; + } + else { + EC_MASTER_WARN(master, "Slave %u can not act as a" + " DC reference clock!", slave->ring_position); + } + } + else { + EC_MASTER_WARN(master, "DC reference clock config (%u-%u)" + " has no slave attached!\n", master->dc_ref_config->alias, + master->dc_ref_config->position); + } + } + else { + // Use first slave with DC support as reference clock + for (slave = master->slaves; + slave < master->slaves + master->slave_count; + slave++) { + if (slave->base_dc_supported && slave->has_dc_system_time) { + ref = slave; + break; + } + } + + } + + master->dc_ref_clock = ref; + + if (ref) { + EC_MASTER_INFO(master, "Using slave %u as DC reference clock.\n", + ref->ring_position); + } + else { + EC_MASTER_INFO(master, "No DC reference clock found.\n"); + } + + // These calls always succeed, because the + // datagrams have been pre-allocated. + ec_datagram_fpwr(&master->ref_sync_datagram, + ref ? ref->station_address : 0xffff, 0x0910, 4); + ec_datagram_frmw(&master->sync_datagram, + ref ? ref->station_address : 0xffff, 0x0910, 4); +} + +/*****************************************************************************/ + +/** Calculates the bus topology; recursion function. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_master_calc_topology_rec( + ec_master_t *master, /**< EtherCAT master. */ + ec_slave_t *port0_slave, /**< Slave at port 0. */ + unsigned int *slave_position /**< Slave position. */ + ) +{ + ec_slave_t *slave = master->slaves + *slave_position; + unsigned int port_index; + int ret; + + static const unsigned int next_table[EC_MAX_PORTS] = { + 3, 2, 0, 1 + }; + + slave->ports[0].next_slave = port0_slave; + + port_index = 3; + while (port_index != 0) { + if (!slave->ports[port_index].link.loop_closed) { + *slave_position = *slave_position + 1; + if (*slave_position < master->slave_count) { + slave->ports[port_index].next_slave = + master->slaves + *slave_position; + ret = ec_master_calc_topology_rec(master, + slave, slave_position); + if (ret) { + return ret; + } + } else { + return -1; + } + } + + port_index = next_table[port_index]; + } + + return 0; +} + +/*****************************************************************************/ + +/** Calculates the bus topology. + */ +void ec_master_calc_topology( + ec_master_t *master /**< EtherCAT master. */ + ) +{ + unsigned int slave_position = 0; + + if (master->slave_count == 0) + return; + + if (ec_master_calc_topology_rec(master, NULL, &slave_position)) + EC_MASTER_ERR(master, "Failed to calculate bus topology.\n"); +} + +/*****************************************************************************/ + +/** Calculates the bus transmission delays. + */ +void ec_master_calc_transmission_delays( + ec_master_t *master /**< EtherCAT master. */ + ) +{ + ec_slave_t *slave; + + for (slave = master->slaves; + slave < master->slaves + master->slave_count; + slave++) { + ec_slave_calc_port_delays(slave); + } + + if (master->dc_ref_clock) { + uint32_t delay = 0; + ec_slave_calc_transmission_delays_rec(master->dc_ref_clock, &delay); + } +} + +/*****************************************************************************/ + +/** Distributed-clocks calculations. + */ +void ec_master_calc_dc( + ec_master_t *master /**< EtherCAT master. */ + ) +{ + // find DC reference clock + ec_master_find_dc_ref_clock(master); + + // calculate bus topology + ec_master_calc_topology(master); + + ec_master_calc_transmission_delays(master); +} + +/*****************************************************************************/ + +/** Request OP state for configured slaves. + */ +void ec_master_request_op( + ec_master_t *master /**< EtherCAT master. */ + ) +{ + unsigned int i; + ec_slave_t *slave; + + if (!master->active) + return; + + EC_MASTER_DBG(master, 1, "Requesting OP...\n"); + + // request OP for all configured slaves + for (i = 0; i < master->slave_count; i++) { + slave = master->slaves + i; + if (slave->config) { + ec_slave_request_state(slave, EC_SLAVE_STATE_OP); + } + } + + // always set DC reference clock to OP + if (master->dc_ref_clock) { + ec_slave_request_state(master->dc_ref_clock, EC_SLAVE_STATE_OP); + } +} + +/****************************************************************************** + * Application interface + *****************************************************************************/ + +/** Same as ecrt_master_create_domain(), but with ERR_PTR() return value. + * + * \return New domain, or ERR_PTR() return value. + */ +ec_domain_t *ecrt_master_create_domain_err( + ec_master_t *master /**< master */ + ) +{ + ec_domain_t *domain, *last_domain; + unsigned int index; + + EC_MASTER_DBG(master, 1, "ecrt_master_create_domain(master = 0x%p)\n", + master); + + if (!(domain = + (ec_domain_t *) kmalloc(sizeof(ec_domain_t), GFP_KERNEL))) { + EC_MASTER_ERR(master, "Error allocating domain memory!\n"); + return ERR_PTR(-ENOMEM); + } + + down(&master->master_sem); + + if (list_empty(&master->domains)) { + index = 0; + } else { + last_domain = list_entry(master->domains.prev, ec_domain_t, list); + index = last_domain->index + 1; + } + + ec_domain_init(domain, master, index); + list_add_tail(&domain->list, &master->domains); + + up(&master->master_sem); + + EC_MASTER_DBG(master, 1, "Created domain %u.\n", domain->index); + + return domain; +} + +/*****************************************************************************/ + +ec_domain_t *ecrt_master_create_domain( + ec_master_t *master /**< master */ + ) +{ + ec_domain_t *d = ecrt_master_create_domain_err(master); + return IS_ERR(d) ? NULL : d; +} + +/*****************************************************************************/ + +int ecrt_master_activate(ec_master_t *master) +{ + uint32_t domain_offset; + ec_domain_t *domain; + int ret; +#ifdef EC_EOE + int eoe_was_running; +#endif + + EC_MASTER_DBG(master, 1, "ecrt_master_activate(master = 0x%p)\n", master); + + if (master->active) { + EC_MASTER_WARN(master, "%s: Master already active!\n", __func__); + return 0; + } + + down(&master->master_sem); + + // finish all domains + domain_offset = 0; + list_for_each_entry(domain, &master->domains, list) { + ret = ec_domain_finish(domain, domain_offset); + if (ret < 0) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Failed to finish domain 0x%p!\n", domain); + return ret; + } + domain_offset += domain->data_size; + } + + up(&master->master_sem); + + // restart EoE process and master thread with new locking + + ec_master_thread_stop(master); +#ifdef EC_EOE + eoe_was_running = master->eoe_thread != NULL; + ec_master_eoe_stop(master); +#endif + + EC_MASTER_DBG(master, 1, "FSM datagram is %p.\n", &master->fsm_datagram); + + master->injection_seq_fsm = 0; + master->injection_seq_rt = 0; + + master->send_cb = master->app_send_cb; + master->receive_cb = master->app_receive_cb; + master->cb_data = master->app_cb_data; + +#ifdef EC_EOE + if (eoe_was_running) { + ec_master_eoe_start(master); + } +#endif + ret = ec_master_thread_start(master, ec_master_operation_thread, + "EtherCAT-OP"); + if (ret < 0) { + EC_MASTER_ERR(master, "Failed to start master thread!\n"); + return ret; + } + + /* Allow scanning after a topology change. */ + master->allow_scan = 1; + + master->active = 1; + + // notify state machine, that the configuration shall now be applied + master->config_changed = 1; + + return 0; +} + +/*****************************************************************************/ + +void ecrt_master_deactivate(ec_master_t *master) +{ + ec_slave_t *slave; +#ifdef EC_EOE + ec_eoe_t *eoe; + int eoe_was_running; +#endif + + EC_MASTER_DBG(master, 1, "%s(master = 0x%p)\n", __func__, master); + + if (!master->active) { + EC_MASTER_WARN(master, "%s: Master not active.\n", __func__); + return; + } + + ec_master_thread_stop(master); +#ifdef EC_EOE + eoe_was_running = master->eoe_thread != NULL; + ec_master_eoe_stop(master); +#endif + + master->send_cb = ec_master_internal_send_cb; + master->receive_cb = ec_master_internal_receive_cb; + master->cb_data = master; + + ec_master_clear_config(master); + + for (slave = master->slaves; + slave < master->slaves + master->slave_count; + slave++) { + + // set states for all slaves + ec_slave_request_state(slave, EC_SLAVE_STATE_PREOP); + + // mark for reconfiguration, because the master could have no + // possibility for a reconfiguration between two sequential operation + // phases. + slave->force_config = 1; + } + +#ifdef EC_EOE + // ... but leave EoE slaves in OP + list_for_each_entry(eoe, &master->eoe_handlers, list) { + if (ec_eoe_is_open(eoe)) + ec_slave_request_state(eoe->slave, EC_SLAVE_STATE_OP); + } +#endif + + master->app_time = 0ULL; + master->dc_ref_time = 0ULL; + +#ifdef EC_EOE + if (eoe_was_running) { + ec_master_eoe_start(master); + } +#endif + if (ec_master_thread_start(master, ec_master_idle_thread, + "EtherCAT-IDLE")) { + EC_MASTER_WARN(master, "Failed to restart master thread!\n"); + } + + /* Disallow scanning to get into the same state like after a master + * request (after ec_master_enter_operation_phase() is called). */ + master->allow_scan = 0; + + master->active = 0; +} + +/*****************************************************************************/ + +void ecrt_master_send(ec_master_t *master) +{ + ec_datagram_t *datagram, *n; + ec_device_index_t dev_idx; + + if (master->injection_seq_rt != master->injection_seq_fsm) { + // inject datagram produced by master FSM + ec_master_queue_datagram(master, &master->fsm_datagram); + master->injection_seq_rt = master->injection_seq_fsm; + } + + ec_master_inject_external_datagrams(master); + + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + if (unlikely(!master->devices[dev_idx].link_state)) { + // link is down, no datagram can be sent + list_for_each_entry_safe(datagram, n, + &master->datagram_queue, queue) { + if (datagram->device_index == dev_idx) { + datagram->state = EC_DATAGRAM_ERROR; + list_del_init(&datagram->queue); + } + } + + if (!master->devices[dev_idx].dev) { + continue; + } + + // query link state + ec_device_poll(&master->devices[dev_idx]); + + // clear frame statistics + ec_device_clear_stats(&master->devices[dev_idx]); + continue; + } + + // send frames + ec_master_send_datagrams(master, dev_idx); + } +} + +/*****************************************************************************/ + +void ecrt_master_receive(ec_master_t *master) +{ + unsigned int dev_idx; + ec_datagram_t *datagram, *next; + + // receive datagrams + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + ec_device_poll(&master->devices[dev_idx]); + } + ec_master_update_device_stats(master); + + // dequeue all datagrams that timed out + list_for_each_entry_safe(datagram, next, &master->datagram_queue, queue) { + if (datagram->state != EC_DATAGRAM_SENT) continue; + +#ifdef EC_HAVE_CYCLES + if (master->devices[EC_DEVICE_MAIN].cycles_poll - + datagram->cycles_sent > timeout_cycles) { +#else + if (master->devices[EC_DEVICE_MAIN].jiffies_poll - + datagram->jiffies_sent > timeout_jiffies) { +#endif + list_del_init(&datagram->queue); + datagram->state = EC_DATAGRAM_TIMED_OUT; + master->stats.timeouts++; + +#ifdef EC_RT_SYSLOG + ec_master_output_stats(master); + + if (unlikely(master->debug_level > 0)) { + unsigned int time_us; +#ifdef EC_HAVE_CYCLES + time_us = (unsigned int) + (master->devices[EC_DEVICE_MAIN].cycles_poll - + datagram->cycles_sent) * 1000 / cpu_khz; +#else + time_us = (unsigned int) + ((master->devices[EC_DEVICE_MAIN].jiffies_poll - + datagram->jiffies_sent) * 1000000 / HZ); +#endif + EC_MASTER_DBG(master, 0, "TIMED OUT datagram %p," + " index %02X waited %u us.\n", + datagram, datagram->index, time_us); + } +#endif /* RT_SYSLOG */ + } + } +} + +/*****************************************************************************/ + +void ecrt_master_send_ext(ec_master_t *master) +{ + ec_datagram_t *datagram, *next; + + list_for_each_entry_safe(datagram, next, &master->ext_datagram_queue, + queue) { + list_del(&datagram->queue); + ec_master_queue_datagram(master, datagram); + } + + ecrt_master_send(master); +} + +/*****************************************************************************/ + +/** Same as ecrt_master_slave_config(), but with ERR_PTR() return value. + */ +ec_slave_config_t *ecrt_master_slave_config_err(ec_master_t *master, + uint16_t alias, uint16_t position, uint32_t vendor_id, + uint32_t product_code) +{ + ec_slave_config_t *sc; + unsigned int found = 0; + + + EC_MASTER_DBG(master, 1, "ecrt_master_slave_config(master = 0x%p," + " alias = %u, position = %u, vendor_id = 0x%08x," + " product_code = 0x%08x)\n", + master, alias, position, vendor_id, product_code); + + list_for_each_entry(sc, &master->configs, list) { + if (sc->alias == alias && sc->position == position) { + found = 1; + break; + } + } + + if (found) { // config with same alias/position already existing + if (sc->vendor_id != vendor_id || sc->product_code != product_code) { + EC_MASTER_ERR(master, "Slave type mismatch. Slave was" + " configured as 0x%08X/0x%08X before. Now configuring" + " with 0x%08X/0x%08X.\n", sc->vendor_id, sc->product_code, + vendor_id, product_code); + return ERR_PTR(-ENOENT); + } + } else { + EC_MASTER_DBG(master, 1, "Creating slave configuration for %u:%u," + " 0x%08X/0x%08X.\n", + alias, position, vendor_id, product_code); + + if (!(sc = (ec_slave_config_t *) kmalloc(sizeof(ec_slave_config_t), + GFP_KERNEL))) { + EC_MASTER_ERR(master, "Failed to allocate memory" + " for slave configuration.\n"); + return ERR_PTR(-ENOMEM); + } + + ec_slave_config_init(sc, master, + alias, position, vendor_id, product_code); + + down(&master->master_sem); + + // try to find the addressed slave + ec_slave_config_attach(sc); + ec_slave_config_load_default_sync_config(sc); + list_add_tail(&sc->list, &master->configs); + + up(&master->master_sem); + } + + return sc; +} + +/*****************************************************************************/ + +ec_slave_config_t *ecrt_master_slave_config(ec_master_t *master, + uint16_t alias, uint16_t position, uint32_t vendor_id, + uint32_t product_code) +{ + ec_slave_config_t *sc = ecrt_master_slave_config_err(master, alias, + position, vendor_id, product_code); + return IS_ERR(sc) ? NULL : sc; +} + +/*****************************************************************************/ + +int ecrt_master_select_reference_clock(ec_master_t *master, + ec_slave_config_t *sc) +{ + if (sc) { + ec_slave_t *slave = sc->slave; + + // output an early warning + if (slave && + (!slave->base_dc_supported || !slave->has_dc_system_time)) { + EC_MASTER_WARN(master, "Slave %u can not act as" + " a reference clock!", slave->ring_position); + } + } + + master->dc_ref_config = sc; + return 0; +} + +/*****************************************************************************/ + +int ecrt_master(ec_master_t *master, ec_master_info_t *master_info) +{ + EC_MASTER_DBG(master, 1, "ecrt_master(master = 0x%p," + " master_info = 0x%p)\n", master, master_info); + + master_info->slave_count = master->slave_count; + master_info->link_up = master->devices[EC_DEVICE_MAIN].link_state; + master_info->scan_busy = master->scan_busy; + master_info->app_time = master->app_time; + return 0; +} + +/*****************************************************************************/ + +int ecrt_master_get_slave(ec_master_t *master, uint16_t slave_position, + ec_slave_info_t *slave_info) +{ + const ec_slave_t *slave; + unsigned int i; + int ret = 0; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + slave = ec_master_find_slave_const(master, 0, slave_position); + + if (slave == NULL) { + ret = -ENOENT; + goto out_get_slave; + } + + slave_info->position = slave->ring_position; + slave_info->vendor_id = slave->sii.vendor_id; + slave_info->product_code = slave->sii.product_code; + slave_info->revision_number = slave->sii.revision_number; + slave_info->serial_number = slave->sii.serial_number; + slave_info->alias = slave->effective_alias; + slave_info->current_on_ebus = slave->sii.current_on_ebus; + + for (i = 0; i < EC_MAX_PORTS; i++) { + slave_info->ports[i].desc = slave->ports[i].desc; + slave_info->ports[i].link.link_up = slave->ports[i].link.link_up; + slave_info->ports[i].link.loop_closed = + slave->ports[i].link.loop_closed; + slave_info->ports[i].link.signal_detected = + slave->ports[i].link.signal_detected; + slave_info->ports[i].receive_time = slave->ports[i].receive_time; + if (slave->ports[i].next_slave) { + slave_info->ports[i].next_slave = + slave->ports[i].next_slave->ring_position; + } else { + slave_info->ports[i].next_slave = 0xffff; + } + slave_info->ports[i].delay_to_next_dc = + slave->ports[i].delay_to_next_dc; + } + + slave_info->al_state = slave->current_state; + slave_info->error_flag = slave->error_flag; + slave_info->sync_count = slave->sii.sync_count; + slave_info->sdo_count = ec_slave_sdo_count(slave); + if (slave->sii.name) { + strncpy(slave_info->name, slave->sii.name, EC_MAX_STRING_LENGTH); + } else { + slave_info->name[0] = 0; + } + +out_get_slave: + up(&master->master_sem); + + return ret; +} + +/*****************************************************************************/ + +void ecrt_master_callbacks(ec_master_t *master, + void (*send_cb)(void *), void (*receive_cb)(void *), void *cb_data) +{ + EC_MASTER_DBG(master, 1, "ecrt_master_callbacks(master = 0x%p," + " send_cb = 0x%p, receive_cb = 0x%p, cb_data = 0x%p)\n", + master, send_cb, receive_cb, cb_data); + + master->app_send_cb = send_cb; + master->app_receive_cb = receive_cb; + master->app_cb_data = cb_data; +} + +/*****************************************************************************/ + +void ecrt_master_state(const ec_master_t *master, ec_master_state_t *state) +{ + ec_device_index_t dev_idx; + + state->slaves_responding = 0U; + state->al_states = 0; + state->link_up = 0U; + + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + /* Announce sum of responding slaves on all links. */ + state->slaves_responding += master->fsm.slaves_responding[dev_idx]; + + /* Binary-or slave states of all links. */ + state->al_states |= master->fsm.slave_states[dev_idx]; + + /* Signal link up if at least one device has link. */ + state->link_up |= master->devices[dev_idx].link_state; + } +} + +/*****************************************************************************/ + +int ecrt_master_link_state(const ec_master_t *master, unsigned int dev_idx, + ec_master_link_state_t *state) +{ + if (dev_idx >= ec_master_num_devices(master)) { + return -EINVAL; + } + + state->slaves_responding = master->fsm.slaves_responding[dev_idx]; + state->al_states = master->fsm.slave_states[dev_idx]; + state->link_up = master->devices[dev_idx].link_state; + + return 0; +} + +/*****************************************************************************/ + +void ecrt_master_application_time(ec_master_t *master, uint64_t app_time) +{ + master->app_time = app_time; + + if (unlikely(!master->dc_ref_time)) { + master->dc_ref_time = app_time; + } +} + +/*****************************************************************************/ + +int ecrt_master_reference_clock_time(ec_master_t *master, uint32_t *time) +{ + if (!master->dc_ref_clock) { + return -ENXIO; + } + + if (master->sync_datagram.state != EC_DATAGRAM_RECEIVED) { + return -EIO; + } + + // Get returned datagram time, transmission delay removed. + *time = EC_READ_U32(master->sync_datagram.data) - + master->dc_ref_clock->transmission_delay; + + return 0; +} + +/*****************************************************************************/ + +void ecrt_master_sync_reference_clock(ec_master_t *master) +{ + if (master->dc_ref_clock) { + EC_WRITE_U32(master->ref_sync_datagram.data, master->app_time); + ec_master_queue_datagram(master, &master->ref_sync_datagram); + } +} + +/*****************************************************************************/ + +void ecrt_master_sync_reference_clock_to( + ec_master_t *master, + uint64_t sync_time + ) +{ + if (master->dc_ref_clock) { + EC_WRITE_U32(master->ref_sync_datagram.data, sync_time); + ec_master_queue_datagram(master, &master->ref_sync_datagram); + } +} + +/*****************************************************************************/ + +void ecrt_master_sync_slave_clocks(ec_master_t *master) +{ + if (master->dc_ref_clock) { + ec_datagram_zero(&master->sync_datagram); + ec_master_queue_datagram(master, &master->sync_datagram); + } +} + +/*****************************************************************************/ + +void ecrt_master_sync_monitor_queue(ec_master_t *master) +{ + ec_datagram_zero(&master->sync_mon_datagram); + ec_master_queue_datagram(master, &master->sync_mon_datagram); +} + +/*****************************************************************************/ + +uint32_t ecrt_master_sync_monitor_process(ec_master_t *master) +{ + if (master->sync_mon_datagram.state == EC_DATAGRAM_RECEIVED) { + return EC_READ_U32(master->sync_mon_datagram.data) & 0x7fffffff; + } else { + return 0xffffffff; + } +} + +/*****************************************************************************/ + +int ecrt_master_sdo_download(ec_master_t *master, uint16_t slave_position, + uint16_t index, uint8_t subindex, uint8_t *data, + size_t data_size, uint32_t *abort_code) +{ + ec_sdo_request_t request; + ec_slave_t *slave; + int ret; + + EC_MASTER_DBG(master, 1, "%s(master = 0x%p," + " slave_position = %u, index = 0x%04X, subindex = 0x%02X," + " data = 0x%p, data_size = %zu, abort_code = 0x%p)\n", + __func__, master, slave_position, index, subindex, + data, data_size, abort_code); + + if (!data_size) { + EC_MASTER_ERR(master, "Zero data size!\n"); + return -EINVAL; + } + + ec_sdo_request_init(&request); + ecrt_sdo_request_index(&request, index, subindex); + ret = ec_sdo_request_alloc(&request, data_size); + if (ret) { + ec_sdo_request_clear(&request); + return ret; + } + + memcpy(request.data, data, data_size); + request.data_size = data_size; + ecrt_sdo_request_write(&request); + + if (down_interruptible(&master->master_sem)) { + ec_sdo_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", slave_position); + ec_sdo_request_clear(&request); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling SDO download request.\n"); + + // schedule request. + list_add_tail(&request.list, &slave->sdo_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + list_del(&request.list); + up(&master->master_sem); + ec_sdo_request_clear(&request); + return -EINTR; + } + // request already processing: interrupt not possible. + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + *abort_code = request.abort_code; + + if (request.state == EC_INT_REQUEST_SUCCESS) { + ret = 0; + } else if (request.errno) { + ret = -request.errno; + } else { + ret = -EIO; + } + + ec_sdo_request_clear(&request); + return ret; +} + +/*****************************************************************************/ + +int ecrt_master_sdo_download_complete(ec_master_t *master, + uint16_t slave_position, uint16_t index, uint8_t *data, + size_t data_size, uint32_t *abort_code) +{ + ec_sdo_request_t request; + ec_slave_t *slave; + int ret; + + EC_MASTER_DBG(master, 1, "%s(master = 0x%p," + " slave_position = %u, index = 0x%04X," + " data = 0x%p, data_size = %zu, abort_code = 0x%p)\n", + __func__, master, slave_position, index, data, data_size, + abort_code); + + if (!data_size) { + EC_MASTER_ERR(master, "Zero data size!\n"); + return -EINVAL; + } + + ec_sdo_request_init(&request); + ecrt_sdo_request_index(&request, index, 0); + ret = ec_sdo_request_alloc(&request, data_size); + if (ret) { + ec_sdo_request_clear(&request); + return ret; + } + + request.complete_access = 1; + memcpy(request.data, data, data_size); + request.data_size = data_size; + ecrt_sdo_request_write(&request); + + if (down_interruptible(&master->master_sem)) { + ec_sdo_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", slave_position); + ec_sdo_request_clear(&request); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling SDO download request" + " (complete access).\n"); + + // schedule request. + list_add_tail(&request.list, &slave->sdo_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + list_del(&request.list); + up(&master->master_sem); + ec_sdo_request_clear(&request); + return -EINTR; + } + // request already processing: interrupt not possible. + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + *abort_code = request.abort_code; + + if (request.state == EC_INT_REQUEST_SUCCESS) { + ret = 0; + } else if (request.errno) { + ret = -request.errno; + } else { + ret = -EIO; + } + + ec_sdo_request_clear(&request); + return ret; +} + +/*****************************************************************************/ + +int ecrt_master_sdo_upload(ec_master_t *master, uint16_t slave_position, + uint16_t index, uint8_t subindex, uint8_t *target, + size_t target_size, size_t *result_size, uint32_t *abort_code) +{ + ec_sdo_request_t request; + ec_slave_t *slave; + int ret = 0; + + EC_MASTER_DBG(master, 1, "%s(master = 0x%p," + " slave_position = %u, index = 0x%04X, subindex = 0x%02X," + " target = 0x%p, target_size = %zu, result_size = 0x%p," + " abort_code = 0x%p)\n", + __func__, master, slave_position, index, subindex, + target, target_size, result_size, abort_code); + + ec_sdo_request_init(&request); + ecrt_sdo_request_index(&request, index, subindex); + ecrt_sdo_request_read(&request); + + if (down_interruptible(&master->master_sem)) { + ec_sdo_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, slave_position))) { + up(&master->master_sem); + ec_sdo_request_clear(&request); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", slave_position); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling SDO upload request.\n"); + + // schedule request. + list_add_tail(&request.list, &slave->sdo_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + list_del(&request.list); + up(&master->master_sem); + ec_sdo_request_clear(&request); + return -EINTR; + } + // request already processing: interrupt not possible. + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + *abort_code = request.abort_code; + + if (request.state != EC_INT_REQUEST_SUCCESS) { + *result_size = 0; + if (request.errno) { + ret = -request.errno; + } else { + ret = -EIO; + } + } else { + if (request.data_size > target_size) { + EC_SLAVE_ERR(slave, "%s(): Buffer too small.\n", __func__); + ret = -EOVERFLOW; + } + else { + memcpy(target, request.data, request.data_size); + *result_size = request.data_size; + ret = 0; + } + } + + ec_sdo_request_clear(&request); + return ret; +} + +/*****************************************************************************/ + +int ecrt_master_write_idn(ec_master_t *master, uint16_t slave_position, + uint8_t drive_no, uint16_t idn, uint8_t *data, size_t data_size, + uint16_t *error_code) +{ + ec_soe_request_t request; + ec_slave_t *slave; + int ret; + + if (drive_no > 7) { + EC_MASTER_ERR(master, "Invalid drive number!\n"); + return -EINVAL; + } + + ec_soe_request_init(&request); + ec_soe_request_set_drive_no(&request, drive_no); + ec_soe_request_set_idn(&request, idn); + + ret = ec_soe_request_alloc(&request, data_size); + if (ret) { + ec_soe_request_clear(&request); + return ret; + } + + memcpy(request.data, data, data_size); + request.data_size = data_size; + ec_soe_request_write(&request); + + if (down_interruptible(&master->master_sem)) { + ec_soe_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + slave_position); + ec_soe_request_clear(&request); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling SoE write request.\n"); + + // schedule SoE write request. + list_add_tail(&request.list, &slave->soe_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + ec_soe_request_clear(&request); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + if (error_code) { + *error_code = request.error_code; + } + ret = request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; + ec_soe_request_clear(&request); + + return ret; +} + +/*****************************************************************************/ + +int ecrt_master_read_idn(ec_master_t *master, uint16_t slave_position, + uint8_t drive_no, uint16_t idn, uint8_t *target, size_t target_size, + size_t *result_size, uint16_t *error_code) +{ + ec_soe_request_t request; + ec_slave_t *slave; + int ret; + + if (drive_no > 7) { + EC_MASTER_ERR(master, "Invalid drive number!\n"); + return -EINVAL; + } + + ec_soe_request_init(&request); + ec_soe_request_set_drive_no(&request, drive_no); + ec_soe_request_set_idn(&request, idn); + ec_soe_request_read(&request); + + if (down_interruptible(&master->master_sem)) { + ec_soe_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, slave_position))) { + up(&master->master_sem); + ec_soe_request_clear(&request); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", slave_position); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling SoE read request.\n"); + + // schedule request. + list_add_tail(&request.list, &slave->soe_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + list_del(&request.list); + up(&master->master_sem); + ec_soe_request_clear(&request); + return -EINTR; + } + // request already processing: interrupt not possible. + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + if (error_code) { + *error_code = request.error_code; + } + + if (request.state != EC_INT_REQUEST_SUCCESS) { + if (result_size) { + *result_size = 0; + } + ret = -EIO; + } else { // success + if (request.data_size > target_size) { + EC_SLAVE_ERR(slave, "%s(): Buffer too small.\n", __func__); + ret = -EOVERFLOW; + } + else { // data fits in buffer + if (result_size) { + *result_size = request.data_size; + } + memcpy(target, request.data, request.data_size); + ret = 0; + } + } + + ec_soe_request_clear(&request); + return ret; +} + +/*****************************************************************************/ + +void ecrt_master_reset(ec_master_t *master) +{ + ec_slave_config_t *sc; + + list_for_each_entry(sc, &master->configs, list) { + if (sc->slave) { + ec_slave_request_state(sc->slave, EC_SLAVE_STATE_OP); + } + } +} + +/*****************************************************************************/ + +/** \cond */ + +EXPORT_SYMBOL(ecrt_master_create_domain); +EXPORT_SYMBOL(ecrt_master_activate); +EXPORT_SYMBOL(ecrt_master_deactivate); +EXPORT_SYMBOL(ecrt_master_send); +EXPORT_SYMBOL(ecrt_master_send_ext); +EXPORT_SYMBOL(ecrt_master_receive); +EXPORT_SYMBOL(ecrt_master_callbacks); +EXPORT_SYMBOL(ecrt_master); +EXPORT_SYMBOL(ecrt_master_get_slave); +EXPORT_SYMBOL(ecrt_master_slave_config); +EXPORT_SYMBOL(ecrt_master_select_reference_clock); +EXPORT_SYMBOL(ecrt_master_state); +EXPORT_SYMBOL(ecrt_master_link_state); +EXPORT_SYMBOL(ecrt_master_application_time); +EXPORT_SYMBOL(ecrt_master_sync_reference_clock); +EXPORT_SYMBOL(ecrt_master_sync_reference_clock_to); +EXPORT_SYMBOL(ecrt_master_sync_slave_clocks); +EXPORT_SYMBOL(ecrt_master_reference_clock_time); +EXPORT_SYMBOL(ecrt_master_sync_monitor_queue); +EXPORT_SYMBOL(ecrt_master_sync_monitor_process); +EXPORT_SYMBOL(ecrt_master_sdo_download); +EXPORT_SYMBOL(ecrt_master_sdo_download_complete); +EXPORT_SYMBOL(ecrt_master_sdo_upload); +EXPORT_SYMBOL(ecrt_master_write_idn); +EXPORT_SYMBOL(ecrt_master_read_idn); +EXPORT_SYMBOL(ecrt_master_reset); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/master/master.h b/net/ethercat/master/master.h new file mode 100644 index 000000000000..98e9fdf91cac --- /dev/null +++ b/net/ethercat/master/master.h @@ -0,0 +1,392 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT master structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_MASTER_H__ +#define __EC_MASTER_H__ + +#include <linux/version.h> +#include <linux/list.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/kthread.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27) +#include <linux/semaphore.h> +#else +#include <asm/semaphore.h> +#endif + +#include "device.h" +#include "domain.h" +#include "ethernet.h" +#include "fsm_master.h" +#include "cdev.h" + +#ifdef EC_RTDM +#include "rtdm.h" +#endif + +/*****************************************************************************/ + +/** Convenience macro for printing master-specific information to syslog. + * + * This will print the message in \a fmt with a prefixed "EtherCAT <INDEX>: ", + * where INDEX is the master index. + * + * \param master EtherCAT master + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_MASTER_INFO(master, fmt, args...) \ + printk(KERN_INFO "EtherCAT %u: " fmt, master->index, ##args) + +/** Convenience macro for printing master-specific errors to syslog. + * + * This will print the message in \a fmt with a prefixed "EtherCAT <INDEX>: ", + * where INDEX is the master index. + * + * \param master EtherCAT master + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_MASTER_ERR(master, fmt, args...) \ + printk(KERN_ERR "EtherCAT ERROR %u: " fmt, master->index, ##args) + +/** Convenience macro for printing master-specific warnings to syslog. + * + * This will print the message in \a fmt with a prefixed "EtherCAT <INDEX>: ", + * where INDEX is the master index. + * + * \param master EtherCAT master + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_MASTER_WARN(master, fmt, args...) \ + printk(KERN_WARNING "EtherCAT WARNING %u: " fmt, master->index, ##args) + +/** Convenience macro for printing master-specific debug messages to syslog. + * + * This will print the message in \a fmt with a prefixed "EtherCAT <INDEX>: ", + * where INDEX is the master index. + * + * \param master EtherCAT master + * \param level Debug level. Master's debug level must be >= \a level for + * output. + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_MASTER_DBG(master, level, fmt, args...) \ + do { \ + if (master->debug_level >= level) { \ + printk(KERN_DEBUG "EtherCAT DEBUG %u: " fmt, \ + master->index, ##args); \ + } \ + } while (0) + + +/** Size of the external datagram ring. + * + * The external datagram ring is used for slave FSMs. + */ +#define EC_EXT_RING_SIZE 32 + +/*****************************************************************************/ + +/** EtherCAT master phase. + */ +typedef enum { + EC_ORPHANED, /**< Orphaned phase. The master has no Ethernet device + attached. */ + EC_IDLE, /**< Idle phase. An Ethernet device is attached, but the master + is not in use, yet. */ + EC_OPERATION /**< Operation phase. The master was requested by a realtime + application. */ +} ec_master_phase_t; + +/*****************************************************************************/ + +/** Cyclic statistics. + */ +typedef struct { + unsigned int timeouts; /**< datagram timeouts */ + unsigned int corrupted; /**< corrupted frames */ + unsigned int unmatched; /**< unmatched datagrams (received, but not + queued any longer) */ + unsigned long output_jiffies; /**< time of last output */ +} ec_stats_t; + +/*****************************************************************************/ + +/** Device statistics. + */ +typedef struct { + u64 tx_count; /**< Number of frames sent. */ + u64 last_tx_count; /**< Number of frames sent of last statistics cycle. */ + u64 rx_count; /**< Number of frames received. */ + u64 last_rx_count; /**< Number of frames received of last statistics + cycle. */ + u64 tx_bytes; /**< Number of bytes sent. */ + u64 last_tx_bytes; /**< Number of bytes sent of last statistics cycle. */ + u64 rx_bytes; /**< Number of bytes received. */ + u64 last_rx_bytes; /**< Number of bytes received of last statistics cycle. + */ + u64 last_loss; /**< Tx/Rx difference of last statistics cycle. */ + s32 tx_frame_rates[EC_RATE_COUNT]; /**< Transmit rates in frames/s for + different statistics cycle periods. + */ + s32 rx_frame_rates[EC_RATE_COUNT]; /**< Receive rates in frames/s for + different statistics cycle periods. + */ + s32 tx_byte_rates[EC_RATE_COUNT]; /**< Transmit rates in byte/s for + different statistics cycle periods. */ + s32 rx_byte_rates[EC_RATE_COUNT]; /**< Receive rates in byte/s for + different statistics cycle periods. */ + s32 loss_rates[EC_RATE_COUNT]; /**< Frame loss rates for different + statistics cycle periods. */ + unsigned long jiffies; /**< Jiffies of last statistic cycle. */ +} ec_device_stats_t; + +/*****************************************************************************/ + +#if EC_MAX_NUM_DEVICES < 1 +#error Invalid number of devices +#endif + +/*****************************************************************************/ + +/** EtherCAT master. + * + * Manages slaves, domains and IO. + */ +struct ec_master { + unsigned int index; /**< Index. */ + unsigned int reserved; /**< \a True, if the master is in use. */ + + ec_cdev_t cdev; /**< Master character device. */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) + struct device *class_device; /**< Master class device. */ +#else + struct class_device *class_device; /**< Master class device. */ +#endif + +#ifdef EC_RTDM + ec_rtdm_dev_t rtdm_dev; /**< RTDM device. */ +#endif + + struct semaphore master_sem; /**< Master semaphore. */ + + ec_device_t devices[EC_MAX_NUM_DEVICES]; /**< EtherCAT devices. */ + const uint8_t *macs[EC_MAX_NUM_DEVICES]; /**< Device MAC addresses. */ +#if EC_MAX_NUM_DEVICES > 1 + unsigned int num_devices; /**< Number of devices. Access this always via + ec_master_num_devices(), because it may be + optimized! */ +#endif + struct semaphore device_sem; /**< Device semaphore. */ + ec_device_stats_t device_stats; /**< Device statistics. */ + + ec_fsm_master_t fsm; /**< Master state machine. */ + ec_datagram_t fsm_datagram; /**< Datagram used for state machines. */ + ec_master_phase_t phase; /**< Master phase. */ + unsigned int active; /**< Master has been activated. */ + unsigned int config_changed; /**< The configuration changed. */ + unsigned int injection_seq_fsm; /**< Datagram injection sequence number + for the FSM side. */ + unsigned int injection_seq_rt; /**< Datagram injection sequence number + for the realtime side. */ + + ec_slave_t *slaves; /**< Array of slaves on the bus. */ + unsigned int slave_count; /**< Number of slaves on the bus. */ + + /* Configuration applied by the application. */ + struct list_head configs; /**< List of slave configurations. */ + struct list_head domains; /**< List of domains. */ + + u64 app_time; /**< Time of the last ecrt_master_sync() call. */ + u64 dc_ref_time; /**< Common reference timestamp for DC start times. */ + ec_datagram_t ref_sync_datagram; /**< Datagram used for synchronizing the + reference clock to the master clock. */ + ec_datagram_t sync_datagram; /**< Datagram used for DC drift + compensation. */ + ec_datagram_t sync_mon_datagram; /**< Datagram used for DC synchronisation + monitoring. */ + ec_slave_config_t *dc_ref_config; /**< Application-selected DC reference + clock slave config. */ + ec_slave_t *dc_ref_clock; /**< DC reference clock slave. */ + + unsigned int scan_busy; /**< Current scan state. */ + unsigned int allow_scan; /**< \a True, if slave scanning is allowed. */ + struct semaphore scan_sem; /**< Semaphore protecting the \a scan_busy + variable and the \a allow_scan flag. */ + wait_queue_head_t scan_queue; /**< Queue for processes that wait for + slave scanning. */ + + unsigned int config_busy; /**< State of slave configuration. */ + struct semaphore config_sem; /**< Semaphore protecting the \a config_busy + variable and the allow_config flag. */ + wait_queue_head_t config_queue; /**< Queue for processes that wait for + slave configuration. */ + + struct list_head datagram_queue; /**< Datagram queue. */ + uint8_t datagram_index; /**< Current datagram index. */ + + struct list_head ext_datagram_queue; /**< Queue for non-application + datagrams. */ + struct semaphore ext_queue_sem; /**< Semaphore protecting the \a + ext_datagram_queue. */ + + ec_datagram_t ext_datagram_ring[EC_EXT_RING_SIZE]; /**< External datagram + ring. */ + unsigned int ext_ring_idx_rt; /**< Index in external datagram ring for RT + side. */ + unsigned int ext_ring_idx_fsm; /**< Index in external datagram ring for + FSM side. */ + unsigned int send_interval; /**< Interval between two calls to + ecrt_master_send(). */ + size_t max_queue_size; /**< Maximum size of datagram queue */ + + ec_slave_t *fsm_slave; /**< Slave that is queried next for FSM exec. */ + struct list_head fsm_exec_list; /**< Slave FSM execution list. */ + unsigned int fsm_exec_count; /**< Number of entries in execution list. */ + + unsigned int debug_level; /**< Master debug level. */ + ec_stats_t stats; /**< Cyclic statistics. */ + + struct task_struct *thread; /**< Master thread. */ + +#ifdef EC_EOE + struct task_struct *eoe_thread; /**< EoE thread. */ + struct list_head eoe_handlers; /**< Ethernet over EtherCAT handlers. */ +#endif + + struct semaphore io_sem; /**< Semaphore used in \a IDLE phase. */ + + void (*send_cb)(void *); /**< Current send datagrams callback. */ + void (*receive_cb)(void *); /**< Current receive datagrams callback. */ + void *cb_data; /**< Current callback data. */ + void (*app_send_cb)(void *); /**< Application's send datagrams + callback. */ + void (*app_receive_cb)(void *); /**< Application's receive datagrams + callback. */ + void *app_cb_data; /**< Application callback data. */ + + struct list_head sii_requests; /**< SII write requests. */ + struct list_head emerg_reg_requests; /**< Emergency register access + requests. */ + + wait_queue_head_t request_queue; /**< Wait queue for external requests + from user space. */ +}; + +/*****************************************************************************/ + +// static funtions +void ec_master_init_static(void); + +// master creation/deletion +int ec_master_init(ec_master_t *, unsigned int, const uint8_t *, + const uint8_t *, dev_t, struct class *, unsigned int); +void ec_master_clear(ec_master_t *); + +/** Number of Ethernet devices. + */ +#if EC_MAX_NUM_DEVICES > 1 +#define ec_master_num_devices(MASTER) ((MASTER)->num_devices) +#else +#define ec_master_num_devices(MASTER) 1 +#endif + +// phase transitions +int ec_master_enter_idle_phase(ec_master_t *); +void ec_master_leave_idle_phase(ec_master_t *); +int ec_master_enter_operation_phase(ec_master_t *); +void ec_master_leave_operation_phase(ec_master_t *); + +#ifdef EC_EOE +// EoE +void ec_master_eoe_start(ec_master_t *); +void ec_master_eoe_stop(ec_master_t *); +#endif + +// datagram IO +void ec_master_receive_datagrams(ec_master_t *, ec_device_t *, + const uint8_t *, size_t); +void ec_master_queue_datagram(ec_master_t *, ec_datagram_t *); +void ec_master_queue_datagram_ext(ec_master_t *, ec_datagram_t *); + +// misc. +void ec_master_set_send_interval(ec_master_t *, unsigned int); +void ec_master_attach_slave_configs(ec_master_t *); +ec_slave_t *ec_master_find_slave(ec_master_t *, uint16_t, uint16_t); +const ec_slave_t *ec_master_find_slave_const(const ec_master_t *, uint16_t, + uint16_t); +void ec_master_output_stats(ec_master_t *); +#ifdef EC_EOE +void ec_master_clear_eoe_handlers(ec_master_t *); +#endif +void ec_master_clear_slaves(ec_master_t *); + +unsigned int ec_master_config_count(const ec_master_t *); +ec_slave_config_t *ec_master_get_config( + const ec_master_t *, unsigned int); +const ec_slave_config_t *ec_master_get_config_const( + const ec_master_t *, unsigned int); +unsigned int ec_master_domain_count(const ec_master_t *); +ec_domain_t *ec_master_find_domain(ec_master_t *, unsigned int); +const ec_domain_t *ec_master_find_domain_const(const ec_master_t *, + unsigned int); +#ifdef EC_EOE +uint16_t ec_master_eoe_handler_count(const ec_master_t *); +const ec_eoe_t *ec_master_get_eoe_handler_const(const ec_master_t *, uint16_t); +#endif + +int ec_master_debug_level(ec_master_t *, unsigned int); + +ec_domain_t *ecrt_master_create_domain_err(ec_master_t *); +ec_slave_config_t *ecrt_master_slave_config_err(ec_master_t *, uint16_t, + uint16_t, uint32_t, uint32_t); + +void ec_master_calc_dc(ec_master_t *); +void ec_master_request_op(ec_master_t *); + +void ec_master_internal_send_cb(void *); +void ec_master_internal_receive_cb(void *); + +extern const unsigned int rate_intervals[EC_RATE_COUNT]; // see master.c + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/module.c b/net/ethercat/master/module.c new file mode 100644 index 000000000000..dfbad367dd88 --- /dev/null +++ b/net/ethercat/master/module.c @@ -0,0 +1,675 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT master driver module. + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/err.h> + +#include "globals.h" +#include "master.h" +#include "device.h" + +/*****************************************************************************/ + +#define MAX_MASTERS 32 /**< Maximum number of masters. */ + +/*****************************************************************************/ + +int __init ec_init_module(void); +void __exit ec_cleanup_module(void); + +static int ec_mac_parse(uint8_t *, const char *, int); + +/*****************************************************************************/ + +static char *main_devices[MAX_MASTERS] = {"6c:cf:39:00:27:47", }; /**< Main devices parameter. */ +static unsigned int master_count = 1; /**< Number of masters. */ +static char *backup_devices[MAX_MASTERS]; /**< Backup devices parameter. */ +static unsigned int backup_count; /**< Number of backup devices. */ +static unsigned int debug_level = 0; /**< Debug level parameter. */ + +static ec_master_t *masters; /**< Array of masters. */ +static struct semaphore master_sem; /**< Master semaphore. */ + +dev_t device_number; /**< Device number for master cdevs. */ +struct class *class; /**< Device class. */ + +static uint8_t macs[MAX_MASTERS][2][ETH_ALEN]; /**< MAC addresses. */ + +char *ec_master_version_str = EC_MASTER_VERSION; /**< Version string. */ + +/*****************************************************************************/ + +/** \cond */ + +MODULE_AUTHOR("Florian Pose <fp@igh-essen.com>"); +MODULE_DESCRIPTION("EtherCAT master driver module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(EC_MASTER_VERSION); + +module_param_array(main_devices, charp, &master_count, S_IRUGO); +MODULE_PARM_DESC(main_devices, "MAC addresses of main devices"); +module_param_array(backup_devices, charp, &backup_count, S_IRUGO); +MODULE_PARM_DESC(backup_devices, "MAC addresses of backup devices"); +module_param_named(debug_level, debug_level, uint, S_IRUGO); +MODULE_PARM_DESC(debug_level, "Debug level"); + +/** \endcond */ + +/*****************************************************************************/ + +/** Module initialization. + * + * Initializes \a master_count masters. + * \return 0 on success, else < 0 + */ +int __init ec_init_module(void) +{ + int i, ret = 0; + + EC_INFO("Master driver %s\n", EC_MASTER_VERSION); + + sema_init(&master_sem, 1); + + if (master_count) { + if (alloc_chrdev_region(&device_number, + 0, master_count, "EtherCAT")) { + EC_ERR("Failed to obtain device number(s)!\n"); + ret = -EBUSY; + goto out_return; + } + } + + class = class_create(THIS_MODULE, "EtherCAT"); + if (IS_ERR(class)) { + EC_ERR("Failed to create device class.\n"); + ret = PTR_ERR(class); + goto out_cdev; + } + + // zero MAC addresses + memset(macs, 0x00, sizeof(uint8_t) * MAX_MASTERS * 2 * ETH_ALEN); + + // process MAC parameters + for (i = 0; i < master_count; i++) { + ret = ec_mac_parse(macs[i][0], main_devices[i], 0); + if (ret) + goto out_class; + + if (i < backup_count) { + ret = ec_mac_parse(macs[i][1], backup_devices[i], 1); + if (ret) + goto out_class; + } + } + EC_INFO("emaster init\n"); + + // initialize static master variables + ec_master_init_static(); + + if (master_count) { + if (!(masters = kmalloc(sizeof(ec_master_t) * master_count, + GFP_KERNEL))) { + EC_ERR("Failed to allocate memory" + " for EtherCAT masters.\n"); + ret = -ENOMEM; + goto out_class; + } + } + + for (i = 0; i < master_count; i++) { + ret = ec_master_init(&masters[i], i, macs[i][0], macs[i][1], + device_number, class, debug_level); + if (ret) + goto out_free_masters; + } + + EC_INFO("%u master%s waiting for devices.\n", + master_count, (master_count == 1 ? "" : "s")); + return ret; + +out_free_masters: + for (i--; i >= 0; i--) + ec_master_clear(&masters[i]); + kfree(masters); +out_class: + class_destroy(class); +out_cdev: + if (master_count) + unregister_chrdev_region(device_number, master_count); +out_return: + return ret; +} + +/*****************************************************************************/ + +/** Module cleanup. + * + * Clears all master instances. + */ +void __exit ec_cleanup_module(void) +{ + unsigned int i; + + for (i = 0; i < master_count; i++) { + ec_master_clear(&masters[i]); + } + + if (master_count) + kfree(masters); + + class_destroy(class); + + if (master_count) + unregister_chrdev_region(device_number, master_count); + + EC_INFO("Master module cleaned up.\n"); +} + +/*****************************************************************************/ + +/** Get the number of masters. + */ +unsigned int ec_master_count(void) +{ + return master_count; +} + +/***************************************************************************** + * MAC address functions + ****************************************************************************/ + +/** + * \return true, if two MAC addresses are equal. + */ +int ec_mac_equal( + const uint8_t *mac1, /**< First MAC address. */ + const uint8_t *mac2 /**< Second MAC address. */ + ) +{ + unsigned int i; + + for (i = 0; i < ETH_ALEN; i++) + if (mac1[i] != mac2[i]) + return 0; + + return 1; +} + +/*****************************************************************************/ + +/** Maximum MAC string size. + */ +#define EC_MAX_MAC_STRING_SIZE (3 * ETH_ALEN) + +/** Print a MAC address to a buffer. + * + * The buffer size must be at least EC_MAX_MAC_STRING_SIZE. + * + * \return number of bytes written. + */ +ssize_t ec_mac_print( + const uint8_t *mac, /**< MAC address */ + char *buffer /**< Target buffer. */ + ) +{ + off_t off = 0; + unsigned int i; + + for (i = 0; i < ETH_ALEN; i++) { + off += sprintf(buffer + off, "%02X", mac[i]); + if (i < ETH_ALEN - 1) off += sprintf(buffer + off, ":"); + } + + return off; +} + +/*****************************************************************************/ + +/** + * \return true, if the MAC address is all-zero. + */ +int ec_mac_is_zero( + const uint8_t *mac /**< MAC address. */ + ) +{ + unsigned int i; + + for (i = 0; i < ETH_ALEN; i++) + if (mac[i]) + return 0; + + return 1; +} + +/*****************************************************************************/ + +/** + * \return true, if the given MAC address is the broadcast address. + */ +int ec_mac_is_broadcast( + const uint8_t *mac /**< MAC address. */ + ) +{ + unsigned int i; + + for (i = 0; i < ETH_ALEN; i++) + if (mac[i] != 0xff) + return 0; + + return 1; +} + +/*****************************************************************************/ + +/** Parse a MAC address from a string. + * + * The MAC address must match the regular expression + * "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}". + * + * \return 0 on success, else < 0 + */ +static int ec_mac_parse(uint8_t *mac, const char *src, int allow_empty) +{ + unsigned int i, value; + const char *orig = src; + char *rem; + + if (!strlen(src)) { + if (allow_empty){ + return 0; + } else { + EC_ERR("MAC address may not be empty.\n"); + return -EINVAL; + } + } + + for (i = 0; i < ETH_ALEN; i++) { + value = simple_strtoul(src, &rem, 16); + if (rem != src + 2 + || value > 0xFF + || (i < ETH_ALEN - 1 && *rem != ':')) { + EC_ERR("Invalid MAC address \"%s\".\n", orig); + return -EINVAL; + } + mac[i] = value; + if (i < ETH_ALEN - 1) { + src = rem + 1; // skip colon + } + } + + return 0; +} + +/*****************************************************************************/ + +/** Outputs frame contents for debugging purposes. + * If the data block is larger than 256 bytes, only the first 128 + * and the last 128 bytes will be shown + */ +void ec_print_data(const uint8_t *data, /**< pointer to data */ + size_t size /**< number of bytes to output */ + ) +{ + unsigned int i; + + EC_DBG(""); + for (i = 0; i < size; i++) { + printk("%02X ", data[i]); + + if ((i + 1) % 16 == 0 && i < size - 1) { + printk("\n"); + EC_DBG(""); + } + + if (i + 1 == 128 && size > 256) { + printk("dropped %zu bytes\n", size - 128 - i); + i = size - 128; + EC_DBG(""); + } + } + printk("\n"); +} + +/*****************************************************************************/ + +/** Outputs frame contents and differences for debugging purposes. + */ +void ec_print_data_diff(const uint8_t *d1, /**< first data */ + const uint8_t *d2, /**< second data */ + size_t size /** number of bytes to output */ + ) +{ + unsigned int i; + + EC_DBG(""); + for (i = 0; i < size; i++) { + if (d1[i] == d2[i]) printk(".. "); + else printk("%02X ", d2[i]); + if ((i + 1) % 16 == 0) { + printk("\n"); + EC_DBG(""); + } + } + printk("\n"); +} + +/*****************************************************************************/ + +/** Prints slave states in clear text. + * + * \return Size of the created string. + */ +size_t ec_state_string(uint8_t states, /**< slave states */ + char *buffer, /**< target buffer + (min. EC_STATE_STRING_SIZE bytes) */ + uint8_t multi /**< Show multi-state mask. */ + ) +{ + off_t off = 0; + unsigned int first = 1; + + if (!states) { + off += sprintf(buffer + off, "(unknown)"); + return off; + } + + if (multi) { // multiple slaves + if (states & EC_SLAVE_STATE_INIT) { + off += sprintf(buffer + off, "INIT"); + first = 0; + } + if (states & EC_SLAVE_STATE_PREOP) { + if (!first) off += sprintf(buffer + off, ", "); + off += sprintf(buffer + off, "PREOP"); + first = 0; + } + if (states & EC_SLAVE_STATE_SAFEOP) { + if (!first) off += sprintf(buffer + off, ", "); + off += sprintf(buffer + off, "SAFEOP"); + first = 0; + } + if (states & EC_SLAVE_STATE_OP) { + if (!first) off += sprintf(buffer + off, ", "); + off += sprintf(buffer + off, "OP"); + } + } else { // single slave + if ((states & EC_SLAVE_STATE_MASK) == EC_SLAVE_STATE_INIT) { + off += sprintf(buffer + off, "INIT"); + } else if ((states & EC_SLAVE_STATE_MASK) == EC_SLAVE_STATE_PREOP) { + off += sprintf(buffer + off, "PREOP"); + } else if ((states & EC_SLAVE_STATE_MASK) == EC_SLAVE_STATE_BOOT) { + off += sprintf(buffer + off, "BOOT"); + } else if ((states & EC_SLAVE_STATE_MASK) == EC_SLAVE_STATE_SAFEOP) { + off += sprintf(buffer + off, "SAFEOP"); + } else if ((states & EC_SLAVE_STATE_MASK) == EC_SLAVE_STATE_OP) { + off += sprintf(buffer + off, "OP"); + } else { + off += sprintf(buffer + off, "(invalid)"); + } + first = 0; + } + + if (states & EC_SLAVE_STATE_ACK_ERR) { + if (!first) off += sprintf(buffer + off, " + "); + off += sprintf(buffer + off, "ERROR"); + } + + return off; +} + +/****************************************************************************** + * Device interface + *****************************************************************************/ + +/** Device names. + */ +const char *ec_device_names[2] = { + "main", + "backup" +}; + +/** Offers an EtherCAT device to a certain master. + * + * The master decides, if it wants to use the device for EtherCAT operation, + * or not. It is important, that the offered net_device is not used by the + * kernel IP stack. If the master, accepted the offer, the address of the + * newly created EtherCAT device is returned, else \a NULL is returned. + * + * \return Pointer to device, if accepted, or NULL if declined. + * \ingroup DeviceInterface + */ +ec_device_t *ecdev_offer( + struct net_device *net_dev, /**< net_device to offer */ + ec_pollfunc_t poll, /**< device poll function */ + struct module *module /**< pointer to the module */ + ) +{ + ec_master_t *master; + char str[EC_MAX_MAC_STRING_SIZE]; + unsigned int i, dev_idx; + + for (i = 0; i < master_count; i++) { + master = &masters[i]; + ec_mac_print(net_dev->dev_addr, str); + + if (down_interruptible(&master->device_sem)) { + EC_MASTER_WARN(master, "%s() interrupted!\n", __func__); + return NULL; + } + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(master); dev_idx++) { + if (!master->devices[dev_idx].dev + && (ec_mac_equal(master->macs[dev_idx], net_dev->dev_addr) + || ec_mac_is_broadcast(master->macs[dev_idx]))) { + + EC_INFO("Accepting %s as %s device for master %u.\n", + str, ec_device_names[dev_idx != 0], master->index); + + ec_device_attach(&master->devices[dev_idx], + net_dev, poll, module); + up(&master->device_sem); + + snprintf(net_dev->name, IFNAMSIZ, "ec%c%u", + ec_device_names[dev_idx != 0][0], master->index); + + return &master->devices[dev_idx]; // offer accepted + } + } + + up(&master->device_sem); + + EC_MASTER_DBG(master, 1, "Master declined device %s.\n", str); + } + + return NULL; // offer declined +} + +/****************************************************************************** + * Application interface + *****************************************************************************/ + +/** Request a master. + * + * Same as ecrt_request_master(), but with ERR_PTR() return value. + * + * \return Requested master. + */ +ec_master_t *ecrt_request_master_err( + unsigned int master_index /**< Master index. */ + ) +{ + ec_master_t *master, *errptr = NULL; + unsigned int dev_idx = EC_DEVICE_MAIN; + + EC_INFO("Requesting master %u...\n", master_index); + + if (master_index >= master_count) { + EC_ERR("Invalid master index %u.\n", master_index); + errptr = ERR_PTR(-EINVAL); + goto out_return; + } + master = &masters[master_index]; + + if (down_interruptible(&master_sem)) { + errptr = ERR_PTR(-EINTR); + goto out_return; + } + + if (master->reserved) { + up(&master_sem); + EC_MASTER_ERR(master, "Master already in use!\n"); + errptr = ERR_PTR(-EBUSY); + goto out_return; + } + master->reserved = 1; + up(&master_sem); + + if (down_interruptible(&master->device_sem)) { + errptr = ERR_PTR(-EINTR); + goto out_release; + } + + if (master->phase != EC_IDLE) { + up(&master->device_sem); + EC_MASTER_ERR(master, "Master still waiting for devices!\n"); + errptr = ERR_PTR(-ENODEV); + goto out_release; + } + + for (; dev_idx < ec_master_num_devices(master); dev_idx++) { + ec_device_t *device = &master->devices[dev_idx]; + if (!try_module_get(device->module)) { + up(&master->device_sem); + EC_MASTER_ERR(master, "Device module is unloading!\n"); + errptr = ERR_PTR(-ENODEV); + goto out_module_put; + } + } + + up(&master->device_sem); + + if (ec_master_enter_operation_phase(master)) { + EC_MASTER_ERR(master, "Failed to enter OPERATION phase!\n"); + errptr = ERR_PTR(-EIO); + goto out_module_put; + } + + EC_INFO("Successfully requested master %u.\n", master_index); + return master; + + out_module_put: + for (; dev_idx > 0; dev_idx--) { + ec_device_t *device = &master->devices[dev_idx - 1]; + module_put(device->module); + } + out_release: + master->reserved = 0; + out_return: + return errptr; +} + +/*****************************************************************************/ + +ec_master_t *ecrt_request_master(unsigned int master_index) +{ + ec_master_t *master = ecrt_request_master_err(master_index); + return IS_ERR(master) ? NULL : master; +} + +/*****************************************************************************/ + +void ecrt_release_master(ec_master_t *master) +{ + unsigned int dev_idx; + + EC_MASTER_INFO(master, "Releasing master...\n"); + + if (!master->reserved) { + EC_MASTER_WARN(master, "%s(): Master was was not requested!\n", + __func__); + return; + } + + ec_master_leave_operation_phase(master); + + for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); + dev_idx++) { + module_put(master->devices[dev_idx].module); + } + + master->reserved = 0; + + EC_MASTER_INFO(master, "Released.\n"); +} + +/*****************************************************************************/ + +unsigned int ecrt_version_magic(void) +{ + return ECRT_VERSION_MAGIC; +} + +/*****************************************************************************/ + +/** Global request state type translation table. + * + * Translates an internal request state to an external one. + */ +const ec_request_state_t ec_request_state_translation_table[] = { + EC_REQUEST_UNUSED, // EC_INT_REQUEST_INIT, + EC_REQUEST_BUSY, // EC_INT_REQUEST_QUEUED, + EC_REQUEST_BUSY, // EC_INT_REQUEST_BUSY, + EC_REQUEST_SUCCESS, // EC_INT_REQUEST_SUCCESS, + EC_REQUEST_ERROR // EC_INT_REQUEST_FAILURE +}; + +/*****************************************************************************/ + +/** \cond */ + +module_init(ec_init_module); +module_exit(ec_cleanup_module); + +EXPORT_SYMBOL(ecdev_offer); + +EXPORT_SYMBOL(ecrt_request_master); +EXPORT_SYMBOL(ecrt_release_master); +EXPORT_SYMBOL(ecrt_version_magic); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/master/pdo.c b/net/ethercat/master/pdo.c new file mode 100644 index 000000000000..1597835cc48b --- /dev/null +++ b/net/ethercat/master/pdo.c @@ -0,0 +1,317 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT process data object methods. +*/ + +/*****************************************************************************/ + +#include <linux/slab.h> +#include <linux/err.h> + +#include "pdo.h" + +/*****************************************************************************/ + +/** PDO constructor. + */ +void ec_pdo_init( + ec_pdo_t *pdo /**< EtherCAT PDO */ + ) +{ + pdo->sync_index = -1; // not assigned + pdo->name = NULL; + INIT_LIST_HEAD(&pdo->entries); +} + +/*****************************************************************************/ + +/** PDO copy constructor. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_pdo_init_copy( + ec_pdo_t *pdo, /**< PDO to create. */ + const ec_pdo_t *other_pdo /**< PDO to copy from. */ + ) +{ + int ret = 0; + + pdo->index = other_pdo->index; + pdo->sync_index = other_pdo->sync_index; + pdo->name = NULL; + INIT_LIST_HEAD(&pdo->entries); + + ret = ec_pdo_set_name(pdo, other_pdo->name); + if (ret < 0) + goto out_return; + + ret = ec_pdo_copy_entries(pdo, other_pdo); + if (ret < 0) + goto out_clear; + + return 0; + +out_clear: + ec_pdo_clear(pdo); +out_return: + return ret; +} + +/*****************************************************************************/ + +/** PDO destructor. + */ +void ec_pdo_clear(ec_pdo_t *pdo /**< EtherCAT PDO. */) +{ + if (pdo->name) + kfree(pdo->name); + + ec_pdo_clear_entries(pdo); +} + +/*****************************************************************************/ + +/** Clear PDO entry list. + */ +void ec_pdo_clear_entries(ec_pdo_t *pdo /**< EtherCAT PDO. */) +{ + ec_pdo_entry_t *entry, *next; + + // free all PDO entries + list_for_each_entry_safe(entry, next, &pdo->entries, list) { + list_del(&entry->list); + ec_pdo_entry_clear(entry); + kfree(entry); + } +} + +/*****************************************************************************/ + +/** Set PDO name. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_pdo_set_name( + ec_pdo_t *pdo, /**< PDO. */ + const char *name /**< New name. */ + ) +{ + unsigned int len; + + if (pdo->name && name && !strcmp(pdo->name, name)) + return 0; + + if (pdo->name) + kfree(pdo->name); + + if (name && (len = strlen(name))) { + if (!(pdo->name = (char *) kmalloc(len + 1, GFP_KERNEL))) { + EC_ERR("Failed to allocate PDO name.\n"); + return -ENOMEM; + } + memcpy(pdo->name, name, len + 1); + } else { + pdo->name = NULL; + } + + return 0; +} + +/*****************************************************************************/ + +/** Add a new PDO entry to the configuration. + * + * \retval Pointer to the added entry, otherwise a ERR_PTR() code. + */ +ec_pdo_entry_t *ec_pdo_add_entry( + ec_pdo_t *pdo, /**< PDO. */ + uint16_t index, /**< New entry's index. */ + uint8_t subindex, /**< New entry's subindex. */ + uint8_t bit_length /**< New entry's bit length. */ + ) +{ + ec_pdo_entry_t *entry; + + if (!(entry = kmalloc(sizeof(ec_pdo_entry_t), GFP_KERNEL))) { + EC_ERR("Failed to allocate memory for PDO entry.\n"); + return ERR_PTR(-ENOMEM); + } + + ec_pdo_entry_init(entry); + entry->index = index; + entry->subindex = subindex; + entry->bit_length = bit_length; + list_add_tail(&entry->list, &pdo->entries); + return entry; +} + +/*****************************************************************************/ + +/** Copy PDO entries from another PDO. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_pdo_copy_entries( + ec_pdo_t *pdo, /**< PDO whos entries shall be replaced. */ + const ec_pdo_t *other /**< Pdo with entries to copy. */ + ) +{ + ec_pdo_entry_t *entry, *other_entry; + int ret; + + ec_pdo_clear_entries(pdo); + + list_for_each_entry(other_entry, &other->entries, list) { + if (!(entry = (ec_pdo_entry_t *) + kmalloc(sizeof(ec_pdo_entry_t), GFP_KERNEL))) { + EC_ERR("Failed to allocate memory for PDO entry copy.\n"); + return -ENOMEM; + } + + ret = ec_pdo_entry_init_copy(entry, other_entry); + if (ret < 0) { + kfree(entry); + return ret; + } + + list_add_tail(&entry->list, &pdo->entries); + } + + return 0; +} + +/*****************************************************************************/ + +/** Compares the entries of two PDOs. + * + * \retval 1 The entries of the given PDOs are equal. + * \retval 0 The entries of the given PDOs differ. + */ +int ec_pdo_equal_entries( + const ec_pdo_t *pdo1, /**< First PDO. */ + const ec_pdo_t *pdo2 /**< Second PDO. */ + ) +{ + const struct list_head *head1, *head2, *item1, *item2; + const ec_pdo_entry_t *entry1, *entry2; + + head1 = item1 = &pdo1->entries; + head2 = item2 = &pdo2->entries; + + while (1) { + item1 = item1->next; + item2 = item2->next; + + if ((item1 == head1) ^ (item2 == head2)) // unequal lengths + return 0; + if (item1 == head1) // both finished + break; + + entry1 = list_entry(item1, ec_pdo_entry_t, list); + entry2 = list_entry(item2, ec_pdo_entry_t, list); + if (!ec_pdo_entry_equal(entry1, entry2)) + return 0; + } + + return 1; +} + +/*****************************************************************************/ + +/** Get the number of PDO entries. + * + * \return Number of PDO entries. + */ +unsigned int ec_pdo_entry_count( + const ec_pdo_t *pdo /**< PDO. */ + ) +{ + const ec_pdo_entry_t *entry; + unsigned int num = 0; + + list_for_each_entry(entry, &pdo->entries, list) { + num++; + } + + return num; +} + +/*****************************************************************************/ + +/** Finds a PDO entry via its position in the list. + * + * Const version. + * + * \return Search result, or NULL. + */ +const ec_pdo_entry_t *ec_pdo_find_entry_by_pos_const( + const ec_pdo_t *pdo, /**< PDO. */ + unsigned int pos /**< Position in the list. */ + ) +{ + const ec_pdo_entry_t *entry; + + list_for_each_entry(entry, &pdo->entries, list) { + if (pos--) + continue; + return entry; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Outputs the PDOs in the list. + */ +void ec_pdo_print_entries( + const ec_pdo_t *pdo /**< PDO. */ + ) +{ + const ec_pdo_entry_t *entry; + + if (list_empty(&pdo->entries)) { + printk("(none)"); + } else { + list_for_each_entry(entry, &pdo->entries, list) { + printk("0x%04X:%02X/%u", + entry->index, entry->subindex, entry->bit_length); + if (entry->list.next != &pdo->entries) + printk(" "); + } + } +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/pdo.h b/net/ethercat/master/pdo.h new file mode 100644 index 000000000000..b84a1b7ae1a3 --- /dev/null +++ b/net/ethercat/master/pdo.h @@ -0,0 +1,75 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT Process data object structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_PDO_H__ +#define __EC_PDO_H__ + +#include <linux/list.h> + +#include "globals.h" +#include "pdo_entry.h" + +/*****************************************************************************/ + +/** PDO description. + */ +typedef struct { + struct list_head list; /**< List item. */ + uint16_t index; /**< PDO index. */ + int8_t sync_index; /**< Assigned sync manager. \todo remove? */ + char *name; /**< PDO name. */ + struct list_head entries; /**< List of PDO entries. */ +} ec_pdo_t; + +/*****************************************************************************/ + +void ec_pdo_init(ec_pdo_t *); +int ec_pdo_init_copy(ec_pdo_t *, const ec_pdo_t *); +void ec_pdo_clear(ec_pdo_t *); +void ec_pdo_clear_entries(ec_pdo_t *); +int ec_pdo_set_name(ec_pdo_t *, const char *); +ec_pdo_entry_t *ec_pdo_add_entry(ec_pdo_t *, uint16_t, uint8_t, uint8_t); +int ec_pdo_copy_entries(ec_pdo_t *, const ec_pdo_t *); +int ec_pdo_equal_entries(const ec_pdo_t *, const ec_pdo_t *); +unsigned int ec_pdo_entry_count(const ec_pdo_t *); +const ec_pdo_entry_t *ec_pdo_find_entry_by_pos_const( + const ec_pdo_t *, unsigned int); + +void ec_pdo_print_entries(const ec_pdo_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/pdo_entry.c b/net/ethercat/master/pdo_entry.c new file mode 100644 index 000000000000..036a75cb0b60 --- /dev/null +++ b/net/ethercat/master/pdo_entry.c @@ -0,0 +1,132 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT process data object entry methods. +*/ + +/*****************************************************************************/ + +#include <linux/slab.h> + +#include "pdo_entry.h" + +/*****************************************************************************/ + +/** PDO entry constructor. + */ +void ec_pdo_entry_init( + ec_pdo_entry_t *entry /**< PDO entry. */ + ) +{ + entry->name = NULL; +} + +/*****************************************************************************/ + +/** PDO entry copy constructor. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_pdo_entry_init_copy( + ec_pdo_entry_t *entry, /**< PDO entry. */ + const ec_pdo_entry_t *other /**< PDO entry to copy from. */ + ) +{ + entry->index = other->index; + entry->subindex = other->subindex; + entry->name = NULL; + entry->bit_length = other->bit_length; + + return ec_pdo_entry_set_name(entry, other->name); +} + +/*****************************************************************************/ + +/** PDO entry destructor. + */ +void ec_pdo_entry_clear(ec_pdo_entry_t *entry /**< PDO entry. */) +{ + if (entry->name) + kfree(entry->name); +} + +/*****************************************************************************/ + +/** Set PDO entry name. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_pdo_entry_set_name( + ec_pdo_entry_t *entry, /**< PDO entry. */ + const char *name /**< New name. */ + ) +{ + unsigned int len; + + if (entry->name && name && !strcmp(entry->name, name)) + return 0; + + if (entry->name) + kfree(entry->name); + + if (name && (len = strlen(name))) { + if (!(entry->name = (char *) kmalloc(len + 1, GFP_KERNEL))) { + EC_ERR("Failed to allocate PDO entry name.\n"); + return -ENOMEM; + } + memcpy(entry->name, name, len + 1); + } else { + entry->name = NULL; + } + + return 0; +} + +/*****************************************************************************/ + +/** Compares two PDO entries. + * + * \retval 1 The entries are equal. + * \retval 0 The entries differ. + */ +int ec_pdo_entry_equal( + const ec_pdo_entry_t *entry1, /**< First PDO entry. */ + const ec_pdo_entry_t *entry2 /**< Second PDO entry. */ + ) +{ + return entry1->index == entry2->index + && entry1->subindex == entry2->subindex + && entry1->bit_length == entry2->bit_length; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/pdo_entry.h b/net/ethercat/master/pdo_entry.h new file mode 100644 index 000000000000..f78e4d2533bf --- /dev/null +++ b/net/ethercat/master/pdo_entry.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT Process data object structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_PDO_ENTRY_H__ +#define __EC_PDO_ENTRY_H__ + +#include <linux/list.h> + +#include "globals.h" + +/*****************************************************************************/ + +/** PDO entry description. + */ +typedef struct { + struct list_head list; /**< list item */ + uint16_t index; /**< PDO entry index */ + uint8_t subindex; /**< PDO entry subindex */ + char *name; /**< entry name */ + uint8_t bit_length; /**< entry length in bit */ +} ec_pdo_entry_t; + +/*****************************************************************************/ + +void ec_pdo_entry_init(ec_pdo_entry_t *); +int ec_pdo_entry_init_copy(ec_pdo_entry_t *, const ec_pdo_entry_t *); +void ec_pdo_entry_clear(ec_pdo_entry_t *); +int ec_pdo_entry_set_name(ec_pdo_entry_t *, const char *); +int ec_pdo_entry_equal(const ec_pdo_entry_t *, const ec_pdo_entry_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/pdo_list.c b/net/ethercat/master/pdo_list.c new file mode 100644 index 000000000000..dbff60527996 --- /dev/null +++ b/net/ethercat/master/pdo_list.c @@ -0,0 +1,346 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT PDO list methods. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> + +#include "globals.h" +#include "pdo.h" +#include "slave_config.h" +#include "master.h" + +#include "pdo_list.h" + +/*****************************************************************************/ + +/** PDO list constructor. + */ +void ec_pdo_list_init( + ec_pdo_list_t *pl /**< PDO list. */ + ) +{ + INIT_LIST_HEAD(&pl->list); +} + +/*****************************************************************************/ + +/** PDO list destructor. + */ +void ec_pdo_list_clear(ec_pdo_list_t *pl /**< PDO list. */) +{ + ec_pdo_list_clear_pdos(pl); +} + +/*****************************************************************************/ + +/** Clears the list of mapped PDOs. + */ +void ec_pdo_list_clear_pdos(ec_pdo_list_t *pl /**< PDO list. */) +{ + ec_pdo_t *pdo, *next; + + list_for_each_entry_safe(pdo, next, &pl->list, list) { + list_del_init(&pdo->list); + ec_pdo_clear(pdo); + kfree(pdo); + } +} + +/*****************************************************************************/ + +/** Calculates the total size of the mapped PDO entries. + * + * \retval Data size in byte. + */ +uint16_t ec_pdo_list_total_size( + const ec_pdo_list_t *pl /**< PDO list. */ + ) +{ + unsigned int bit_size; + const ec_pdo_t *pdo; + const ec_pdo_entry_t *pdo_entry; + uint16_t byte_size; + + bit_size = 0; + list_for_each_entry(pdo, &pl->list, list) { + list_for_each_entry(pdo_entry, &pdo->entries, list) { + bit_size += pdo_entry->bit_length; + } + } + + if (bit_size % 8) // round up to full bytes + byte_size = bit_size / 8 + 1; + else + byte_size = bit_size / 8; + + return byte_size; +} + +/*****************************************************************************/ + +/** Add a new PDO to the list. + * + * \return Pointer to new PDO, otherwise an ERR_PTR() code. + */ +ec_pdo_t *ec_pdo_list_add_pdo( + ec_pdo_list_t *pl, /**< PDO list. */ + uint16_t index /**< PDO index. */ + ) +{ + ec_pdo_t *pdo; + + if (!(pdo = (ec_pdo_t *) kmalloc(sizeof(ec_pdo_t), GFP_KERNEL))) { + EC_ERR("Failed to allocate memory for PDO.\n"); + return ERR_PTR(-ENOMEM); + } + + ec_pdo_init(pdo); + pdo->index = index; + list_add_tail(&pdo->list, &pl->list); + return pdo; +} + +/*****************************************************************************/ + +/** Add the copy of an existing PDO to the list. + * + * \return 0 on success, else < 0 + */ +int ec_pdo_list_add_pdo_copy( + ec_pdo_list_t *pl, /**< PDO list. */ + const ec_pdo_t *pdo /**< PDO to add. */ + ) +{ + ec_pdo_t *mapped_pdo; + int ret; + + // PDO already mapped? + list_for_each_entry(mapped_pdo, &pl->list, list) { + if (mapped_pdo->index != pdo->index) continue; + EC_ERR("PDO 0x%04X is already mapped!\n", pdo->index); + return -EEXIST; + } + + if (!(mapped_pdo = kmalloc(sizeof(ec_pdo_t), GFP_KERNEL))) { + EC_ERR("Failed to allocate PDO memory.\n"); + return -ENOMEM; + } + + ret = ec_pdo_init_copy(mapped_pdo, pdo); + if (ret < 0) { + kfree(mapped_pdo); + return ret; + } + + list_add_tail(&mapped_pdo->list, &pl->list); + return 0; +} + +/*****************************************************************************/ + +/** Makes a deep copy of another PDO list. + * + * \return 0 on success, else < 0 + */ +int ec_pdo_list_copy( + ec_pdo_list_t *pl, /**< PDO list. */ + const ec_pdo_list_t *other /**< PDO list to copy from. */ + ) +{ + ec_pdo_t *other_pdo; + int ret; + + ec_pdo_list_clear_pdos(pl); + + // PDO already mapped? + list_for_each_entry(other_pdo, &other->list, list) { + ret = ec_pdo_list_add_pdo_copy(pl, other_pdo); + if (ret) + return ret; + } + + return 0; +} + +/*****************************************************************************/ + +/** Compares two PDO lists. + * + * Only the list is compared, not the PDO entries (i. e. the PDO + * mapping). + * + * \retval 1 The given PDO lists are equal. + * \retval 0 The given PDO lists differ. + */ +int ec_pdo_list_equal( + const ec_pdo_list_t *pl1, /**< First list. */ + const ec_pdo_list_t *pl2 /**< Second list. */ + ) +{ + const struct list_head *h1, *h2, *l1, *l2; + const ec_pdo_t *p1, *p2; + + h1 = l1 = &pl1->list; + h2 = l2 = &pl2->list; + + while (1) { + l1 = l1->next; + l2 = l2->next; + + if ((l1 == h1) ^ (l2 == h2)) // unequal lengths + return 0; + if (l1 == h1) // both finished + break; + + p1 = list_entry(l1, ec_pdo_t, list); + p2 = list_entry(l2, ec_pdo_t, list); + + if (p1->index != p2->index) + return 0; + } + + return 1; +} + +/*****************************************************************************/ + +/** Finds a PDO with the given index. + * + * \return Search result, or NULL. + */ +ec_pdo_t *ec_pdo_list_find_pdo( + const ec_pdo_list_t *pl, /**< PDO list. */ + uint16_t index /**< PDO index. */ + ) +{ + ec_pdo_t *pdo; + + list_for_each_entry(pdo, &pl->list, list) { + if (pdo->index != index) + continue; + return pdo; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Finds a PDO with the given index and returns a const pointer. + * + * \return Search result, or NULL. + */ +const ec_pdo_t *ec_pdo_list_find_pdo_const( + const ec_pdo_list_t *pl, /**< PDO list. */ + uint16_t index /**< PDO index. */ + ) +{ + const ec_pdo_t *pdo; + + list_for_each_entry(pdo, &pl->list, list) { + if (pdo->index != index) + continue; + return pdo; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Finds a PDO via its position in the list. + * + * Const version. + * + * \return Zero on success, otherwise a negative error code. + */ +const ec_pdo_t *ec_pdo_list_find_pdo_by_pos_const( + const ec_pdo_list_t *pl, /**< PDO list. */ + unsigned int pos /**< Position in the list. */ + ) +{ + const ec_pdo_t *pdo; + + list_for_each_entry(pdo, &pl->list, list) { + if (pos--) + continue; + return pdo; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Get the number of PDOs in the list. + * + * \return Number of PDOs. + */ +unsigned int ec_pdo_list_count( + const ec_pdo_list_t *pl /**< PDO list. */ + ) +{ + const ec_pdo_t *pdo; + unsigned int num = 0; + + list_for_each_entry(pdo, &pl->list, list) { + num++; + } + + return num; +} + +/*****************************************************************************/ + +/** Outputs the PDOs in the list. + */ +void ec_pdo_list_print( + const ec_pdo_list_t *pl /**< PDO list. */ + ) +{ + const ec_pdo_t *pdo; + + if (list_empty(&pl->list)) { + printk("(none)"); + } else { + list_for_each_entry(pdo, &pl->list, list) { + printk("0x%04X", pdo->index); + if (pdo->list.next != &pl->list) + printk(" "); + } + } +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/pdo_list.h b/net/ethercat/master/pdo_list.h new file mode 100644 index 000000000000..e2a78ccbf93a --- /dev/null +++ b/net/ethercat/master/pdo_list.h @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT PDO list structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_PDO_LIST_H__ +#define __EC_PDO_LIST_H__ + +#include <linux/list.h> + +#include "globals.h" +#include "pdo.h" + +/*****************************************************************************/ + +/** EtherCAT PDO list. + */ +typedef struct { + struct list_head list; /**< List of PDOs. */ +} ec_pdo_list_t; + +/*****************************************************************************/ + +void ec_pdo_list_init(ec_pdo_list_t *); +void ec_pdo_list_clear(ec_pdo_list_t *); + +void ec_pdo_list_clear_pdos(ec_pdo_list_t *); + +ec_pdo_t *ec_pdo_list_add_pdo(ec_pdo_list_t *, uint16_t); +int ec_pdo_list_add_pdo_copy(ec_pdo_list_t *, const ec_pdo_t *); + +int ec_pdo_list_copy(ec_pdo_list_t *, const ec_pdo_list_t *); + +uint16_t ec_pdo_list_total_size(const ec_pdo_list_t *); +int ec_pdo_list_equal(const ec_pdo_list_t *, const ec_pdo_list_t *); + +ec_pdo_t *ec_pdo_list_find_pdo(const ec_pdo_list_t *, uint16_t); +const ec_pdo_t *ec_pdo_list_find_pdo_const(const ec_pdo_list_t *, + uint16_t); +const ec_pdo_t *ec_pdo_list_find_pdo_by_pos_const( + const ec_pdo_list_t *, unsigned int); +unsigned int ec_pdo_list_count(const ec_pdo_list_t *); + +void ec_pdo_list_print(const ec_pdo_list_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/reg_request.c b/net/ethercat/master/reg_request.c new file mode 100644 index 000000000000..c1670d6141eb --- /dev/null +++ b/net/ethercat/master/reg_request.c @@ -0,0 +1,131 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * Register request functions. + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/slab.h> + +#include "reg_request.h" + +/*****************************************************************************/ + +/** Register request constructor. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_reg_request_init( + ec_reg_request_t *reg, /**< Register request. */ + size_t size /**< Memory size. */ + ) +{ + if (!(reg->data = (uint8_t *) kmalloc(size, GFP_KERNEL))) { + EC_ERR("Failed to allocate %zu bytes of register memory.\n", size); + return -ENOMEM; + } + + INIT_LIST_HEAD(®->list); + reg->mem_size = size; + memset(reg->data, 0x00, size); + reg->dir = EC_DIR_INVALID; + reg->address = 0; + reg->transfer_size = 0; + reg->state = EC_INT_REQUEST_INIT; + reg->ring_position = 0; + return 0; +} + +/*****************************************************************************/ + +/** Register request destructor. + */ +void ec_reg_request_clear( + ec_reg_request_t *reg /**< Register request. */ + ) +{ + if (reg->data) { + kfree(reg->data); + } +} + +/***************************************************************************** + * Application interface. + ****************************************************************************/ + +uint8_t *ecrt_reg_request_data(ec_reg_request_t *reg) +{ + return reg->data; +} + +/*****************************************************************************/ + +ec_request_state_t ecrt_reg_request_state(const ec_reg_request_t *reg) +{ + return ec_request_state_translation_table[reg->state]; +} + +/*****************************************************************************/ + +void ecrt_reg_request_write(ec_reg_request_t *reg, uint16_t address, + size_t size) +{ + reg->dir = EC_DIR_OUTPUT; + reg->address = address; + reg->transfer_size = min(size, reg->mem_size); + reg->state = EC_INT_REQUEST_QUEUED; +} + +/*****************************************************************************/ + +void ecrt_reg_request_read(ec_reg_request_t *reg, uint16_t address, + size_t size) +{ + reg->dir = EC_DIR_INPUT; + reg->address = address; + reg->transfer_size = min(size, reg->mem_size); + reg->state = EC_INT_REQUEST_QUEUED; +} + +/*****************************************************************************/ + +/** \cond */ + +EXPORT_SYMBOL(ecrt_reg_request_data); +EXPORT_SYMBOL(ecrt_reg_request_state); +EXPORT_SYMBOL(ecrt_reg_request_write); +EXPORT_SYMBOL(ecrt_reg_request_read); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/master/reg_request.h b/net/ethercat/master/reg_request.h new file mode 100644 index 000000000000..f12212925885 --- /dev/null +++ b/net/ethercat/master/reg_request.h @@ -0,0 +1,67 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT register request structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_REG_REQUEST_H__ +#define __EC_REG_REQUEST_H__ + +#include <linux/list.h> + +#include "globals.h" + +/*****************************************************************************/ + +/** Register request. + */ +struct ec_reg_request { + struct list_head list; /**< List item. */ + size_t mem_size; /**< Size of data memory. */ + uint8_t *data; /**< Pointer to data memory. */ + ec_direction_t dir; /**< Direction. EC_DIR_OUTPUT means writing to the + slave, EC_DIR_INPUT means reading from the slave. */ + uint16_t address; /**< Register address. */ + size_t transfer_size; /**< Size of the data to transfer. */ + ec_internal_request_state_t state; /**< Request state. */ + uint16_t ring_position; /**< Ring position for emergency requests. */ +}; + +/*****************************************************************************/ + +int ec_reg_request_init(ec_reg_request_t *, size_t); +void ec_reg_request_clear(ec_reg_request_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/rtdm-ioctl.c b/net/ethercat/master/rtdm-ioctl.c new file mode 100644 index 000000000000..69fb35527a8a --- /dev/null +++ b/net/ethercat/master/rtdm-ioctl.c @@ -0,0 +1,4646 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT master character device. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/vmalloc.h> + +#include "master.h" +#include "slave_config.h" +#include "voe_handler.h" +#include "ethernet.h" +#include "ioctl.h" + +/** Set to 1 to enable ioctl() latency tracing. + * + * Requires CPU timestamp counter! + */ +#define DEBUG_LATENCY 0 + +/** Optional compiler attributes fo ioctl() functions. + */ +#if 0 +#define ATTRIBUTES __attribute__ ((__noinline__)) +#else +#define ATTRIBUTES +#endif + +/*****************************************************************************/ + +/** Copies a string to an ioctl structure. + */ +static void ec_ioctl_strcpy( + char *target, /**< Target. */ + const char *source /**< Source. */ + ) +{ + if (source) { + strncpy(target, source, EC_IOCTL_STRING_SIZE); + target[EC_IOCTL_STRING_SIZE - 1] = 0; + } else { + target[0] = 0; + } +} + +/*****************************************************************************/ + +/** Get module information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_module( + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_module_t data; + + data.ioctl_version_magic = EC_IOCTL_VERSION_MAGIC; + data.master_count = ec_master_count(); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get master information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_master( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_master_t io; + unsigned int dev_idx, j; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + io.slave_count = master->slave_count; + io.config_count = ec_master_config_count(master); + io.domain_count = ec_master_domain_count(master); +#ifdef EC_EOE + io.eoe_handler_count = ec_master_eoe_handler_count(master); +#endif + io.phase = (uint8_t) master->phase; + io.active = (uint8_t) master->active; + io.scan_busy = master->scan_busy; + + up(&master->master_sem); + + if (down_interruptible(&master->device_sem)) { + return -EINTR; + } + + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(master); dev_idx++) { + ec_device_t *device = &master->devices[dev_idx]; + + if (device->dev) { + memcpy(io.devices[dev_idx].address, device->dev->dev_addr, + ETH_ALEN); + } else { + memcpy(io.devices[dev_idx].address, master->macs[dev_idx], + ETH_ALEN); + } + io.devices[dev_idx].attached = device->dev ? 1 : 0; + io.devices[dev_idx].link_state = device->link_state ? 1 : 0; + io.devices[dev_idx].tx_count = device->tx_count; + io.devices[dev_idx].rx_count = device->rx_count; + io.devices[dev_idx].tx_bytes = device->tx_bytes; + io.devices[dev_idx].rx_bytes = device->rx_bytes; + io.devices[dev_idx].tx_errors = device->tx_errors; + for (j = 0; j < EC_RATE_COUNT; j++) { + io.devices[dev_idx].tx_frame_rates[j] = + device->tx_frame_rates[j]; + io.devices[dev_idx].rx_frame_rates[j] = + device->rx_frame_rates[j]; + io.devices[dev_idx].tx_byte_rates[j] = + device->tx_byte_rates[j]; + io.devices[dev_idx].rx_byte_rates[j] = + device->rx_byte_rates[j]; + } + } + io.num_devices = ec_master_num_devices(master); + + io.tx_count = master->device_stats.tx_count; + io.rx_count = master->device_stats.rx_count; + io.tx_bytes = master->device_stats.tx_bytes; + io.rx_bytes = master->device_stats.rx_bytes; + for (j = 0; j < EC_RATE_COUNT; j++) { + io.tx_frame_rates[j] = + master->device_stats.tx_frame_rates[j]; + io.rx_frame_rates[j] = + master->device_stats.rx_frame_rates[j]; + io.tx_byte_rates[j] = + master->device_stats.tx_byte_rates[j]; + io.rx_byte_rates[j] = + master->device_stats.rx_byte_rates[j]; + io.loss_rates[j] = + master->device_stats.loss_rates[j]; + } + + up(&master->device_sem); + + io.app_time = master->app_time; + io.dc_ref_time = master->dc_ref_time; + io.ref_clock = + master->dc_ref_clock ? master->dc_ref_clock->ring_position : 0xffff; + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Get slave information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_slave_t data; + const ec_slave_t *slave; + int i; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.position); + return -EINVAL; + } + + data.device_index = slave->device_index; + data.vendor_id = slave->sii.vendor_id; + data.product_code = slave->sii.product_code; + data.revision_number = slave->sii.revision_number; + data.serial_number = slave->sii.serial_number; + data.alias = slave->effective_alias; + data.boot_rx_mailbox_offset = slave->sii.boot_rx_mailbox_offset; + data.boot_rx_mailbox_size = slave->sii.boot_rx_mailbox_size; + data.boot_tx_mailbox_offset = slave->sii.boot_tx_mailbox_offset; + data.boot_tx_mailbox_size = slave->sii.boot_tx_mailbox_size; + data.std_rx_mailbox_offset = slave->sii.std_rx_mailbox_offset; + data.std_rx_mailbox_size = slave->sii.std_rx_mailbox_size; + data.std_tx_mailbox_offset = slave->sii.std_tx_mailbox_offset; + data.std_tx_mailbox_size = slave->sii.std_tx_mailbox_size; + data.mailbox_protocols = slave->sii.mailbox_protocols; + data.has_general_category = slave->sii.has_general; + data.coe_details = slave->sii.coe_details; + data.general_flags = slave->sii.general_flags; + data.current_on_ebus = slave->sii.current_on_ebus; + for (i = 0; i < EC_MAX_PORTS; i++) { + data.ports[i].desc = slave->ports[i].desc; + data.ports[i].link.link_up = slave->ports[i].link.link_up; + data.ports[i].link.loop_closed = slave->ports[i].link.loop_closed; + data.ports[i].link.signal_detected = + slave->ports[i].link.signal_detected; + data.ports[i].receive_time = slave->ports[i].receive_time; + if (slave->ports[i].next_slave) { + data.ports[i].next_slave = + slave->ports[i].next_slave->ring_position; + } else { + data.ports[i].next_slave = 0xffff; + } + data.ports[i].delay_to_next_dc = slave->ports[i].delay_to_next_dc; + } + data.fmmu_bit = slave->base_fmmu_bit_operation; + data.dc_supported = slave->base_dc_supported; + data.dc_range = slave->base_dc_range; + data.has_dc_system_time = slave->has_dc_system_time; + data.transmission_delay = slave->transmission_delay; + data.al_state = slave->current_state; + data.error_flag = slave->error_flag; + + data.sync_count = slave->sii.sync_count; + data.sdo_count = ec_slave_sdo_count(slave); + data.sii_nwords = slave->sii_nwords; + ec_ioctl_strcpy(data.group, slave->sii.group); + ec_ioctl_strcpy(data.image, slave->sii.image); + ec_ioctl_strcpy(data.order, slave->sii.order); + ec_ioctl_strcpy(data.name, slave->sii.name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave sync manager information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sync( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_slave_sync_t data; + const ec_slave_t *slave; + const ec_sync_t *sync; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (data.sync_index >= slave->sii.sync_count) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", + data.sync_index); + return -EINVAL; + } + + sync = &slave->sii.syncs[data.sync_index]; + + data.physical_start_address = sync->physical_start_address; + data.default_size = sync->default_length; + data.control_register = sync->control_register; + data.enable = sync->enable; + data.pdo_count = ec_pdo_list_count(&sync->pdos); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave sync manager PDO information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sync_pdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_slave_sync_pdo_t data; + const ec_slave_t *slave; + const ec_sync_t *sync; + const ec_pdo_t *pdo; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (data.sync_index >= slave->sii.sync_count) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", + data.sync_index); + return -EINVAL; + } + + sync = &slave->sii.syncs[data.sync_index]; + if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( + &sync->pdos, data.pdo_pos))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not contain a PDO with " + "position %u!\n", data.sync_index, data.pdo_pos); + return -EINVAL; + } + + data.index = pdo->index; + data.entry_count = ec_pdo_entry_count(pdo); + ec_ioctl_strcpy(data.name, pdo->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave sync manager PDO entry information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sync_pdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_slave_sync_pdo_entry_t data; + const ec_slave_t *slave; + const ec_sync_t *sync; + const ec_pdo_t *pdo; + const ec_pdo_entry_t *entry; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (data.sync_index >= slave->sii.sync_count) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", + data.sync_index); + return -EINVAL; + } + + sync = &slave->sii.syncs[data.sync_index]; + if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( + &sync->pdos, data.pdo_pos))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Sync manager %u does not contain a PDO with " + "position %u!\n", data.sync_index, data.pdo_pos); + return -EINVAL; + } + + if (!(entry = ec_pdo_find_entry_by_pos_const( + pdo, data.entry_pos))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "PDO 0x%04X does not contain an entry with " + "position %u!\n", data.pdo_pos, data.entry_pos); + return -EINVAL; + } + + data.index = entry->index; + data.subindex = entry->subindex; + data.bit_length = entry->bit_length; + ec_ioctl_strcpy(data.name, entry->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get domain information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_domain_t data; + const ec_domain_t *domain; + unsigned int dev_idx; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(domain = ec_master_find_domain_const(master, data.index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Domain %u does not exist!\n", data.index); + return -EINVAL; + } + + data.data_size = domain->data_size; + data.logical_base_address = domain->logical_base_address; + for (dev_idx = EC_DEVICE_MAIN; + dev_idx < ec_master_num_devices(domain->master); dev_idx++) { + data.working_counter[dev_idx] = domain->working_counter[dev_idx]; + } + data.expected_working_counter = domain->expected_working_counter; + data.fmmu_count = ec_domain_fmmu_count(domain); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get domain FMMU information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_fmmu( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_domain_fmmu_t data; + const ec_domain_t *domain; + const ec_fmmu_config_t *fmmu; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Domain %u does not exist!\n", + data.domain_index); + return -EINVAL; + } + + if (!(fmmu = ec_domain_find_fmmu(domain, data.fmmu_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Domain %u has less than %u" + " fmmu configurations.\n", + data.domain_index, data.fmmu_index + 1); + return -EINVAL; + } + + data.slave_config_alias = fmmu->sc->alias; + data.slave_config_position = fmmu->sc->position; + data.sync_index = fmmu->sync_index; + data.dir = fmmu->dir; + data.logical_address = fmmu->logical_start_address; + data.data_size = fmmu->data_size; + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get domain data. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_data( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< Userspace address to store the results. */ + ) +{ + ec_ioctl_domain_data_t data; + const ec_domain_t *domain; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Domain %u does not exist!\n", + data.domain_index); + return -EINVAL; + } + + if (domain->data_size != data.data_size) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Data size mismatch %u/%zu!\n", + data.data_size, domain->data_size); + return -EFAULT; + } + + if (copy_to_user((void __user *) data.target, domain->data, + domain->data_size)) { + up(&master->master_sem); + return -EFAULT; + } + + up(&master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** Set master debug level. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_master_debug( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + return ec_master_debug_level(master, (unsigned long) arg); +} + +/*****************************************************************************/ + +/** Issue a bus scan. + * + * \return Always zero (success). + */ +static ATTRIBUTES int ec_ioctl_master_rescan( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + master->fsm.rescan_required = 1; + return 0; +} + +/*****************************************************************************/ + +/** Set slave state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_state_t data; + ec_slave_t *slave; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + ec_slave_request_state(slave, data.al_state); + + up(&master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** Get slave SDO information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sdo_t data; + const ec_slave_t *slave; + const ec_sdo_t *sdo; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (!(sdo = ec_slave_get_sdo_by_pos_const( + slave, data.sdo_position))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "SDO %u does not exist!\n", data.sdo_position); + return -EINVAL; + } + + data.sdo_index = sdo->index; + data.max_subindex = sdo->max_subindex; + ec_ioctl_strcpy(data.name, sdo->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave SDO entry information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sdo_entry_t data; + const ec_slave_t *slave; + const ec_sdo_t *sdo; + const ec_sdo_entry_t *entry; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (data.sdo_spec <= 0) { + if (!(sdo = ec_slave_get_sdo_by_pos_const( + slave, -data.sdo_spec))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "SDO %u does not exist!\n", -data.sdo_spec); + return -EINVAL; + } + } else { + if (!(sdo = ec_slave_get_sdo_const( + slave, data.sdo_spec))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "SDO 0x%04X does not exist!\n", + data.sdo_spec); + return -EINVAL; + } + } + + if (!(entry = ec_sdo_get_entry_const( + sdo, data.sdo_entry_subindex))) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "SDO entry 0x%04X:%02X does not exist!\n", + sdo->index, data.sdo_entry_subindex); + return -EINVAL; + } + + data.data_type = entry->data_type; + data.bit_length = entry->bit_length; + data.read_access[EC_SDO_ENTRY_ACCESS_PREOP] = + entry->read_access[EC_SDO_ENTRY_ACCESS_PREOP]; + data.read_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = + entry->read_access[EC_SDO_ENTRY_ACCESS_SAFEOP]; + data.read_access[EC_SDO_ENTRY_ACCESS_OP] = + entry->read_access[EC_SDO_ENTRY_ACCESS_OP]; + data.write_access[EC_SDO_ENTRY_ACCESS_PREOP] = + entry->write_access[EC_SDO_ENTRY_ACCESS_PREOP]; + data.write_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = + entry->write_access[EC_SDO_ENTRY_ACCESS_SAFEOP]; + data.write_access[EC_SDO_ENTRY_ACCESS_OP] = + entry->write_access[EC_SDO_ENTRY_ACCESS_OP]; + ec_ioctl_strcpy(data.description, entry->description); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Upload SDO. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sdo_upload( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sdo_upload_t data; + uint8_t *target; + int ret; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (!(target = kmalloc(data.target_size, GFP_KERNEL))) { + EC_MASTER_ERR(master, "Failed to allocate %zu bytes" + " for SDO upload.\n", data.target_size); + return -ENOMEM; + } + + ret = ecrt_master_sdo_upload(master, data.slave_position, + data.sdo_index, data.sdo_entry_subindex, target, + data.target_size, &data.data_size, &data.abort_code); + + if (!ret) { + if (copy_to_user((void __user *) data.target, + target, data.data_size)) { + kfree(target); + return -EFAULT; + } + } + + kfree(target); + + if (__copy_to_user((void __user *) arg, &data, sizeof(data))) { + return -EFAULT; + } + + return ret; +} + +/*****************************************************************************/ + +/** Download SDO. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sdo_download( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sdo_download_t data; + uint8_t *sdo_data; + int retval; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (!(sdo_data = kmalloc(data.data_size, GFP_KERNEL))) { + EC_MASTER_ERR(master, "Failed to allocate %zu bytes" + " for SDO download.\n", data.data_size); + return -ENOMEM; + } + + if (copy_from_user(sdo_data, (void __user *) data.data, data.data_size)) { + kfree(sdo_data); + return -EFAULT; + } + + if (data.complete_access) { + retval = ecrt_master_sdo_download_complete(master, data.slave_position, + data.sdo_index, sdo_data, data.data_size, &data.abort_code); + } else { + retval = ecrt_master_sdo_download(master, data.slave_position, + data.sdo_index, data.sdo_entry_subindex, sdo_data, + data.data_size, &data.abort_code); + } + + kfree(sdo_data); + + if (__copy_to_user((void __user *) arg, &data, sizeof(data))) { + retval = -EFAULT; + } + + return retval; +} + +/*****************************************************************************/ + +/** Read a slave's SII. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sii_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sii_t data; + const ec_slave_t *slave; + int retval; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(slave = ec_master_find_slave_const( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + return -EINVAL; + } + + if (!data.nwords + || data.offset + data.nwords > slave->sii_nwords) { + up(&master->master_sem); + EC_SLAVE_ERR(slave, "Invalid SII read offset/size %u/%u for slave SII" + " size %zu!\n", data.offset, data.nwords, slave->sii_nwords); + return -EINVAL; + } + + if (copy_to_user((void __user *) data.words, + slave->sii_words + data.offset, data.nwords * 2)) + retval = -EFAULT; + else + retval = 0; + + up(&master->master_sem); + return retval; +} + +/*****************************************************************************/ + +/** Write a slave's SII. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_sii_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_sii_t data; + ec_slave_t *slave; + unsigned int byte_size; + uint16_t *words; + ec_sii_write_request_t request; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (!data.nwords) { + return 0; + } + + byte_size = sizeof(uint16_t) * data.nwords; + if (!(words = kmalloc(byte_size, GFP_KERNEL))) { + EC_MASTER_ERR(master, "Failed to allocate %u bytes" + " for SII contents.\n", byte_size); + return -ENOMEM; + } + + if (copy_from_user(words, + (void __user *) data.words, byte_size)) { + kfree(words); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(words); + return -EINTR; + } + + if (!(slave = ec_master_find_slave( + master, 0, data.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + data.slave_position); + kfree(words); + return -EINVAL; + } + + // init SII write request + INIT_LIST_HEAD(&request.list); + request.slave = slave; + request.words = words; + request.offset = data.offset; + request.nwords = data.nwords; + request.state = EC_INT_REQUEST_QUEUED; + + // schedule SII write request. + list_add_tail(&request.list, &master->sii_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + kfree(words); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + kfree(words); + + return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; +} + +/*****************************************************************************/ + +/** Read a slave's registers. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_reg_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_reg_t io; + ec_slave_t *slave; + ec_reg_request_t request; + int ret; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + if (!io.size) { + return 0; + } + + // init register request + ret = ec_reg_request_init(&request, io.size); + if (ret) { + return ret; + } + + ecrt_reg_request_read(&request, io.address, io.size); + + if (down_interruptible(&master->master_sem)) { + ec_reg_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave( + master, 0, io.slave_position))) { + up(&master->master_sem); + ec_reg_request_clear(&request); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + io.slave_position); + return -EINVAL; + } + + // schedule request. + list_add_tail(&request.list, &slave->reg_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + ec_reg_request_clear(&request); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + if (request.state == EC_INT_REQUEST_SUCCESS) { + if (copy_to_user((void __user *) io.data, request.data, io.size)) { + return -EFAULT; + } + } + ec_reg_request_clear(&request); + + return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; +} + +/*****************************************************************************/ + +/** Write a slave's registers. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_reg_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_reg_t io; + ec_slave_t *slave; + ec_reg_request_t request; + int ret; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + if (!io.size) { + return 0; + } + + // init register request + ret = ec_reg_request_init(&request, io.size); + if (ret) { + return ret; + } + + if (copy_from_user(request.data, (void __user *) io.data, io.size)) { + ec_reg_request_clear(&request); + return -EFAULT; + } + + ecrt_reg_request_write(&request, io.address, io.size); + + if (down_interruptible(&master->master_sem)) { + ec_reg_request_clear(&request); + return -EINTR; + } + + if (io.emergency) { + request.ring_position = io.slave_position; + // schedule request. + list_add_tail(&request.list, &master->emerg_reg_requests); + } + else { + if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { + up(&master->master_sem); + ec_reg_request_clear(&request); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + io.slave_position); + return -EINVAL; + } + + // schedule request. + list_add_tail(&request.list, &slave->reg_requests); + } + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + ec_reg_request_clear(&request); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + ec_reg_request_clear(&request); + + return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; +} + +/*****************************************************************************/ + +/** Get slave configuration information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_t data; + const ec_slave_config_t *sc; + uint8_t i; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config_const( + master, data.config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + data.config_index); + return -EINVAL; + } + + data.alias = sc->alias; + data.position = sc->position; + data.vendor_id = sc->vendor_id; + data.product_code = sc->product_code; + for (i = 0; i < EC_MAX_SYNC_MANAGERS; i++) { + data.syncs[i].dir = sc->sync_configs[i].dir; + data.syncs[i].watchdog_mode = sc->sync_configs[i].watchdog_mode; + data.syncs[i].pdo_count = + ec_pdo_list_count(&sc->sync_configs[i].pdos); + } + data.watchdog_divider = sc->watchdog_divider; + data.watchdog_intervals = sc->watchdog_intervals; + data.sdo_count = ec_slave_config_sdo_count(sc); + data.idn_count = ec_slave_config_idn_count(sc); + data.slave_position = sc->slave ? sc->slave->ring_position : -1; + data.dc_assign_activate = sc->dc_assign_activate; + for (i = 0; i < EC_SYNC_SIGNAL_COUNT; i++) { + data.dc_sync[i] = sc->dc_sync[i]; + } + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave configuration PDO information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config_pdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_pdo_t data; + const ec_slave_config_t *sc; + const ec_pdo_t *pdo; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (data.sync_index >= EC_MAX_SYNC_MANAGERS) { + EC_MASTER_ERR(master, "Invalid sync manager index %u!\n", + data.sync_index); + return -EINVAL; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config_const( + master, data.config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + data.config_index); + return -EINVAL; + } + + if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( + &sc->sync_configs[data.sync_index].pdos, + data.pdo_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Invalid PDO position!\n"); + return -EINVAL; + } + + data.index = pdo->index; + data.entry_count = ec_pdo_entry_count(pdo); + ec_ioctl_strcpy(data.name, pdo->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave configuration PDO entry information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config_pdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_pdo_entry_t data; + const ec_slave_config_t *sc; + const ec_pdo_t *pdo; + const ec_pdo_entry_t *entry; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (data.sync_index >= EC_MAX_SYNC_MANAGERS) { + EC_MASTER_ERR(master, "Invalid sync manager index %u!\n", + data.sync_index); + return -EINVAL; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config_const( + master, data.config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + data.config_index); + return -EINVAL; + } + + if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( + &sc->sync_configs[data.sync_index].pdos, + data.pdo_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Invalid PDO position!\n"); + return -EINVAL; + } + + if (!(entry = ec_pdo_find_entry_by_pos_const( + pdo, data.entry_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Entry not found!\n"); + return -EINVAL; + } + + data.index = entry->index; + data.subindex = entry->subindex; + data.bit_length = entry->bit_length; + ec_ioctl_strcpy(data.name, entry->name); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get slave configuration SDO information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config_sdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_sdo_t *ioctl; + const ec_slave_config_t *sc; + const ec_sdo_request_t *req; + + if (!(ioctl = kmalloc(sizeof(*ioctl), GFP_KERNEL))) { + return -ENOMEM; + } + + if (copy_from_user(ioctl, (void __user *) arg, sizeof(*ioctl))) { + kfree(ioctl); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(ioctl); + return -EINTR; + } + + if (!(sc = ec_master_get_config_const( + master, ioctl->config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + ioctl->config_index); + kfree(ioctl); + return -EINVAL; + } + + if (!(req = ec_slave_config_get_sdo_by_pos_const( + sc, ioctl->sdo_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Invalid SDO position!\n"); + kfree(ioctl); + return -EINVAL; + } + + ioctl->index = req->index; + ioctl->subindex = req->subindex; + ioctl->size = req->data_size; + memcpy(ioctl->data, req->data, + min((u32) ioctl->size, (u32) EC_MAX_SDO_DATA_SIZE)); + ioctl->complete_access = req->complete_access; + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, ioctl, sizeof(*ioctl))) { + kfree(ioctl); + return -EFAULT; + } + + kfree(ioctl); + return 0; +} + +/*****************************************************************************/ + +/** Get slave configuration IDN information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_config_idn( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_config_idn_t *ioctl; + const ec_slave_config_t *sc; + const ec_soe_request_t *req; + + if (!(ioctl = kmalloc(sizeof(*ioctl), GFP_KERNEL))) { + return -ENOMEM; + } + + if (copy_from_user(ioctl, (void __user *) arg, sizeof(*ioctl))) { + kfree(ioctl); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(ioctl); + return -EINTR; + } + + if (!(sc = ec_master_get_config_const( + master, ioctl->config_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave config %u does not exist!\n", + ioctl->config_index); + kfree(ioctl); + return -EINVAL; + } + + if (!(req = ec_slave_config_get_idn_by_pos_const( + sc, ioctl->idn_pos))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Invalid IDN position!\n"); + kfree(ioctl); + return -EINVAL; + } + + ioctl->drive_no = req->drive_no; + ioctl->idn = req->idn; + ioctl->state = req->state; + ioctl->size = req->data_size; + memcpy(ioctl->data, req->data, + min((u32) ioctl->size, (u32) EC_MAX_IDN_DATA_SIZE)); + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, ioctl, sizeof(*ioctl))) { + kfree(ioctl); + return -EFAULT; + } + + kfree(ioctl); + return 0; +} + +/*****************************************************************************/ + +#ifdef EC_EOE + +/** Get EoE handler information. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_eoe_handler( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_eoe_handler_t data; + const ec_eoe_t *eoe; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(eoe = ec_master_get_eoe_handler_const(master, data.eoe_index))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "EoE handler %u does not exist!\n", + data.eoe_index); + return -EINVAL; + } + + if (eoe->slave) { + data.slave_position = eoe->slave->ring_position; + } else { + data.slave_position = 0xffff; + } + snprintf(data.name, EC_DATAGRAM_NAME_SIZE, eoe->dev->name); + data.open = eoe->opened; + data.rx_bytes = eoe->stats.tx_bytes; + data.rx_rate = eoe->tx_rate; + data.tx_bytes = eoe->stats.rx_bytes; + data.tx_rate = eoe->tx_rate; + data.tx_queued_frames = eoe->tx_queued_frames; + data.tx_queue_size = eoe->tx_queue_size; + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +#endif + +/*****************************************************************************/ + +/** Request the master from userspace. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_request( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_master_t *m; + int ret = 0; + + m = ecrt_request_master_err(master->index); + if (IS_ERR(m)) { + ret = PTR_ERR(m); + } else { + ctx->requested = 1; + } + + return ret; +} + +/*****************************************************************************/ + +/** Create a domain. + * + * \return Domain index on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_create_domain( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_domain_t *domain; + + if (unlikely(!ctx->requested)) + return -EPERM; + + domain = ecrt_master_create_domain_err(master); + if (IS_ERR(domain)) + return PTR_ERR(domain); + + return domain->index; +} + +/*****************************************************************************/ + +/** Create a slave configuration. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_create_slave_config( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_t data; + ec_slave_config_t *sc, *entry; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + sc = ecrt_master_slave_config_err(master, data.alias, data.position, + data.vendor_id, data.product_code); + if (IS_ERR(sc)) + return PTR_ERR(sc); + + data.config_index = 0; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + list_for_each_entry(entry, &master->configs, list) { + if (entry == sc) + break; + data.config_index++; + } + + up(&master->master_sem); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Select the DC reference clock. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_select_ref_clock( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + unsigned long config_index = (unsigned long) arg; + ec_slave_config_t *sc = NULL; + int ret = 0; + + if (unlikely(!ctx->requested)) { + ret = -EPERM; + goto out_return; + } + + if (down_interruptible(&master->master_sem)) { + ret = -EINTR; + goto out_return; + } + + if (config_index != 0xFFFFFFFF) { + if (!(sc = ec_master_get_config(master, config_index))) { + ret = -ENOENT; + goto out_up; + } + } + + ecrt_master_select_reference_clock(master, sc); + +out_up: + up(&master->master_sem); +out_return: + return ret; +} + +/*****************************************************************************/ + +/** Activates the master. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_activate( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_master_activate_t io; + ec_domain_t *domain; + off_t offset; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + io.process_data = NULL; + + /* Get the sum of the domains' process data sizes. */ + + ctx->process_data_size = 0; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + list_for_each_entry(domain, &master->domains, list) { + ctx->process_data_size += ecrt_domain_size(domain); + } + + up(&master->master_sem); + + if (ctx->process_data_size) { + ctx->process_data = vmalloc(ctx->process_data_size); + if (!ctx->process_data) { + ctx->process_data_size = 0; + return -ENOMEM; + } + + /* Set the memory as external process data memory for the + * domains. + */ + offset = 0; + list_for_each_entry(domain, &master->domains, list) { + ecrt_domain_external_memory(domain, + ctx->process_data + offset); + offset += ecrt_domain_size(domain); + } + +#ifdef EC_IOCTL_RTDM + /* RTDM uses a different approach for memory-mapping, which has to be + * initiated by the kernel. + */ + ret = ec_rtdm_mmap(ctx, &io.process_data); + if (ret < 0) { + EC_MASTER_ERR(master, "Failed to map process data" + " memory to user space (code %i).\n", ret); + return ret; + } +#endif + } + + io.process_data_size = ctx->process_data_size; + +#ifndef EC_IOCTL_RTDM + ecrt_master_callbacks(master, ec_master_internal_send_cb, + ec_master_internal_receive_cb, master); +#endif + + ret = ecrt_master_activate(master); + if (ret < 0) + return ret; + + if (copy_to_user((void __user *) arg, &io, + sizeof(ec_ioctl_master_activate_t))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Deactivates the master. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_deactivate( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) + return -EPERM; + + ecrt_master_deactivate(master); + return 0; +} + +/*****************************************************************************/ + +/** Set max. number of databytes in a cycle + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_set_send_interval( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + size_t send_interval; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&send_interval, (void __user *) arg, + sizeof(send_interval))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + ec_master_set_send_interval(master, send_interval); + + up(&master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** Send frames. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_send( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_send(master); + return 0; +} + +/*****************************************************************************/ + +/** Receive frames. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_receive( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_receive(master); + return 0; +} + +/*****************************************************************************/ + +/** Get the master state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_master_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_master_state_t data; + + ecrt_master_state(master, &data); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get the link state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_master_link_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_link_state_t ioctl; + ec_master_link_state_t state; + int ret; + + if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { + return -EFAULT; + } + + ret = ecrt_master_link_state(master, ioctl.dev_idx, &state); + if (ret < 0) { + return ret; + } + + if (copy_to_user((void __user *) ioctl.state, &state, sizeof(state))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Set the master DC application time. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_app_time( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + uint64_t time; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&time, (void __user *) arg, sizeof(time))) { + return -EFAULT; + } + + ecrt_master_application_time(master, time); + return 0; +} + +/*****************************************************************************/ + +/** Sync the reference clock. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_ref( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_sync_reference_clock(master); + return 0; +} + +/*****************************************************************************/ + +/** Sync the reference clock. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_ref_to( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + uint64_t time; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&time, (void __user *) arg, sizeof(time))) { + return -EFAULT; + } + + ecrt_master_sync_reference_clock_to(master, time); + return 0; +} + +/*****************************************************************************/ + +/** Sync the slave clocks. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_slaves( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_sync_slave_clocks(master); + return 0; +} + +/*****************************************************************************/ + +/** Get the system time of the reference clock. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_ref_clock_time( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + uint32_t time; + int ret; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ret = ecrt_master_reference_clock_time(master, &time); + if (ret) { + return ret; + } + + if (copy_to_user((void __user *) arg, &time, sizeof(time))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Queue the sync monitoring datagram. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_mon_queue( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + ecrt_master_sync_monitor_queue(master); + return 0; +} + +/*****************************************************************************/ + +/** Processes the sync monitoring datagram. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sync_mon_process( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + uint32_t time_diff; + + if (unlikely(!ctx->requested)) + return -EPERM; + + time_diff = ecrt_master_sync_monitor_process(master); + + if (copy_to_user((void __user *) arg, &time_diff, sizeof(time_diff))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Reset configuration. + * + * \return Always zero (success). + */ +static ATTRIBUTES int ec_ioctl_reset( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + down(&master->master_sem); + ecrt_master_reset(master); + up(&master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** Configure a sync manager. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_sync( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_t data; + ec_slave_config_t *sc; + unsigned int i; + int ret = 0; + + if (unlikely(!ctx->requested)) { + ret = -EPERM; + goto out_return; + } + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + ret = -EFAULT; + goto out_return; + } + + if (down_interruptible(&master->master_sem)) { + ret = -EINTR; + goto out_return; + } + + if (!(sc = ec_master_get_config(master, data.config_index))) { + ret = -ENOENT; + goto out_up; + } + + for (i = 0; i < EC_MAX_SYNC_MANAGERS; i++) { + if (data.syncs[i].config_this) { + ret = ecrt_slave_config_sync_manager(sc, i, data.syncs[i].dir, + data.syncs[i].watchdog_mode); + if (ret) { + goto out_up; + } + } + } + +out_up: + up(&master->master_sem); +out_return: + return ret; +} + +/*****************************************************************************/ + +/** Configure a slave's watchdogs. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_watchdog( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_t data; + ec_slave_config_t *sc; + int ret = 0; + + if (unlikely(!ctx->requested)) { + ret = -EPERM; + goto out_return; + } + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + ret = -EFAULT; + goto out_return; + } + + if (down_interruptible(&master->master_sem)) { + ret = -EINTR; + goto out_return; + } + + if (!(sc = ec_master_get_config(master, data.config_index))) { + ret = -ENOENT; + goto out_up; + } + + ecrt_slave_config_watchdog(sc, + data.watchdog_divider, data.watchdog_intervals); + +out_up: + up(&master->master_sem); +out_return: + return ret; +} + +/*****************************************************************************/ + +/** Add a PDO to the assignment. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_add_pdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_pdo_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + return ecrt_slave_config_pdo_assign_add(sc, data.sync_index, data.index); +} + +/*****************************************************************************/ + +/** Clears the PDO assignment. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_clear_pdos( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_pdo_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + ecrt_slave_config_pdo_assign_clear(sc, data.sync_index); + return 0; +} + +/*****************************************************************************/ + +/** Add an entry to a PDO's mapping. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_add_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_add_pdo_entry_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + return ecrt_slave_config_pdo_mapping_add(sc, data.pdo_index, + data.entry_index, data.entry_subindex, data.entry_bit_length); +} + +/*****************************************************************************/ + +/** Clears the mapping of a PDO. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_clear_entries( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_pdo_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + ecrt_slave_config_pdo_mapping_clear(sc, data.index); + return 0; +} + +/*****************************************************************************/ + +/** Registers a PDO entry. + * + * \return Process data offset on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_reg_pdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_pdo_entry_t data; + ec_slave_config_t *sc; + ec_domain_t *domain; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + if (!(domain = ec_master_find_domain(master, data.domain_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc or domain could be invalidated */ + + ret = ecrt_slave_config_reg_pdo_entry(sc, data.entry_index, + data.entry_subindex, domain, &data.bit_position); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return ret; +} + +/*****************************************************************************/ + +/** Registers a PDO entry by its position. + * + * \return Process data offset on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_reg_pdo_pos( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_pdo_pos_t io; + ec_slave_config_t *sc; + ec_domain_t *domain; + int ret; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + if (!(sc = ec_master_get_config(master, io.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + if (!(domain = ec_master_find_domain(master, io.domain_index))) { + up(&master->master_sem); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc or domain could be invalidated */ + + ret = ecrt_slave_config_reg_pdo_entry_pos(sc, io.sync_index, + io.pdo_pos, io.entry_pos, domain, &io.bit_position); + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) + return -EFAULT; + + return ret; +} + +/*****************************************************************************/ + +/** Sets the DC AssignActivate word and the sync signal times. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_dc( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_config_t data; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + ecrt_slave_config_dc(sc, data.dc_assign_activate, + data.dc_sync[0].cycle_time, + data.dc_sync[0].shift_time, + data.dc_sync[1].cycle_time, + data.dc_sync[1].shift_time); + + up(&master->master_sem); + + return 0; +} + +/*****************************************************************************/ + +/** Configures an SDO. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_sdo( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_sdo_t data; + ec_slave_config_t *sc; + uint8_t *sdo_data = NULL; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (!data.size) + return -EINVAL; + + if (!(sdo_data = kmalloc(data.size, GFP_KERNEL))) { + return -ENOMEM; + } + + if (copy_from_user(sdo_data, (void __user *) data.data, data.size)) { + kfree(sdo_data); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(sdo_data); + return -EINTR; + } + + if (!(sc = ec_master_get_config(master, data.config_index))) { + up(&master->master_sem); + kfree(sdo_data); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + if (data.complete_access) { + ret = ecrt_slave_config_complete_sdo(sc, + data.index, sdo_data, data.size); + } else { + ret = ecrt_slave_config_sdo(sc, data.index, data.subindex, sdo_data, + data.size); + } + kfree(sdo_data); + return ret; +} + +/*****************************************************************************/ + +/** Set the emergency ring buffer size. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_emerg_size( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_emerg_t io; + ec_slave_config_t *sc; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) + return -EFAULT; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + if (!(sc = ec_master_get_config(master, io.config_index))) { + up(&master->master_sem); + return -ENOENT; + } + + ret = ecrt_slave_config_emerg_size(sc, io.size); + + up(&master->master_sem); + + return ret; +} + +/*****************************************************************************/ + +/** Get an emergency message from the ring. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_emerg_pop( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_emerg_t io; + ec_slave_config_t *sc; + u8 msg[EC_COE_EMERGENCY_MSG_SIZE]; + int ret; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because configuration will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + ret = ecrt_slave_config_emerg_pop(sc, msg); + if (ret < 0) { + return ret; + } + + if (copy_to_user((void __user *) io.target, msg, sizeof(msg))) { + return -EFAULT; + } + + return ret; +} + +/*****************************************************************************/ + +/** Clear the emergency ring. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_emerg_clear( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_emerg_t io; + ec_slave_config_t *sc; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because configuration will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + return ecrt_slave_config_emerg_clear(sc); +} + +/*****************************************************************************/ + +/** Get the number of emergency overruns. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_emerg_overruns( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_emerg_t io; + ec_slave_config_t *sc; + int ret; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because configuration will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + ret = ecrt_slave_config_emerg_overruns(sc); + if (ret < 0) { + return ret; + } + + io.overruns = ret; + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Create an SDO request. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_create_sdo_request( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + data.request_index = 0; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + sc = ec_master_get_config(master, data.config_index); + if (!sc) { + up(&master->master_sem); + return -ENOENT; + } + + list_for_each_entry(req, &sc->sdo_requests, list) { + data.request_index++; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + req = ecrt_slave_config_create_sdo_request_err(sc, data.sdo_index, + data.sdo_subindex, data.size); + if (IS_ERR(req)) + return PTR_ERR(req); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Create a register request. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_create_reg_request( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + io.request_index = 0; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + sc = ec_master_get_config(master, io.config_index); + if (!sc) { + up(&master->master_sem); + return -ENOENT; + } + + list_for_each_entry(reg, &sc->reg_requests, list) { + io.request_index++; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + reg = ecrt_slave_config_create_reg_request_err(sc, io.mem_size); + if (IS_ERR(reg)) { + return PTR_ERR(reg); + } + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Create a VoE handler. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_create_voe_handler( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + data.voe_index = 0; + + if (down_interruptible(&master->master_sem)) + return -EINTR; + + sc = ec_master_get_config(master, data.config_index); + if (!sc) { + up(&master->master_sem); + return -ENOENT; + } + + list_for_each_entry(voe, &sc->voe_handlers, list) { + data.voe_index++; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + voe = ecrt_slave_config_create_voe_handler_err(sc, data.size); + if (IS_ERR(voe)) + return PTR_ERR(voe); + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Get the slave configuration's state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_state_t data; + const ec_slave_config_t *sc; + ec_slave_config_state_t state; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because sc will not be deleted in the + * meantime. */ + + if (!(sc = ec_master_get_config_const(master, data.config_index))) { + return -ENOENT; + } + + ecrt_slave_config_state(sc, &state); + + if (copy_to_user((void __user *) data.state, &state, sizeof(state))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Configures an IDN. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sc_idn( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sc_idn_t ioctl; + ec_slave_config_t *sc; + uint8_t *data = NULL; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) + return -EFAULT; + + if (!ioctl.size) + return -EINVAL; + + if (!(data = kmalloc(ioctl.size, GFP_KERNEL))) { + return -ENOMEM; + } + + if (copy_from_user(data, (void __user *) ioctl.data, ioctl.size)) { + kfree(data); + return -EFAULT; + } + + if (down_interruptible(&master->master_sem)) { + kfree(data); + return -EINTR; + } + + if (!(sc = ec_master_get_config(master, ioctl.config_index))) { + up(&master->master_sem); + kfree(data); + return -ENOENT; + } + + up(&master->master_sem); /** \todo sc could be invalidated */ + + ret = ecrt_slave_config_idn( + sc, ioctl.drive_no, ioctl.idn, ioctl.al_state, data, ioctl.size); + kfree(data); + return ret; +} + +/*****************************************************************************/ + +/** Gets the domain's data size. + * + * \return Domain size, or a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_size( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + const ec_domain_t *domain; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + list_for_each_entry(domain, &master->domains, list) { + if (domain->index == (unsigned long) arg) { + size_t size = ecrt_domain_size(domain); + up(&master->master_sem); + return size; + } + } + + up(&master->master_sem); + return -ENOENT; +} + +/*****************************************************************************/ + +/** Gets the domain's offset in the total process data. + * + * \return Domain offset, or a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_offset( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + int offset = 0; + const ec_domain_t *domain; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (down_interruptible(&master->master_sem)) { + return -EINTR; + } + + list_for_each_entry(domain, &master->domains, list) { + if (domain->index == (unsigned long) arg) { + up(&master->master_sem); + return offset; + } + offset += ecrt_domain_size(domain); + } + + up(&master->master_sem); + return -ENOENT; +} + +/*****************************************************************************/ + +/** Process the domain. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_process( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_domain_t *domain; + + if (unlikely(!ctx->requested)) + return -EPERM; + + /* no locking of master_sem needed, because domain will not be deleted in + * the meantime. */ + + if (!(domain = ec_master_find_domain(master, (unsigned long) arg))) { + return -ENOENT; + } + + ecrt_domain_process(domain); + return 0; +} + +/*****************************************************************************/ + +/** Queue the domain. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_queue( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_domain_t *domain; + + if (unlikely(!ctx->requested)) + return -EPERM; + + /* no locking of master_sem needed, because domain will not be deleted in + * the meantime. */ + + if (!(domain = ec_master_find_domain(master, (unsigned long) arg))) { + return -ENOENT; + } + + ecrt_domain_queue(domain); + return 0; +} + +/*****************************************************************************/ + +/** Get the domain state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_domain_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_domain_state_t data; + const ec_domain_t *domain; + ec_domain_state_t state; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because domain will not be deleted in + * the meantime. */ + + if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { + return -ENOENT; + } + + ecrt_domain_state(domain, &state); + + if (copy_to_user((void __user *) data.state, &state, sizeof(state))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Sets an SDO request's SDO index and subindex. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_index( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + ecrt_sdo_request_index(req, data.sdo_index, data.sdo_subindex); + return 0; +} + +/*****************************************************************************/ + +/** Sets an SDO request's timeout. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_timeout( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + ecrt_sdo_request_timeout(req, data.timeout); + return 0; +} + +/*****************************************************************************/ + +/** Gets an SDO request's state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + data.state = ecrt_sdo_request_state(req); + if (data.state == EC_REQUEST_SUCCESS && req->dir == EC_DIR_INPUT) + data.size = ecrt_sdo_request_data_size(req); + else + data.size = 0; + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Starts an SDO read operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + ecrt_sdo_request_read(req); + return 0; +} + +/*****************************************************************************/ + +/** Starts an SDO write operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + int ret; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (!data.size) { + EC_MASTER_ERR(master, "SDO download: Data size may not be zero!\n"); + return -EINVAL; + } + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + ret = ec_sdo_request_alloc(req, data.size); + if (ret) + return ret; + + if (copy_from_user(req->data, (void __user *) data.data, data.size)) + return -EFAULT; + + req->data_size = data.size; + ecrt_sdo_request_write(req); + return 0; +} + +/*****************************************************************************/ + +/** Read SDO data. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_sdo_request_data( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_sdo_request_t data; + ec_slave_config_t *sc; + ec_sdo_request_t *req; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor req will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { + return -ENOENT; + } + + if (copy_to_user((void __user *) data.data, ecrt_sdo_request_data(req), + ecrt_sdo_request_data_size(req))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Read register data. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_reg_request_data( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + if (io.mem_size <= 0) { + return 0; + } + + /* no locking of master_sem needed, because neither sc nor reg will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { + return -ENOENT; + } + + if (copy_to_user((void __user *) io.data, ecrt_reg_request_data(reg), + min(reg->mem_size, io.mem_size))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Gets an register request's state. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_reg_request_state( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because neither sc nor reg will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { + return -ENOENT; + } + + io.state = ecrt_reg_request_state(reg); + io.new_data = io.state == EC_REQUEST_SUCCESS && reg->dir == EC_DIR_INPUT; + + if (copy_to_user((void __user *) arg, &io, sizeof(io))) { + return -EFAULT; + } + + return 0; +} + +/*****************************************************************************/ + +/** Starts an register write operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_reg_request_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because neither sc nor reg will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { + return -ENOENT; + } + + if (io.transfer_size > reg->mem_size) { + return -EOVERFLOW; + } + + if (copy_from_user(reg->data, (void __user *) io.data, + io.transfer_size)) { + return -EFAULT; + } + + ecrt_reg_request_write(reg, io.address, io.transfer_size); + return 0; +} + +/*****************************************************************************/ + +/** Starts an register read operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_reg_request_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_reg_request_t io; + ec_slave_config_t *sc; + ec_reg_request_t *reg; + + if (unlikely(!ctx->requested)) { + return -EPERM; + } + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + /* no locking of master_sem needed, because neither sc nor reg will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, io.config_index))) { + return -ENOENT; + } + + if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { + return -ENOENT; + } + + if (io.transfer_size > reg->mem_size) { + return -EOVERFLOW; + } + + ecrt_reg_request_read(reg, io.address, io.transfer_size); + return 0; +} + +/*****************************************************************************/ + +/** Sets the VoE send header. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_send_header( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + uint32_t vendor_id; + uint16_t vendor_type; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + if (get_user(vendor_id, data.vendor_id)) + return -EFAULT; + + if (get_user(vendor_type, data.vendor_type)) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + ecrt_voe_handler_send_header(voe, vendor_id, vendor_type); + return 0; +} + +/*****************************************************************************/ + +/** Gets the received VoE header. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_rec_header( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + uint32_t vendor_id; + uint16_t vendor_type; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + ecrt_voe_handler_received_header(voe, &vendor_id, &vendor_type); + + if (likely(data.vendor_id)) + if (put_user(vendor_id, data.vendor_id)) + return -EFAULT; + + if (likely(data.vendor_type)) + if (put_user(vendor_type, data.vendor_type)) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Starts a VoE read operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + ecrt_voe_handler_read(voe); + return 0; +} + +/*****************************************************************************/ + +/** Starts a VoE read operation without sending a sync message first. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_read_nosync( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + ecrt_voe_handler_read_nosync(voe); + return 0; +} + +/*****************************************************************************/ + +/** Starts a VoE write operation. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + if (data.size) { + if (data.size > ec_voe_handler_mem_size(voe)) + return -EOVERFLOW; + + if (copy_from_user(ecrt_voe_handler_data(voe), + (void __user *) data.data, data.size)) + return -EFAULT; + } + + ecrt_voe_handler_write(voe, data.size); + return 0; +} + +/*****************************************************************************/ + +/** Executes the VoE state machine. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_exec( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + data.state = ecrt_voe_handler_execute(voe); + if (data.state == EC_REQUEST_SUCCESS && voe->dir == EC_DIR_INPUT) + data.size = ecrt_voe_handler_data_size(voe); + else + data.size = 0; + + if (copy_to_user((void __user *) arg, &data, sizeof(data))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Reads the received VoE data. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_voe_data( + ec_master_t *master, /**< EtherCAT master. */ + void *arg, /**< ioctl() argument. */ + ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ + ) +{ + ec_ioctl_voe_t data; + ec_slave_config_t *sc; + ec_voe_handler_t *voe; + + if (unlikely(!ctx->requested)) + return -EPERM; + + if (copy_from_user(&data, (void __user *) arg, sizeof(data))) + return -EFAULT; + + /* no locking of master_sem needed, because neither sc nor voe will not be + * deleted in the meantime. */ + + if (!(sc = ec_master_get_config(master, data.config_index))) { + return -ENOENT; + } + + if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { + return -ENOENT; + } + + if (copy_to_user((void __user *) data.data, ecrt_voe_handler_data(voe), + ecrt_voe_handler_data_size(voe))) + return -EFAULT; + + return 0; +} + +/*****************************************************************************/ + +/** Read a file from a slave via FoE. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_foe_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_foe_t io; + ec_foe_request_t request; + ec_slave_t *slave; + int ret; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + ec_foe_request_init(&request, io.file_name); + ret = ec_foe_request_alloc(&request, 10000); // FIXME + if (ret) { + ec_foe_request_clear(&request); + return ret; + } + + ec_foe_request_read(&request); + + if (down_interruptible(&master->master_sem)) { + ec_foe_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { + up(&master->master_sem); + ec_foe_request_clear(&request); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + io.slave_position); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling FoE read request.\n"); + + // schedule request. + list_add_tail(&request.list, &slave->foe_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + list_del(&request.list); + up(&master->master_sem); + ec_foe_request_clear(&request); + return -EINTR; + } + // request already processing: interrupt not possible. + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + io.result = request.result; + io.error_code = request.error_code; + + if (request.state != EC_INT_REQUEST_SUCCESS) { + io.data_size = 0; + ret = -EIO; + } else { + if (request.data_size > io.buffer_size) { + EC_SLAVE_ERR(slave, "%s(): Buffer too small.\n", __func__); + ec_foe_request_clear(&request); + return -EOVERFLOW; + } + io.data_size = request.data_size; + if (copy_to_user((void __user *) io.buffer, + request.buffer, io.data_size)) { + ec_foe_request_clear(&request); + return -EFAULT; + } + ret = 0; + } + + if (__copy_to_user((void __user *) arg, &io, sizeof(io))) { + ret = -EFAULT; + } + + ec_foe_request_clear(&request); + return ret; +} + +/*****************************************************************************/ + +/** Write a file to a slave via FoE + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_foe_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_foe_t io; + ec_foe_request_t request; + ec_slave_t *slave; + int ret; + + if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { + return -EFAULT; + } + + ec_foe_request_init(&request, io.file_name); + + ret = ec_foe_request_alloc(&request, io.buffer_size); + if (ret) { + ec_foe_request_clear(&request); + return ret; + } + + if (copy_from_user(request.buffer, + (void __user *) io.buffer, io.buffer_size)) { + ec_foe_request_clear(&request); + return -EFAULT; + } + + request.data_size = io.buffer_size; + ec_foe_request_write(&request); + + if (down_interruptible(&master->master_sem)) { + ec_foe_request_clear(&request); + return -EINTR; + } + + if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { + up(&master->master_sem); + EC_MASTER_ERR(master, "Slave %u does not exist!\n", + io.slave_position); + ec_foe_request_clear(&request); + return -EINVAL; + } + + EC_SLAVE_DBG(slave, 1, "Scheduling FoE write request.\n"); + + // schedule FoE write request. + list_add_tail(&request.list, &slave->foe_requests); + + up(&master->master_sem); + + // wait for processing through FSM + if (wait_event_interruptible(master->request_queue, + request.state != EC_INT_REQUEST_QUEUED)) { + // interrupted by signal + down(&master->master_sem); + if (request.state == EC_INT_REQUEST_QUEUED) { + // abort request + list_del(&request.list); + up(&master->master_sem); + ec_foe_request_clear(&request); + return -EINTR; + } + up(&master->master_sem); + } + + // wait until master FSM has finished processing + wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); + + io.result = request.result; + io.error_code = request.error_code; + + ret = request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; + + if (__copy_to_user((void __user *) arg, &io, sizeof(io))) { + ret = -EFAULT; + } + + ec_foe_request_clear(&request); + return ret; +} + +/*****************************************************************************/ + +/** Read an SoE IDN. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_soe_read( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_soe_read_t ioctl; + u8 *data; + int retval; + + if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { + return -EFAULT; + } + + data = kmalloc(ioctl.mem_size, GFP_KERNEL); + if (!data) { + EC_MASTER_ERR(master, "Failed to allocate %zu bytes of IDN data.\n", + ioctl.mem_size); + return -ENOMEM; + } + + retval = ecrt_master_read_idn(master, ioctl.slave_position, + ioctl.drive_no, ioctl.idn, data, ioctl.mem_size, &ioctl.data_size, + &ioctl.error_code); + if (retval) { + kfree(data); + return retval; + } + + if (copy_to_user((void __user *) ioctl.data, + data, ioctl.data_size)) { + kfree(data); + return -EFAULT; + } + kfree(data); + + if (__copy_to_user((void __user *) arg, &ioctl, sizeof(ioctl))) { + retval = -EFAULT; + } + + EC_MASTER_DBG(master, 1, "Finished SoE read request.\n"); + return retval; +} + +/*****************************************************************************/ + +/** Write an IDN to a slave via SoE. + * + * \return Zero on success, otherwise a negative error code. + */ +static ATTRIBUTES int ec_ioctl_slave_soe_write( + ec_master_t *master, /**< EtherCAT master. */ + void *arg /**< ioctl() argument. */ + ) +{ + ec_ioctl_slave_soe_write_t ioctl; + u8 *data; + int retval; + + if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { + return -EFAULT; + } + + data = kmalloc(ioctl.data_size, GFP_KERNEL); + if (!data) { + EC_MASTER_ERR(master, "Failed to allocate %zu bytes of IDN data.\n", + ioctl.data_size); + return -ENOMEM; + } + if (copy_from_user(data, (void __user *) ioctl.data, ioctl.data_size)) { + kfree(data); + return -EFAULT; + } + + retval = ecrt_master_write_idn(master, ioctl.slave_position, + ioctl.drive_no, ioctl.idn, data, ioctl.data_size, + &ioctl.error_code); + kfree(data); + if (retval) { + return retval; + } + + if (__copy_to_user((void __user *) arg, &ioctl, sizeof(ioctl))) { + retval = -EFAULT; + } + + EC_MASTER_DBG(master, 1, "Finished SoE write request.\n"); + return retval; +} + +/*****************************************************************************/ + +/** ioctl() function to use. + */ +#ifdef EC_IOCTL_RTDM +#define EC_IOCTL ec_ioctl_rtdm +#else +#define EC_IOCTL ec_ioctl +#endif + +/** Called when an ioctl() command is issued. + * + * \return ioctl() return code. + */ +long EC_IOCTL( + ec_master_t *master, /**< EtherCAT master. */ + ec_ioctl_context_t *ctx, /**< Device context. */ + unsigned int cmd, /**< ioctl() command identifier. */ + void *arg /**< ioctl() argument. */ + ) +{ +#if DEBUG_LATENCY + cycles_t a = get_cycles(), b; + unsigned int t; +#endif + int ret; + + switch (cmd) { + case EC_IOCTL_MODULE: + ret = ec_ioctl_module(arg); + break; + case EC_IOCTL_MASTER: + ret = ec_ioctl_master(master, arg); + break; + case EC_IOCTL_SLAVE: + ret = ec_ioctl_slave(master, arg); + break; + case EC_IOCTL_SLAVE_SYNC: + ret = ec_ioctl_slave_sync(master, arg); + break; + case EC_IOCTL_SLAVE_SYNC_PDO: + ret = ec_ioctl_slave_sync_pdo(master, arg); + break; + case EC_IOCTL_SLAVE_SYNC_PDO_ENTRY: + ret = ec_ioctl_slave_sync_pdo_entry(master, arg); + break; + case EC_IOCTL_DOMAIN: + ret = ec_ioctl_domain(master, arg); + break; + case EC_IOCTL_DOMAIN_FMMU: + ret = ec_ioctl_domain_fmmu(master, arg); + break; + case EC_IOCTL_DOMAIN_DATA: + ret = ec_ioctl_domain_data(master, arg); + break; + case EC_IOCTL_MASTER_DEBUG: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_master_debug(master, arg); + break; + case EC_IOCTL_MASTER_RESCAN: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_master_rescan(master, arg); + break; + case EC_IOCTL_SLAVE_STATE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_state(master, arg); + break; + case EC_IOCTL_SLAVE_SDO: + ret = ec_ioctl_slave_sdo(master, arg); + break; + case EC_IOCTL_SLAVE_SDO_ENTRY: + ret = ec_ioctl_slave_sdo_entry(master, arg); + break; + case EC_IOCTL_SLAVE_SDO_UPLOAD: + ret = ec_ioctl_slave_sdo_upload(master, arg); + break; + case EC_IOCTL_SLAVE_SDO_DOWNLOAD: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_sdo_download(master, arg); + break; + case EC_IOCTL_SLAVE_SII_READ: + ret = ec_ioctl_slave_sii_read(master, arg); + break; + case EC_IOCTL_SLAVE_SII_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_sii_write(master, arg); + break; + case EC_IOCTL_SLAVE_REG_READ: + ret = ec_ioctl_slave_reg_read(master, arg); + break; + case EC_IOCTL_SLAVE_REG_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_reg_write(master, arg); + break; + case EC_IOCTL_SLAVE_FOE_READ: + ret = ec_ioctl_slave_foe_read(master, arg); + break; + case EC_IOCTL_SLAVE_FOE_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_foe_write(master, arg); + break; + case EC_IOCTL_SLAVE_SOE_READ: + ret = ec_ioctl_slave_soe_read(master, arg); + break; + case EC_IOCTL_SLAVE_SOE_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_slave_soe_write(master, arg); + break; + case EC_IOCTL_CONFIG: + ret = ec_ioctl_config(master, arg); + break; + case EC_IOCTL_CONFIG_PDO: + ret = ec_ioctl_config_pdo(master, arg); + break; + case EC_IOCTL_CONFIG_PDO_ENTRY: + ret = ec_ioctl_config_pdo_entry(master, arg); + break; + case EC_IOCTL_CONFIG_SDO: + ret = ec_ioctl_config_sdo(master, arg); + break; + case EC_IOCTL_CONFIG_IDN: + ret = ec_ioctl_config_idn(master, arg); + break; +#ifdef EC_EOE + case EC_IOCTL_EOE_HANDLER: + ret = ec_ioctl_eoe_handler(master, arg); + break; +#endif + case EC_IOCTL_REQUEST: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_request(master, arg, ctx); + break; + case EC_IOCTL_CREATE_DOMAIN: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_create_domain(master, arg, ctx); + break; + case EC_IOCTL_CREATE_SLAVE_CONFIG: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_create_slave_config(master, arg, ctx); + break; + case EC_IOCTL_SELECT_REF_CLOCK: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_select_ref_clock(master, arg, ctx); + break; + case EC_IOCTL_ACTIVATE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_activate(master, arg, ctx); + break; + case EC_IOCTL_DEACTIVATE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_deactivate(master, arg, ctx); + break; + case EC_IOCTL_SEND: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_send(master, arg, ctx); + break; + case EC_IOCTL_RECEIVE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_receive(master, arg, ctx); + break; + case EC_IOCTL_MASTER_STATE: + ret = ec_ioctl_master_state(master, arg, ctx); + break; + case EC_IOCTL_MASTER_LINK_STATE: + ret = ec_ioctl_master_link_state(master, arg, ctx); + break; + case EC_IOCTL_APP_TIME: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_app_time(master, arg, ctx); + break; + case EC_IOCTL_SYNC_REF: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_ref(master, arg, ctx); + break; + case EC_IOCTL_SYNC_REF_TO: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_ref_to(master, arg, ctx); + break; + case EC_IOCTL_SYNC_SLAVES: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_slaves(master, arg, ctx); + break; + case EC_IOCTL_REF_CLOCK_TIME: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_ref_clock_time(master, arg, ctx); + break; + case EC_IOCTL_SYNC_MON_QUEUE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_mon_queue(master, arg, ctx); + break; + case EC_IOCTL_SYNC_MON_PROCESS: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sync_mon_process(master, arg, ctx); + break; + case EC_IOCTL_RESET: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_reset(master, arg, ctx); + break; + case EC_IOCTL_SC_SYNC: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_sync(master, arg, ctx); + break; + case EC_IOCTL_SC_WATCHDOG: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_watchdog(master, arg, ctx); + break; + case EC_IOCTL_SC_ADD_PDO: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_add_pdo(master, arg, ctx); + break; + case EC_IOCTL_SC_CLEAR_PDOS: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_clear_pdos(master, arg, ctx); + break; + case EC_IOCTL_SC_ADD_ENTRY: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_add_entry(master, arg, ctx); + break; + case EC_IOCTL_SC_CLEAR_ENTRIES: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_clear_entries(master, arg, ctx); + break; + case EC_IOCTL_SC_REG_PDO_ENTRY: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_reg_pdo_entry(master, arg, ctx); + break; + case EC_IOCTL_SC_REG_PDO_POS: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_reg_pdo_pos(master, arg, ctx); + break; + case EC_IOCTL_SC_DC: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_dc(master, arg, ctx); + break; + case EC_IOCTL_SC_SDO: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_sdo(master, arg, ctx); + break; + case EC_IOCTL_SC_EMERG_SIZE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_emerg_size(master, arg, ctx); + break; + case EC_IOCTL_SC_EMERG_POP: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_emerg_pop(master, arg, ctx); + break; + case EC_IOCTL_SC_EMERG_CLEAR: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_emerg_clear(master, arg, ctx); + break; + case EC_IOCTL_SC_EMERG_OVERRUNS: + ret = ec_ioctl_sc_emerg_overruns(master, arg, ctx); + break; + case EC_IOCTL_SC_SDO_REQUEST: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_create_sdo_request(master, arg, ctx); + break; + case EC_IOCTL_SC_REG_REQUEST: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_create_reg_request(master, arg, ctx); + break; + case EC_IOCTL_SC_VOE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_create_voe_handler(master, arg, ctx); + break; + case EC_IOCTL_SC_STATE: + ret = ec_ioctl_sc_state(master, arg, ctx); + break; + case EC_IOCTL_SC_IDN: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sc_idn(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_SIZE: + ret = ec_ioctl_domain_size(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_OFFSET: + ret = ec_ioctl_domain_offset(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_PROCESS: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_domain_process(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_QUEUE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_domain_queue(master, arg, ctx); + break; + case EC_IOCTL_DOMAIN_STATE: + ret = ec_ioctl_domain_state(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_INDEX: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sdo_request_index(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_TIMEOUT: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sdo_request_timeout(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_STATE: + ret = ec_ioctl_sdo_request_state(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_READ: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sdo_request_read(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_sdo_request_write(master, arg, ctx); + break; + case EC_IOCTL_SDO_REQUEST_DATA: + ret = ec_ioctl_sdo_request_data(master, arg, ctx); + break; + case EC_IOCTL_REG_REQUEST_DATA: + ret = ec_ioctl_reg_request_data(master, arg, ctx); + break; + case EC_IOCTL_REG_REQUEST_STATE: + ret = ec_ioctl_reg_request_state(master, arg, ctx); + break; + case EC_IOCTL_REG_REQUEST_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_reg_request_write(master, arg, ctx); + break; + case EC_IOCTL_REG_REQUEST_READ: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_reg_request_read(master, arg, ctx); + break; + case EC_IOCTL_VOE_SEND_HEADER: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_send_header(master, arg, ctx); + break; + case EC_IOCTL_VOE_REC_HEADER: + ret = ec_ioctl_voe_rec_header(master, arg, ctx); + break; + case EC_IOCTL_VOE_READ: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_read(master, arg, ctx); + break; + case EC_IOCTL_VOE_READ_NOSYNC: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_read_nosync(master, arg, ctx); + break; + case EC_IOCTL_VOE_WRITE: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_write(master, arg, ctx); + break; + case EC_IOCTL_VOE_EXEC: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_voe_exec(master, arg, ctx); + break; + case EC_IOCTL_VOE_DATA: + ret = ec_ioctl_voe_data(master, arg, ctx); + break; + case EC_IOCTL_SET_SEND_INTERVAL: + if (!ctx->writable) { + ret = -EPERM; + break; + } + ret = ec_ioctl_set_send_interval(master, arg, ctx); + break; + default: + ret = -ENOTTY; + break; + } + +#if DEBUG_LATENCY + b = get_cycles(); + t = (unsigned int) ((b - a) * 1000LL) / cpu_khz; + if (t > 50) { + EC_MASTER_WARN(master, "ioctl(0x%02x) took %u us.\n", + _IOC_NR(cmd), t); + } +#endif + + return ret; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/rtdm.c b/net/ethercat/master/rtdm.c new file mode 100644 index 000000000000..5e6e7bb70206 --- /dev/null +++ b/net/ethercat/master/rtdm.c @@ -0,0 +1,241 @@ +/***************************************************************************** + * + * $Id$ + * + * Copyright (C) 2009-2010 Moehwald GmbH B. Benner + * 2011 IgH Andreas Stewering-Bone + * 2012 Florian Pose <fp@igh-essen.com> + * + * This file is part of the IgH EtherCAT master. + * + * The IgH EtherCAT master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation; version 2 of the License. + * + * The IgH EtherCAT master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT master. If not, see <http://www.gnu.org/licenses/>. + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + ****************************************************************************/ + +/** \file + * RTDM interface. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/mman.h> + +#include <rtdm/rtdm_driver.h> + +#include "master.h" +#include "ioctl.h" +#include "rtdm.h" + +/** Set to 1 to enable device operations debugging. + */ +#define DEBUG 0 + +/****************************************************************************/ + +/** Context structure for an open RTDM file handle. + */ +typedef struct { + rtdm_user_info_t *user_info; /**< RTDM user data. */ + ec_ioctl_context_t ioctl_ctx; /**< Context structure. */ +} ec_rtdm_context_t; + +/****************************************************************************/ + +int ec_rtdm_open(struct rtdm_dev_context *, rtdm_user_info_t *, int); +int ec_rtdm_close(struct rtdm_dev_context *, rtdm_user_info_t *); +int ec_rtdm_ioctl(struct rtdm_dev_context *, rtdm_user_info_t *, + unsigned int, void __user *); + +/****************************************************************************/ + +/** Initialize an RTDM device. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_rtdm_dev_init( + ec_rtdm_dev_t *rtdm_dev, /**< EtherCAT RTDM device. */ + ec_master_t *master /**< EtherCAT master. */ + ) +{ + int ret; + + rtdm_dev->master = master; + + rtdm_dev->dev = kzalloc(sizeof(struct rtdm_device), GFP_KERNEL); + if (!rtdm_dev->dev) { + EC_MASTER_ERR(master, "Failed to reserve memory for RTDM device.\n"); + return -ENOMEM; + } + + rtdm_dev->dev->struct_version = RTDM_DEVICE_STRUCT_VER; + rtdm_dev->dev->device_flags = RTDM_NAMED_DEVICE; + rtdm_dev->dev->context_size = sizeof(ec_rtdm_context_t); + snprintf(rtdm_dev->dev->device_name, RTDM_MAX_DEVNAME_LEN, + "EtherCAT%u", master->index); + rtdm_dev->dev->open_nrt = ec_rtdm_open; + rtdm_dev->dev->ops.close_nrt = ec_rtdm_close; + rtdm_dev->dev->ops.ioctl_rt = ec_rtdm_ioctl; + rtdm_dev->dev->ops.ioctl_nrt = ec_rtdm_ioctl; + rtdm_dev->dev->device_class = RTDM_CLASS_EXPERIMENTAL; + rtdm_dev->dev->device_sub_class = 222; + rtdm_dev->dev->driver_name = "EtherCAT"; + rtdm_dev->dev->driver_version = RTDM_DRIVER_VER(1, 0, 2); + rtdm_dev->dev->peripheral_name = rtdm_dev->dev->device_name; + rtdm_dev->dev->provider_name = "EtherLab Community"; + rtdm_dev->dev->proc_name = rtdm_dev->dev->device_name; + rtdm_dev->dev->device_data = rtdm_dev; /* pointer to parent */ + + EC_MASTER_INFO(master, "Registering RTDM device %s.\n", + rtdm_dev->dev->driver_name); + ret = rtdm_dev_register(rtdm_dev->dev); + if (ret) { + EC_MASTER_ERR(master, "Initialization of RTDM interface failed" + " (return value %i).\n", ret); + kfree(rtdm_dev->dev); + } + + return ret; +} + +/****************************************************************************/ + +/** Clear an RTDM device. + */ +void ec_rtdm_dev_clear( + ec_rtdm_dev_t *rtdm_dev /**< EtherCAT RTDM device. */ + ) +{ + int ret; + + EC_MASTER_INFO(rtdm_dev->master, "Unregistering RTDM device %s.\n", + rtdm_dev->dev->driver_name); + ret = rtdm_dev_unregister(rtdm_dev->dev, 1000 /* poll delay [ms] */); + if (ret < 0) { + EC_MASTER_WARN(rtdm_dev->master, + "Failed to unregister RTDM device (code %i).\n", ret); + } + + kfree(rtdm_dev->dev); +} + +/****************************************************************************/ + +/** Driver open. + * + * \return Always zero (success). + */ +int ec_rtdm_open( + struct rtdm_dev_context *context, /**< Context. */ + rtdm_user_info_t *user_info, /**< User data. */ + int oflags /**< Open flags. */ + ) +{ + ec_rtdm_context_t *ctx = (ec_rtdm_context_t *) context->dev_private; +#if DEBUG + ec_rtdm_dev_t *rtdm_dev = (ec_rtdm_dev_t *) context->device->device_data; +#endif + + ctx->user_info = user_info; + ctx->ioctl_ctx.writable = oflags & O_WRONLY || oflags & O_RDWR; + ctx->ioctl_ctx.requested = 0; + ctx->ioctl_ctx.process_data = NULL; + ctx->ioctl_ctx.process_data_size = 0; + +#if DEBUG + EC_MASTER_INFO(rtdm_dev->master, "RTDM device %s opened.\n", + context->device->device_name); +#endif + return 0; +} + +/****************************************************************************/ + +/** Driver close. + * + * \return Always zero (success). + */ +int ec_rtdm_close( + struct rtdm_dev_context *context, /**< Context. */ + rtdm_user_info_t *user_info /**< User data. */ + ) +{ + ec_rtdm_context_t *ctx = (ec_rtdm_context_t *) context->dev_private; + ec_rtdm_dev_t *rtdm_dev = (ec_rtdm_dev_t *) context->device->device_data; + + if (ctx->ioctl_ctx.requested) { + ecrt_release_master(rtdm_dev->master); + } + +#if DEBUG + EC_MASTER_INFO(rtdm_dev->master, "RTDM device %s closed.\n", + context->device->device_name); +#endif + return 0; +} + +/****************************************************************************/ + +/** Driver ioctl. + * + * \return ioctl() return code. + */ +int ec_rtdm_ioctl( + struct rtdm_dev_context *context, /**< Context. */ + rtdm_user_info_t *user_info, /**< User data. */ + unsigned int request, /**< Request. */ + void __user *arg /**< Argument. */ + ) +{ + ec_rtdm_context_t *ctx = (ec_rtdm_context_t *) context->dev_private; + ec_rtdm_dev_t *rtdm_dev = (ec_rtdm_dev_t *) context->device->device_data; + +#if DEBUG + EC_MASTER_INFO(rtdm_dev->master, "ioctl(request = %u, ctl = %02x)" + " on RTDM device %s.\n", request, _IOC_NR(request), + context->device->device_name); +#endif + return ec_ioctl_rtdm(rtdm_dev->master, &ctx->ioctl_ctx, request, arg); +} + +/****************************************************************************/ + +/** Memory-map process data to user space. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_rtdm_mmap( + ec_ioctl_context_t *ioctl_ctx, /**< Context. */ + void **user_address /**< Userspace address. */ + ) +{ + ec_rtdm_context_t *ctx = + container_of(ioctl_ctx, ec_rtdm_context_t, ioctl_ctx); + int ret; + + ret = rtdm_mmap_to_user(ctx->user_info, + ioctl_ctx->process_data, ioctl_ctx->process_data_size, + PROT_READ | PROT_WRITE, + user_address, + NULL, NULL); + if (ret < 0) { + return ret; + } + + return 0; +} + +/****************************************************************************/ diff --git a/net/ethercat/master/rtdm.h b/net/ethercat/master/rtdm.h new file mode 100644 index 000000000000..3d1b49dfd248 --- /dev/null +++ b/net/ethercat/master/rtdm.h @@ -0,0 +1,56 @@ +/***************************************************************************** + * + * $Id$ + * + * Copyright (C) 2012 Florian Pose <fp@igh-essen.com> + * + * This file is part of the IgH EtherCAT master. + * + * The IgH EtherCAT master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation; version 2 of the License. + * + * The IgH EtherCAT master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT master. If not, see <http://www.gnu.org/licenses/>. + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + ****************************************************************************/ + +/** \file + * RTDM interface. + */ + +#ifndef __EC_RTDM_H__ +#define __EC_RTDM_H__ + +#include "../include/ecrt.h" /* ec_master_t */ + +/*****************************************************************************/ + +struct rtdm_device; + +/*****************************************************************************/ + +/** EtherCAT RTDM device. + */ +typedef struct ec_rtdm_dev { + ec_master_t *master; /**< Master pointer. */ + struct rtdm_device *dev; /**< RTDM device. */ +} ec_rtdm_dev_t; + +/*****************************************************************************/ + +int ec_rtdm_dev_init(ec_rtdm_dev_t *, ec_master_t *); +void ec_rtdm_dev_clear(ec_rtdm_dev_t *); + +/****************************************************************************/ + +#endif diff --git a/net/ethercat/master/sdo.c b/net/ethercat/master/sdo.c new file mode 100644 index 000000000000..6d398e6f05c3 --- /dev/null +++ b/net/ethercat/master/sdo.c @@ -0,0 +1,132 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + CANopen SDO functions. +*/ + +/*****************************************************************************/ + +#include <linux/slab.h> + +#include "master.h" + +#include "sdo.h" + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_sdo_init( + ec_sdo_t *sdo, /**< SDO. */ + ec_slave_t *slave, /**< Parent slave. */ + uint16_t index /**< SDO index. */ + ) +{ + sdo->slave = slave; + sdo->index = index; + sdo->object_code = 0x00; + sdo->name = NULL; + sdo->max_subindex = 0; + INIT_LIST_HEAD(&sdo->entries); +} + +/*****************************************************************************/ + +/** SDO destructor. + * + * Clears and frees an SDO object. + */ +void ec_sdo_clear( + ec_sdo_t *sdo /**< SDO. */ + ) +{ + ec_sdo_entry_t *entry, *next; + + // free all entries + list_for_each_entry_safe(entry, next, &sdo->entries, list) { + list_del(&entry->list); + ec_sdo_entry_clear(entry); + kfree(entry); + } + + if (sdo->name) + kfree(sdo->name); +} + +/*****************************************************************************/ + +/** Get an SDO entry from an SDO via its subindex. + * + * \retval >0 Pointer to the requested SDO entry. + * \retval NULL SDO entry not found. + */ +ec_sdo_entry_t *ec_sdo_get_entry( + ec_sdo_t *sdo, /**< SDO. */ + uint8_t subindex /**< Entry subindex. */ + ) +{ + ec_sdo_entry_t *entry; + + list_for_each_entry(entry, &sdo->entries, list) { + if (entry->subindex != subindex) + continue; + return entry; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Get an SDO entry from an SDO via its subindex. + * + * const version. + * + * \retval >0 Pointer to the requested SDO entry. + * \retval NULL SDO entry not found. + */ +const ec_sdo_entry_t *ec_sdo_get_entry_const( + const ec_sdo_t *sdo, /**< SDO. */ + uint8_t subindex /**< Entry subindex. */ + ) +{ + const ec_sdo_entry_t *entry; + + list_for_each_entry(entry, &sdo->entries, list) { + if (entry->subindex != subindex) + continue; + return entry; + } + + return NULL; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/sdo.h b/net/ethercat/master/sdo.h new file mode 100644 index 000000000000..0af7dae03e24 --- /dev/null +++ b/net/ethercat/master/sdo.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT CANopen SDO structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_SDO_H__ +#define __EC_SDO_H__ + +#include <linux/list.h> + +#include "globals.h" +#include "sdo_entry.h" + +/*****************************************************************************/ + +/** CANopen SDO. + */ +struct ec_sdo { + struct list_head list; /**< List item. */ + ec_slave_t *slave; /**< Parent slave. */ + uint16_t index; /**< SDO index. */ + uint8_t object_code; /**< Object code. */ + char *name; /**< SDO name. */ + uint8_t max_subindex; /**< Maximum subindex. */ + struct list_head entries; /**< List of entries. */ +}; + +/*****************************************************************************/ + +void ec_sdo_init(ec_sdo_t *, ec_slave_t *, uint16_t); +void ec_sdo_clear(ec_sdo_t *); + +ec_sdo_entry_t *ec_sdo_get_entry(ec_sdo_t *, uint8_t); +const ec_sdo_entry_t *ec_sdo_get_entry_const(const ec_sdo_t *, uint8_t); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/sdo_entry.c b/net/ethercat/master/sdo_entry.c new file mode 100644 index 000000000000..acb3364ed2ee --- /dev/null +++ b/net/ethercat/master/sdo_entry.c @@ -0,0 +1,77 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + CANopen over EtherCAT SDO entry functions. +*/ + +/*****************************************************************************/ + +#include <linux/slab.h> + +#include "sdo_entry.h" + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_sdo_entry_init( + ec_sdo_entry_t *entry, /**< SDO entry. */ + ec_sdo_t *sdo, /**< Parent SDO. */ + uint8_t subindex /**< Subindex. */ + ) +{ + entry->sdo = sdo; + entry->subindex = subindex; + entry->data_type = 0x0000; + entry->bit_length = 0; + entry->read_access[EC_SDO_ENTRY_ACCESS_PREOP] = 0; + entry->read_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = 0; + entry->read_access[EC_SDO_ENTRY_ACCESS_OP] = 0; + entry->write_access[EC_SDO_ENTRY_ACCESS_PREOP] = 0; + entry->write_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = 0; + entry->write_access[EC_SDO_ENTRY_ACCESS_OP] = 0; + entry->description = NULL; +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_sdo_entry_clear( + ec_sdo_entry_t *entry /**< SDO entry. */ + ) +{ + + if (entry->description) + kfree(entry->description); +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/sdo_entry.h b/net/ethercat/master/sdo_entry.h new file mode 100644 index 000000000000..b461f3e63b34 --- /dev/null +++ b/net/ethercat/master/sdo_entry.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT CANopen SDO entry structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_SDO_ENTRY_H__ +#define __EC_SDO_ENTRY_H__ + +#include <linux/list.h> +#include <linux/kobject.h> + +#include "globals.h" + +/*****************************************************************************/ + +struct ec_sdo; +typedef struct ec_sdo ec_sdo_t; /**< \see ec_sdo. */ + +/*****************************************************************************/ + +/** CANopen SDO entry. + */ +typedef struct { + struct list_head list; /**< List item. */ + ec_sdo_t *sdo; /**< Parent SDO. */ + uint8_t subindex; /**< Subindex. */ + uint16_t data_type; /**< Data type. */ + uint16_t bit_length; /**< Data size in bit. */ + uint8_t read_access[EC_SDO_ENTRY_ACCESS_COUNT]; /**< Read access. */ + uint8_t write_access[EC_SDO_ENTRY_ACCESS_COUNT]; /**< Write access. */ + char *description; /**< Description. */ +} ec_sdo_entry_t; + +/*****************************************************************************/ + +void ec_sdo_entry_init(ec_sdo_entry_t *, ec_sdo_t *, uint8_t); +void ec_sdo_entry_clear(ec_sdo_entry_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/sdo_request.c b/net/ethercat/master/sdo_request.c new file mode 100644 index 000000000000..a1ba8997b825 --- /dev/null +++ b/net/ethercat/master/sdo_request.c @@ -0,0 +1,258 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * Canopen over EtherCAT SDO request functions. + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/slab.h> + +#include "sdo_request.h" + +/*****************************************************************************/ + +/** Default timeout in ms to wait for SDO transfer responses. + */ +#define EC_SDO_REQUEST_RESPONSE_TIMEOUT 1000 + +/*****************************************************************************/ + +void ec_sdo_request_clear_data(ec_sdo_request_t *); + +/*****************************************************************************/ + +/** SDO request constructor. + */ +void ec_sdo_request_init( + ec_sdo_request_t *req /**< SDO request. */ + ) +{ + req->complete_access = 0; + req->data = NULL; + req->mem_size = 0; + req->data_size = 0; + req->dir = EC_DIR_INVALID; + req->issue_timeout = 0; // no timeout + req->response_timeout = EC_SDO_REQUEST_RESPONSE_TIMEOUT; + req->state = EC_INT_REQUEST_INIT; + req->errno = 0; + req->abort_code = 0x00000000; +} + +/*****************************************************************************/ + +/** SDO request destructor. + */ +void ec_sdo_request_clear( + ec_sdo_request_t *req /**< SDO request. */ + ) +{ + ec_sdo_request_clear_data(req); +} + +/*****************************************************************************/ + +/** Copy another SDO request. + * + * \attention Only the index subindex and data are copied. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_sdo_request_copy( + ec_sdo_request_t *req, /**< SDO request. */ + const ec_sdo_request_t *other /**< Other SDO request to copy from. */ + ) +{ + req->complete_access = other->complete_access; + req->index = other->index; + req->subindex = other->subindex; + return ec_sdo_request_copy_data(req, other->data, other->data_size); +} + +/*****************************************************************************/ + +/** SDO request destructor. + */ +void ec_sdo_request_clear_data( + ec_sdo_request_t *req /**< SDO request. */ + ) +{ + if (req->data) { + kfree(req->data); + req->data = NULL; + } + + req->mem_size = 0; + req->data_size = 0; +} + +/*****************************************************************************/ + +/** Pre-allocates the data memory. + * + * If the \a mem_size is already bigger than \a size, nothing is done. + * + * \return 0 on success, otherwise -ENOMEM. + */ +int ec_sdo_request_alloc( + ec_sdo_request_t *req, /**< SDO request. */ + size_t size /**< Data size to allocate. */ + ) +{ + if (size <= req->mem_size) + return 0; + + ec_sdo_request_clear_data(req); + + if (!(req->data = (uint8_t *) kmalloc(size, GFP_KERNEL))) { + EC_ERR("Failed to allocate %zu bytes of SDO memory.\n", size); + return -ENOMEM; + } + + req->mem_size = size; + req->data_size = 0; + return 0; +} + +/*****************************************************************************/ + +/** Copies SDO data from an external source. + * + * If the \a mem_size is to small, new memory is allocated. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_sdo_request_copy_data( + ec_sdo_request_t *req, /**< SDO request. */ + const uint8_t *source, /**< Source data. */ + size_t size /**< Number of bytes in \a source. */ + ) +{ + int ret = ec_sdo_request_alloc(req, size); + if (ret < 0) + return ret; + + memcpy(req->data, source, size); + req->data_size = size; + return 0; +} + +/*****************************************************************************/ + +/** Checks, if the timeout was exceeded. + * + * \return non-zero if the timeout was exceeded, else zero. + */ +int ec_sdo_request_timed_out(const ec_sdo_request_t *req /**< SDO request. */) +{ + return req->issue_timeout + && jiffies - req->jiffies_start > HZ * req->issue_timeout / 1000; +} + +/***************************************************************************** + * Application interface. + ****************************************************************************/ + +void ecrt_sdo_request_index(ec_sdo_request_t *req, uint16_t index, + uint8_t subindex) +{ + req->index = index; + req->subindex = subindex; +} + +/*****************************************************************************/ + +void ecrt_sdo_request_timeout(ec_sdo_request_t *req, uint32_t timeout) +{ + req->issue_timeout = timeout; +} + +/*****************************************************************************/ + +uint8_t *ecrt_sdo_request_data(ec_sdo_request_t *req) +{ + return req->data; +} + +/*****************************************************************************/ + +size_t ecrt_sdo_request_data_size(const ec_sdo_request_t *req) +{ + return req->data_size; +} + +/*****************************************************************************/ + +ec_request_state_t ecrt_sdo_request_state(const ec_sdo_request_t *req) +{ + return ec_request_state_translation_table[req->state]; +} + +/*****************************************************************************/ + +void ecrt_sdo_request_read(ec_sdo_request_t *req) +{ + req->dir = EC_DIR_INPUT; + req->state = EC_INT_REQUEST_QUEUED; + req->errno = 0; + req->abort_code = 0x00000000; + req->jiffies_start = jiffies; +} + +/*****************************************************************************/ + +void ecrt_sdo_request_write(ec_sdo_request_t *req) +{ + req->dir = EC_DIR_OUTPUT; + req->state = EC_INT_REQUEST_QUEUED; + req->errno = 0; + req->abort_code = 0x00000000; + req->jiffies_start = jiffies; +} + +/*****************************************************************************/ + +/** \cond */ + +EXPORT_SYMBOL(ecrt_sdo_request_index); +EXPORT_SYMBOL(ecrt_sdo_request_timeout); +EXPORT_SYMBOL(ecrt_sdo_request_data); +EXPORT_SYMBOL(ecrt_sdo_request_data_size); +EXPORT_SYMBOL(ecrt_sdo_request_state); +EXPORT_SYMBOL(ecrt_sdo_request_read); +EXPORT_SYMBOL(ecrt_sdo_request_write); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/master/sdo_request.h b/net/ethercat/master/sdo_request.h new file mode 100644 index 000000000000..80b1c101c2d3 --- /dev/null +++ b/net/ethercat/master/sdo_request.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT CANopen SDO request structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_SDO_REQUEST_H__ +#define __EC_SDO_REQUEST_H__ + +#include <linux/list.h> + +#include "globals.h" + +/*****************************************************************************/ + +/** CANopen SDO request. + */ +struct ec_sdo_request { + struct list_head list; /**< List item. */ + uint16_t index; /**< SDO index. */ + uint8_t subindex; /**< SDO subindex. */ + uint8_t *data; /**< Pointer to SDO data. */ + size_t mem_size; /**< Size of SDO data memory. */ + size_t data_size; /**< Size of SDO data. */ + uint8_t complete_access; /**< SDO shall be transferred completely. */ + uint32_t issue_timeout; /**< Maximum time in ms, the processing of the + request may take. */ + uint32_t response_timeout; /**< Maximum time in ms, the transfer is + retried, if the slave does not respond. */ + ec_direction_t dir; /**< Direction. EC_DIR_OUTPUT means downloading to + the slave, EC_DIR_INPUT means uploading from the + slave. */ + ec_internal_request_state_t state; /**< SDO request state. */ + unsigned long jiffies_start; /**< Jiffies, when the request was issued. */ + unsigned long jiffies_sent; /**< Jiffies, when the upload/download + request was sent. */ + int errno; /**< Error number. */ + uint32_t abort_code; /**< SDO request abort code. Zero on success. */ +}; + +/*****************************************************************************/ + +void ec_sdo_request_init(ec_sdo_request_t *); +void ec_sdo_request_clear(ec_sdo_request_t *); + +int ec_sdo_request_copy(ec_sdo_request_t *, const ec_sdo_request_t *); +int ec_sdo_request_alloc(ec_sdo_request_t *, size_t); +int ec_sdo_request_copy_data(ec_sdo_request_t *, const uint8_t *, size_t); +int ec_sdo_request_timed_out(const ec_sdo_request_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/slave.c b/net/ethercat/master/slave.c new file mode 100644 index 000000000000..c081fe3f1c93 --- /dev/null +++ b/net/ethercat/master/slave.c @@ -0,0 +1,1000 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave methods. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/delay.h> + +#include "globals.h" +#include "datagram.h" +#include "master.h" +#include "slave_config.h" + +#include "slave.h" + +/*****************************************************************************/ + +extern const ec_code_msg_t al_status_messages[]; + +/*****************************************************************************/ + +char *ec_slave_sii_string(ec_slave_t *, unsigned int); + +/*****************************************************************************/ + +/** + Slave constructor. + \return 0 in case of success, else < 0 +*/ + +void ec_slave_init( + ec_slave_t *slave, /**< EtherCAT slave */ + ec_master_t *master, /**< EtherCAT master */ + ec_device_index_t dev_idx, /**< Device index. */ + uint16_t ring_position, /**< ring position */ + uint16_t station_address /**< station address to configure */ + ) +{ + unsigned int i; + + slave->master = master; + slave->device_index = dev_idx; + slave->ring_position = ring_position; + slave->station_address = station_address; + slave->effective_alias = 0x0000; + + slave->config = NULL; + slave->requested_state = EC_SLAVE_STATE_PREOP; + slave->current_state = EC_SLAVE_STATE_UNKNOWN; + slave->error_flag = 0; + slave->force_config = 0; + slave->configured_rx_mailbox_offset = 0x0000; + slave->configured_rx_mailbox_size = 0x0000; + slave->configured_tx_mailbox_offset = 0x0000; + slave->configured_tx_mailbox_size = 0x0000; + + slave->base_type = 0; + slave->base_revision = 0; + slave->base_build = 0; + slave->base_fmmu_count = 0; + slave->base_sync_count = 0; + + for (i = 0; i < EC_MAX_PORTS; i++) { + slave->ports[i].desc = EC_PORT_NOT_IMPLEMENTED; + + slave->ports[i].link.link_up = 0; + slave->ports[i].link.loop_closed = 0; + slave->ports[i].link.signal_detected = 0; + slave->sii.physical_layer[i] = 0xFF; + + slave->ports[i].receive_time = 0U; + + slave->ports[i].next_slave = NULL; + slave->ports[i].delay_to_next_dc = 0U; + } + + slave->base_fmmu_bit_operation = 0; + slave->base_dc_supported = 0; + slave->base_dc_range = EC_DC_32; + slave->has_dc_system_time = 0; + slave->transmission_delay = 0U; + + slave->sii_words = NULL; + slave->sii_nwords = 0; + + slave->sii.alias = 0x0000; + slave->sii.vendor_id = 0x00000000; + slave->sii.product_code = 0x00000000; + slave->sii.revision_number = 0x00000000; + slave->sii.serial_number = 0x00000000; + slave->sii.boot_rx_mailbox_offset = 0x0000; + slave->sii.boot_rx_mailbox_size = 0x0000; + slave->sii.boot_tx_mailbox_offset = 0x0000; + slave->sii.boot_tx_mailbox_size = 0x0000; + slave->sii.std_rx_mailbox_offset = 0x0000; + slave->sii.std_rx_mailbox_size = 0x0000; + slave->sii.std_tx_mailbox_offset = 0x0000; + slave->sii.std_tx_mailbox_size = 0x0000; + slave->sii.mailbox_protocols = 0; + + slave->sii.strings = NULL; + slave->sii.string_count = 0; + + slave->sii.has_general = 0; + slave->sii.group = NULL; + slave->sii.image = NULL; + slave->sii.order = NULL; + slave->sii.name = NULL; + memset(&slave->sii.coe_details, 0x00, sizeof(ec_sii_coe_details_t)); + memset(&slave->sii.general_flags, 0x00, sizeof(ec_sii_general_flags_t)); + slave->sii.current_on_ebus = 0; + + slave->sii.syncs = NULL; + slave->sii.sync_count = 0; + + INIT_LIST_HEAD(&slave->sii.pdos); + + INIT_LIST_HEAD(&slave->sdo_dictionary); + + slave->sdo_dictionary_fetched = 0; + slave->jiffies_preop = 0; + + INIT_LIST_HEAD(&slave->sdo_requests); + INIT_LIST_HEAD(&slave->reg_requests); + INIT_LIST_HEAD(&slave->foe_requests); + INIT_LIST_HEAD(&slave->soe_requests); + + // create state machine object + ec_fsm_slave_init(&slave->fsm, slave); +} + +/*****************************************************************************/ + +/** + Slave destructor. + Clears and frees a slave object. +*/ + +void ec_slave_clear(ec_slave_t *slave /**< EtherCAT slave */) +{ + ec_sdo_t *sdo, *next_sdo; + unsigned int i; + ec_pdo_t *pdo, *next_pdo; + + // abort all pending requests + + while (!list_empty(&slave->sdo_requests)) { + ec_sdo_request_t *request = + list_entry(slave->sdo_requests.next, ec_sdo_request_t, list); + list_del_init(&request->list); // dequeue + EC_SLAVE_WARN(slave, "Discarding SDO request," + " slave about to be deleted.\n"); + request->state = EC_INT_REQUEST_FAILURE; + } + + while (!list_empty(&slave->reg_requests)) { + ec_reg_request_t *reg = + list_entry(slave->reg_requests.next, ec_reg_request_t, list); + list_del_init(®->list); // dequeue + EC_SLAVE_WARN(slave, "Discarding register request," + " slave about to be deleted.\n"); + reg->state = EC_INT_REQUEST_FAILURE; + } + + while (!list_empty(&slave->foe_requests)) { + ec_foe_request_t *request = + list_entry(slave->foe_requests.next, ec_foe_request_t, list); + list_del_init(&request->list); // dequeue + EC_SLAVE_WARN(slave, "Discarding FoE request," + " slave about to be deleted.\n"); + request->state = EC_INT_REQUEST_FAILURE; + } + + while (!list_empty(&slave->soe_requests)) { + ec_soe_request_t *request = + list_entry(slave->soe_requests.next, ec_soe_request_t, list); + list_del_init(&request->list); // dequeue + EC_SLAVE_WARN(slave, "Discarding SoE request," + " slave about to be deleted.\n"); + request->state = EC_INT_REQUEST_FAILURE; + } + + wake_up_all(&slave->master->request_queue); + + if (slave->config) { + ec_slave_config_detach(slave->config); + } + + // free all SDOs + list_for_each_entry_safe(sdo, next_sdo, &slave->sdo_dictionary, list) { + list_del(&sdo->list); + ec_sdo_clear(sdo); + kfree(sdo); + } + + // free all strings + if (slave->sii.strings) { + for (i = 0; i < slave->sii.string_count; i++) + kfree(slave->sii.strings[i]); + kfree(slave->sii.strings); + } + + // free all sync managers + ec_slave_clear_sync_managers(slave); + + // free all SII PDOs + list_for_each_entry_safe(pdo, next_pdo, &slave->sii.pdos, list) { + list_del(&pdo->list); + ec_pdo_clear(pdo); + kfree(pdo); + } + + if (slave->sii_words) { + kfree(slave->sii_words); + } + + ec_fsm_slave_clear(&slave->fsm); +} + +/*****************************************************************************/ + +/** Clear the sync manager array. + */ +void ec_slave_clear_sync_managers(ec_slave_t *slave /**< EtherCAT slave. */) +{ + unsigned int i; + + if (slave->sii.syncs) { + for (i = 0; i < slave->sii.sync_count; i++) { + ec_sync_clear(&slave->sii.syncs[i]); + } + kfree(slave->sii.syncs); + slave->sii.syncs = NULL; + } +} + +/*****************************************************************************/ + +/** + * Sets the application state of a slave. + */ + +void ec_slave_set_state(ec_slave_t *slave, /**< EtherCAT slave */ + ec_slave_state_t new_state /**< new application state */ + ) +{ + if (new_state != slave->current_state) { + if (slave->master->debug_level) { + char old_state[EC_STATE_STRING_SIZE], + cur_state[EC_STATE_STRING_SIZE]; + ec_state_string(slave->current_state, old_state, 0); + ec_state_string(new_state, cur_state, 0); + EC_SLAVE_DBG(slave, 0, "%s -> %s.\n", old_state, cur_state); + } + slave->current_state = new_state; + } +} + +/*****************************************************************************/ + +/** + * Request a slave state and resets the error flag. + */ + +void ec_slave_request_state(ec_slave_t *slave, /**< EtherCAT slave */ + ec_slave_state_t state /**< new state */ + ) +{ + slave->requested_state = state; + slave->error_flag = 0; +} + +/*****************************************************************************/ + +/** + Fetches data from a STRING category. + \todo range checking + \return 0 in case of success, else < 0 +*/ + +int ec_slave_fetch_sii_strings( + ec_slave_t *slave, /**< EtherCAT slave */ + const uint8_t *data, /**< category data */ + size_t data_size /**< number of bytes */ + ) +{ + int i, err; + size_t size; + off_t offset; + + slave->sii.string_count = data[0]; + + if (slave->sii.string_count) { + if (!(slave->sii.strings = + kmalloc(sizeof(char *) * slave->sii.string_count, + GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate string array memory.\n"); + err = -ENOMEM; + goto out_zero; + } + + offset = 1; + for (i = 0; i < slave->sii.string_count; i++) { + size = data[offset]; + // allocate memory for string structure and data at a single blow + if (!(slave->sii.strings[i] = + kmalloc(sizeof(char) * size + 1, GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate string memory.\n"); + err = -ENOMEM; + goto out_free; + } + memcpy(slave->sii.strings[i], data + offset + 1, size); + slave->sii.strings[i][size] = 0x00; // append binary zero + offset += 1 + size; + } + } + + return 0; + +out_free: + for (i--; i >= 0; i--) + kfree(slave->sii.strings[i]); + kfree(slave->sii.strings); + slave->sii.strings = NULL; +out_zero: + slave->sii.string_count = 0; + return err; +} + +/*****************************************************************************/ + +/** + Fetches data from a GENERAL category. + \return 0 in case of success, else < 0 +*/ + +int ec_slave_fetch_sii_general( + ec_slave_t *slave, /**< EtherCAT slave */ + const uint8_t *data, /**< category data */ + size_t data_size /**< size in bytes */ + ) +{ + unsigned int i; + uint8_t flags; + + if (data_size != 32) { + EC_SLAVE_ERR(slave, "Wrong size of general category (%zu/32).\n", + data_size); + return -EINVAL; + } + + slave->sii.group = ec_slave_sii_string(slave, data[0]); + slave->sii.image = ec_slave_sii_string(slave, data[1]); + slave->sii.order = ec_slave_sii_string(slave, data[2]); + slave->sii.name = ec_slave_sii_string(slave, data[3]); + + for (i = 0; i < 4; i++) + slave->sii.physical_layer[i] = + (data[4] & (0x03 << (i * 2))) >> (i * 2); + + // read CoE details + flags = EC_READ_U8(data + 5); + slave->sii.coe_details.enable_sdo = (flags >> 0) & 0x01; + slave->sii.coe_details.enable_sdo_info = (flags >> 1) & 0x01; + slave->sii.coe_details.enable_pdo_assign = (flags >> 2) & 0x01; + slave->sii.coe_details.enable_pdo_configuration = (flags >> 3) & 0x01; + slave->sii.coe_details.enable_upload_at_startup = (flags >> 4) & 0x01; + slave->sii.coe_details.enable_sdo_complete_access = (flags >> 5) & 0x01; + + // read general flags + flags = EC_READ_U8(data + 0x000B); + slave->sii.general_flags.enable_safeop = (flags >> 0) & 0x01; + slave->sii.general_flags.enable_not_lrw = (flags >> 1) & 0x01; + + slave->sii.current_on_ebus = EC_READ_S16(data + 0x0C); + slave->sii.has_general = 1; + return 0; +} + +/*****************************************************************************/ + +/** Fetches data from a SYNC MANAGER category. + * + * Appends the sync managers described in the category to the existing ones. + * + * \return 0 in case of success, else < 0 + */ +int ec_slave_fetch_sii_syncs( + ec_slave_t *slave, /**< EtherCAT slave. */ + const uint8_t *data, /**< Category data. */ + size_t data_size /**< Number of bytes. */ + ) +{ + unsigned int i, count, total_count; + ec_sync_t *sync; + size_t memsize; + ec_sync_t *syncs; + uint8_t index; + + // one sync manager struct is 4 words long + if (data_size % 8) { + EC_SLAVE_ERR(slave, "Invalid SII sync manager category size %zu.\n", + data_size); + return -EINVAL; + } + + count = data_size / 8; + + if (count) { + total_count = count + slave->sii.sync_count; + if (total_count > EC_MAX_SYNC_MANAGERS) { + EC_SLAVE_ERR(slave, "Exceeded maximum number of" + " sync managers!\n"); + return -EOVERFLOW; + } + memsize = sizeof(ec_sync_t) * total_count; + if (!(syncs = kmalloc(memsize, GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate %zu bytes" + " for sync managers.\n", memsize); + return -ENOMEM; + } + + for (i = 0; i < slave->sii.sync_count; i++) + ec_sync_init_copy(syncs + i, slave->sii.syncs + i); + + // initialize new sync managers + for (i = 0; i < count; i++, data += 8) { + index = i + slave->sii.sync_count; + sync = &syncs[index]; + + ec_sync_init(sync, slave); + sync->physical_start_address = EC_READ_U16(data); + sync->default_length = EC_READ_U16(data + 2); + sync->control_register = EC_READ_U8(data + 4); + sync->enable = EC_READ_U8(data + 6); + } + + if (slave->sii.syncs) + kfree(slave->sii.syncs); + slave->sii.syncs = syncs; + slave->sii.sync_count = total_count; + } + + return 0; +} + +/*****************************************************************************/ + +/** + Fetches data from a [RT]xPDO category. + \return 0 in case of success, else < 0 +*/ + +int ec_slave_fetch_sii_pdos( + ec_slave_t *slave, /**< EtherCAT slave */ + const uint8_t *data, /**< category data */ + size_t data_size, /**< number of bytes */ + ec_direction_t dir /**< PDO direction. */ + ) +{ + int ret; + ec_pdo_t *pdo; + ec_pdo_entry_t *entry; + unsigned int entry_count, i; + + while (data_size >= 8) { + if (!(pdo = kmalloc(sizeof(ec_pdo_t), GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate PDO memory.\n"); + return -ENOMEM; + } + + ec_pdo_init(pdo); + pdo->index = EC_READ_U16(data); + entry_count = EC_READ_U8(data + 2); + pdo->sync_index = EC_READ_U8(data + 3); + ret = ec_pdo_set_name(pdo, + ec_slave_sii_string(slave, EC_READ_U8(data + 5))); + if (ret) { + ec_pdo_clear(pdo); + kfree(pdo); + return ret; + } + list_add_tail(&pdo->list, &slave->sii.pdos); + + data_size -= 8; + data += 8; + + for (i = 0; i < entry_count; i++) { + if (!(entry = kmalloc(sizeof(ec_pdo_entry_t), GFP_KERNEL))) { + EC_SLAVE_ERR(slave, "Failed to allocate PDO entry memory.\n"); + return -ENOMEM; + } + + ec_pdo_entry_init(entry); + entry->index = EC_READ_U16(data); + entry->subindex = EC_READ_U8(data + 2); + ret = ec_pdo_entry_set_name(entry, + ec_slave_sii_string(slave, EC_READ_U8(data + 3))); + if (ret) { + ec_pdo_entry_clear(entry); + kfree(entry); + return ret; + } + entry->bit_length = EC_READ_U8(data + 5); + list_add_tail(&entry->list, &pdo->entries); + + data_size -= 8; + data += 8; + } + + // if sync manager index is positive, the PDO is mapped by default + if (pdo->sync_index >= 0) { + ec_sync_t *sync; + + if (!(sync = ec_slave_get_sync(slave, pdo->sync_index))) { + EC_SLAVE_ERR(slave, "Invalid SM index %i for PDO 0x%04X.", + pdo->sync_index, pdo->index); + return -ENOENT; + } + + ret = ec_pdo_list_add_pdo_copy(&sync->pdos, pdo); + if (ret) + return ret; + } + } + + return 0; +} + +/*****************************************************************************/ + +/** + Searches the string list for an index. + \return 0 in case of success, else < 0 +*/ + +char *ec_slave_sii_string( + ec_slave_t *slave, /**< EtherCAT slave */ + unsigned int index /**< string index */ + ) +{ + if (!index--) + return NULL; + + if (index >= slave->sii.string_count) { + EC_SLAVE_DBG(slave, 1, "String %u not found.\n", index); + return NULL; + } + + return slave->sii.strings[index]; +} + +/*****************************************************************************/ + +/** Get the sync manager given an index. + * + * \return pointer to sync manager, or NULL. + */ +ec_sync_t *ec_slave_get_sync( + ec_slave_t *slave, /**< EtherCAT slave. */ + uint8_t sync_index /**< Sync manager index. */ + ) +{ + if (sync_index < slave->sii.sync_count) { + return &slave->sii.syncs[sync_index]; + } else { + return NULL; + } +} + +/*****************************************************************************/ + +/** + Counts the total number of SDOs and entries in the dictionary. +*/ + +void ec_slave_sdo_dict_info(const ec_slave_t *slave, /**< EtherCAT slave */ + unsigned int *sdo_count, /**< number of SDOs */ + unsigned int *entry_count /**< total number of + entries */ + ) +{ + unsigned int sdos = 0, entries = 0; + ec_sdo_t *sdo; + ec_sdo_entry_t *entry; + + list_for_each_entry(sdo, &slave->sdo_dictionary, list) { + sdos++; + list_for_each_entry(entry, &sdo->entries, list) { + entries++; + } + } + + *sdo_count = sdos; + *entry_count = entries; +} + +/*****************************************************************************/ + +/** + * Get an SDO from the dictionary. + * \returns The desired SDO, or NULL. + */ + +ec_sdo_t *ec_slave_get_sdo( + ec_slave_t *slave, /**< EtherCAT slave */ + uint16_t index /**< SDO index */ + ) +{ + ec_sdo_t *sdo; + + list_for_each_entry(sdo, &slave->sdo_dictionary, list) { + if (sdo->index != index) + continue; + return sdo; + } + + return NULL; +} + +/*****************************************************************************/ + +/** + * Get an SDO from the dictionary. + * + * const version. + * + * \returns The desired SDO, or NULL. + */ + +const ec_sdo_t *ec_slave_get_sdo_const( + const ec_slave_t *slave, /**< EtherCAT slave */ + uint16_t index /**< SDO index */ + ) +{ + const ec_sdo_t *sdo; + + list_for_each_entry(sdo, &slave->sdo_dictionary, list) { + if (sdo->index != index) + continue; + return sdo; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Get an SDO from the dictionary, given its position in the list. + * \returns The desired SDO, or NULL. + */ + +const ec_sdo_t *ec_slave_get_sdo_by_pos_const( + const ec_slave_t *slave, /**< EtherCAT slave. */ + uint16_t sdo_position /**< SDO list position. */ + ) +{ + const ec_sdo_t *sdo; + + list_for_each_entry(sdo, &slave->sdo_dictionary, list) { + if (sdo_position--) + continue; + return sdo; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Get the number of SDOs in the dictionary. + * \returns SDO count. + */ + +uint16_t ec_slave_sdo_count( + const ec_slave_t *slave /**< EtherCAT slave. */ + ) +{ + const ec_sdo_t *sdo; + uint16_t count = 0; + + list_for_each_entry(sdo, &slave->sdo_dictionary, list) { + count++; + } + + return count; +} + +/*****************************************************************************/ + +/** Finds a mapped PDO. + * \returns The desired PDO object, or NULL. + */ +const ec_pdo_t *ec_slave_find_pdo( + const ec_slave_t *slave, /**< Slave. */ + uint16_t index /**< PDO index to find. */ + ) +{ + unsigned int i; + const ec_sync_t *sync; + const ec_pdo_t *pdo; + + for (i = 0; i < slave->sii.sync_count; i++) { + sync = &slave->sii.syncs[i]; + + if (!(pdo = ec_pdo_list_find_pdo_const(&sync->pdos, index))) + continue; + + return pdo; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Find name for a PDO and its entries. + */ +void ec_slave_find_names_for_pdo( + ec_slave_t *slave, + ec_pdo_t *pdo + ) +{ + const ec_sdo_t *sdo; + ec_pdo_entry_t *pdo_entry; + const ec_sdo_entry_t *sdo_entry; + + list_for_each_entry(sdo, &slave->sdo_dictionary, list) { + if (sdo->index == pdo->index) { + ec_pdo_set_name(pdo, sdo->name); + } else { + list_for_each_entry(pdo_entry, &pdo->entries, list) { + if (sdo->index == pdo_entry->index) { + sdo_entry = ec_sdo_get_entry_const( + sdo, pdo_entry->subindex); + if (sdo_entry) { + ec_pdo_entry_set_name(pdo_entry, + sdo_entry->description); + } + } + } + } + } +} + +/*****************************************************************************/ + +/** Attach PDO names. + */ +void ec_slave_attach_pdo_names( + ec_slave_t *slave + ) +{ + unsigned int i; + ec_sync_t *sync; + ec_pdo_t *pdo; + + for (i = 0; i < slave->sii.sync_count; i++) { + sync = slave->sii.syncs + i; + list_for_each_entry(pdo, &sync->pdos.list, list) { + ec_slave_find_names_for_pdo(slave, pdo); + } + } +} + +/*****************************************************************************/ + +/** Returns the previous connected port of a given port. + * + * \return Port index. + */ +unsigned int ec_slave_get_previous_port( + ec_slave_t *slave, /**< EtherCAT slave. */ + unsigned int port_index /**< Port index. */ + ) +{ + static const unsigned int prev_table[EC_MAX_PORTS] = { + 2, 3, 1, 0 + }; + + if (port_index >= EC_MAX_PORTS) { + EC_SLAVE_WARN(slave, "%s(port_index=%u): Invalid port index!\n", + __func__, port_index); + } + + do { + port_index = prev_table[port_index]; + if (slave->ports[port_index].next_slave) { + return port_index; + } + } while (port_index); + + return 0; +} + +/*****************************************************************************/ + +/** Returns the next connected port of a given port. + * + * \return Port index. + */ +unsigned int ec_slave_get_next_port( + ec_slave_t *slave, /**< EtherCAT slave. */ + unsigned int port_index /**< Port index. */ + ) +{ + static const unsigned int next_table[EC_MAX_PORTS] = { + 3, 2, 0, 1 + }; + + if (port_index >= EC_MAX_PORTS) { + EC_SLAVE_WARN(slave, "%s(port_index=%u): Invalid port index!\n", + __func__, port_index); + } + + do { + port_index = next_table[port_index]; + if (slave->ports[port_index].next_slave) { + return port_index; + } + } while (port_index); + + return 0; +} + +/*****************************************************************************/ + +/** Calculates the sum of round-trip-times of connected ports 1-3. + * + * \return Round-trip-time in ns. + */ +uint32_t ec_slave_calc_rtt_sum( + ec_slave_t *slave /**< EtherCAT slave. */ + ) +{ + uint32_t rtt_sum = 0, rtt; + unsigned int port_index = ec_slave_get_next_port(slave, 0); + + while (port_index != 0) { + unsigned int prev_index = + ec_slave_get_previous_port(slave, port_index); + + rtt = slave->ports[port_index].receive_time - + slave->ports[prev_index].receive_time; + rtt_sum += rtt; + port_index = ec_slave_get_next_port(slave, port_index); + } + + return rtt_sum; +} + +/*****************************************************************************/ + +/** Finds the next slave supporting DC delay measurement. + * + * \return Next DC slave, or NULL. + */ +ec_slave_t *ec_slave_find_next_dc_slave( + ec_slave_t *slave /**< EtherCAT slave. */ + ) +{ + unsigned int port_index; + ec_slave_t *dc_slave = NULL; + + if (slave->base_dc_supported) { + dc_slave = slave; + } else { + port_index = ec_slave_get_next_port(slave, 0); + + while (port_index != 0) { + ec_slave_t *next = slave->ports[port_index].next_slave; + + if (next) { + dc_slave = ec_slave_find_next_dc_slave(next); + + if (dc_slave) { + break; + } + } + port_index = ec_slave_get_next_port(slave, port_index); + } + } + + return dc_slave; +} + +/*****************************************************************************/ + +/** Calculates the port transmission delays. + */ +void ec_slave_calc_port_delays( + ec_slave_t *slave /**< EtherCAT slave. */ + ) +{ + unsigned int port_index; + ec_slave_t *next_slave, *next_dc; + uint32_t rtt, next_rtt_sum; + + if (!slave->base_dc_supported) + return; + + port_index = ec_slave_get_next_port(slave, 0); + + while (port_index != 0) { + next_slave = slave->ports[port_index].next_slave; + next_dc = ec_slave_find_next_dc_slave(next_slave); + + if (next_dc) { + unsigned int prev_port = + ec_slave_get_previous_port(slave, port_index); + + rtt = slave->ports[port_index].receive_time - + slave->ports[prev_port].receive_time; + next_rtt_sum = ec_slave_calc_rtt_sum(next_dc); + + slave->ports[port_index].delay_to_next_dc = + (rtt - next_rtt_sum) / 2; // FIXME + next_dc->ports[0].delay_to_next_dc = + (rtt - next_rtt_sum) / 2; + +#if 0 + EC_SLAVE_DBG(slave, 1, "delay %u:%u rtt=%u" + " next_rtt_sum=%u delay=%u\n", + slave->ring_position, port_index, rtt, next_rtt_sum, + slave->ports[port_index].delay_to_next_dc); +#endif + } + + port_index = ec_slave_get_next_port(slave, port_index); + } +} + +/*****************************************************************************/ + +/** Recursively calculates transmission delays. + */ +void ec_slave_calc_transmission_delays_rec( + ec_slave_t *slave, /**< Current slave. */ + uint32_t *delay /**< Sum of delays. */ + ) +{ + unsigned int i; + ec_slave_t *next_dc; + + EC_SLAVE_DBG(slave, 1, "%s(delay = %u ns)\n", __func__, *delay); + + slave->transmission_delay = *delay; + + i = ec_slave_get_next_port(slave, 0); + + while (i != 0) { + ec_slave_port_t *port = &slave->ports[i]; + next_dc = ec_slave_find_next_dc_slave(port->next_slave); + if (next_dc) { + *delay = *delay + port->delay_to_next_dc; +#if 0 + EC_SLAVE_DBG(slave, 1, "%u:%u %u\n", + slave->ring_position, i, *delay); +#endif + ec_slave_calc_transmission_delays_rec(next_dc, delay); + } + + i = ec_slave_get_next_port(slave, i); + } + + *delay = *delay + slave->ports[0].delay_to_next_dc; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/slave.h b/net/ethercat/master/slave.h new file mode 100644 index 000000000000..b71d57e28208 --- /dev/null +++ b/net/ethercat/master/slave.h @@ -0,0 +1,273 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_SLAVE_H__ +#define __EC_SLAVE_H__ + +#include <linux/list.h> +#include <linux/kobject.h> + +#include "globals.h" +#include "datagram.h" +#include "pdo.h" +#include "sync.h" +#include "sdo.h" +#include "fsm_slave.h" + +/*****************************************************************************/ + +/** Convenience macro for printing slave-specific information to syslog. + * + * This will print the message in \a fmt with a prefixed + * "EtherCAT <INDEX>-<POSITION>: ", where INDEX is the master index and + * POSITION is the slave's ring position. + * + * \param slave EtherCAT slave + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_SLAVE_INFO(slave, fmt, args...) \ + printk(KERN_INFO "EtherCAT %u-%u: " fmt, slave->master->index, \ + slave->ring_position, ##args) + +/** Convenience macro for printing slave-specific errors to syslog. + * + * This will print the message in \a fmt with a prefixed + * "EtherCAT <INDEX>-<POSITION>: ", where INDEX is the master index and + * POSITION is the slave's ring position. + * + * \param slave EtherCAT slave + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_SLAVE_ERR(slave, fmt, args...) \ + printk(KERN_ERR "EtherCAT ERROR %u-%u: " fmt, slave->master->index, \ + slave->ring_position, ##args) + +/** Convenience macro for printing slave-specific warnings to syslog. + * + * This will print the message in \a fmt with a prefixed + * "EtherCAT <INDEX>-<POSITION>: ", where INDEX is the master index and + * POSITION is the slave's ring position. + * + * \param slave EtherCAT slave + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_SLAVE_WARN(slave, fmt, args...) \ + printk(KERN_WARNING "EtherCAT WARNING %u-%u: " fmt, \ + slave->master->index, slave->ring_position, ##args) + +/** Convenience macro for printing slave-specific debug messages to syslog. + * + * This will print the message in \a fmt with a prefixed + * "EtherCAT <INDEX>-<POSITION>: ", where INDEX is the master index and + * POSITION is the slave's ring position. + * + * \param slave EtherCAT slave + * \param level Debug level. Master's debug level must be >= \a level for + * output. + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_SLAVE_DBG(slave, level, fmt, args...) \ + do { \ + if (slave->master->debug_level >= level) { \ + printk(KERN_DEBUG "EtherCAT DEBUG %u-%u: " fmt, \ + slave->master->index, slave->ring_position, ##args); \ + } \ + } while (0) + +/*****************************************************************************/ + +/** Slave port. + */ +typedef struct { + ec_slave_port_desc_t desc; /**< Port descriptors. */ + ec_slave_port_link_t link; /**< Port link status. */ + ec_slave_t *next_slave; /**< Connected slaves. */ + uint32_t receive_time; /**< Port receive times for delay + measurement. */ + uint32_t delay_to_next_dc; /**< Delay to next slave with DC support behind + this port [ns]. */ +} ec_slave_port_t; + +/*****************************************************************************/ + +/** Slave information interface data. + */ +typedef struct { + // Non-category data + uint16_t alias; /**< Configured station alias. */ + uint32_t vendor_id; /**< Vendor ID. */ + uint32_t product_code; /**< Vendor-specific product code. */ + uint32_t revision_number; /**< Revision number. */ + uint32_t serial_number; /**< Serial number. */ + uint16_t boot_rx_mailbox_offset; /**< Bootstrap receive mailbox address. */ + uint16_t boot_rx_mailbox_size; /**< Bootstrap receive mailbox size. */ + uint16_t boot_tx_mailbox_offset; /**< Bootstrap transmit mailbox address. */ + uint16_t boot_tx_mailbox_size; /**< Bootstrap transmit mailbox size. */ + uint16_t std_rx_mailbox_offset; /**< Standard receive mailbox address. */ + uint16_t std_rx_mailbox_size; /**< Standard receive mailbox size. */ + uint16_t std_tx_mailbox_offset; /**< Standard transmit mailbox address. */ + uint16_t std_tx_mailbox_size; /**< Standard transmit mailbox size. */ + uint16_t mailbox_protocols; /**< Supported mailbox protocols. */ + + // Strings + char **strings; /**< Strings in SII categories. */ + unsigned int string_count; /**< Number of SII strings. */ + + // General + unsigned int has_general; /**< General category present. */ + char *group; /**< Group name. */ + char *image; /**< Image name. */ + char *order; /**< Order number. */ + char *name; /**< Slave name. */ + uint8_t physical_layer[EC_MAX_PORTS]; /**< Port media. */ + ec_sii_coe_details_t coe_details; /**< CoE detail flags. */ + ec_sii_general_flags_t general_flags; /**< General flags. */ + int16_t current_on_ebus; /**< Power consumption in mA. */ + + // SyncM + ec_sync_t *syncs; /**< SYNC MANAGER categories. */ + unsigned int sync_count; /**< Number of sync managers. */ + + // [RT]XPDO + struct list_head pdos; /**< SII [RT]XPDO categories. */ +} ec_sii_t; + +/*****************************************************************************/ + +/** EtherCAT slave. + */ +struct ec_slave +{ + ec_master_t *master; /**< Master owning the slave. */ + ec_device_index_t device_index; /**< Index of device the slave responds + on. */ + + // addresses + uint16_t ring_position; /**< Ring position. */ + uint16_t station_address; /**< Configured station address. */ + uint16_t effective_alias; /**< Effective alias address. */ + + ec_slave_port_t ports[EC_MAX_PORTS]; /**< Ports. */ + + // configuration + ec_slave_config_t *config; /**< Current configuration. */ + ec_slave_state_t requested_state; /**< Requested application state. */ + ec_slave_state_t current_state; /**< Current application state. */ + unsigned int error_flag; /**< Stop processing after an error. */ + unsigned int force_config; /**< Force (re-)configuration. */ + uint16_t configured_rx_mailbox_offset; /**< Configured receive mailbox + offset. */ + uint16_t configured_rx_mailbox_size; /**< Configured receive mailbox size. + */ + uint16_t configured_tx_mailbox_offset; /**< Configured send mailbox + offset. */ + uint16_t configured_tx_mailbox_size; /**< Configured send mailbox size. */ + + // base data + uint8_t base_type; /**< Slave type. */ + uint8_t base_revision; /**< Revision. */ + uint16_t base_build; /**< Build number. */ + uint8_t base_fmmu_count; /**< Number of supported FMMUs. */ + uint8_t base_sync_count; /**< Number of supported sync managers. */ + uint8_t base_fmmu_bit_operation; /**< FMMU bit operation is supported. */ + uint8_t base_dc_supported; /**< Distributed clocks are supported. */ + ec_slave_dc_range_t base_dc_range; /**< DC range. */ + uint8_t has_dc_system_time; /**< The slave supports the DC system time + register. Otherwise it can only be used for + delay measurement. */ + uint32_t transmission_delay; /**< DC system time transmission delay + (offset from reference clock). */ + + // SII + uint16_t *sii_words; /**< Complete SII image. */ + size_t sii_nwords; /**< Size of the SII contents in words. */ + + // Slave information interface + ec_sii_t sii; /**< Extracted SII data. */ + + struct list_head sdo_dictionary; /**< SDO dictionary list */ + uint8_t sdo_dictionary_fetched; /**< Dictionary has been fetched. */ + unsigned long jiffies_preop; /**< Time, the slave went to PREOP. */ + + struct list_head sdo_requests; /**< SDO access requests. */ + struct list_head reg_requests; /**< Register access requests. */ + struct list_head foe_requests; /**< FoE write requests. */ + struct list_head soe_requests; /**< SoE write requests. */ + + ec_fsm_slave_t fsm; /**< Slave state machine. */ +}; + +/*****************************************************************************/ + +// slave construction/destruction +void ec_slave_init(ec_slave_t *, ec_master_t *, ec_device_index_t, + uint16_t, uint16_t); +void ec_slave_clear(ec_slave_t *); + +void ec_slave_clear_sync_managers(ec_slave_t *); + +void ec_slave_request_state(ec_slave_t *, ec_slave_state_t); +void ec_slave_set_state(ec_slave_t *, ec_slave_state_t); + +// SII categories +int ec_slave_fetch_sii_strings(ec_slave_t *, const uint8_t *, size_t); +int ec_slave_fetch_sii_general(ec_slave_t *, const uint8_t *, size_t); +int ec_slave_fetch_sii_syncs(ec_slave_t *, const uint8_t *, size_t); +int ec_slave_fetch_sii_pdos(ec_slave_t *, const uint8_t *, size_t, + ec_direction_t); + +// misc. +ec_sync_t *ec_slave_get_sync(ec_slave_t *, uint8_t); + +void ec_slave_sdo_dict_info(const ec_slave_t *, + unsigned int *, unsigned int *); +ec_sdo_t *ec_slave_get_sdo(ec_slave_t *, uint16_t); +const ec_sdo_t *ec_slave_get_sdo_const(const ec_slave_t *, uint16_t); +const ec_sdo_t *ec_slave_get_sdo_by_pos_const(const ec_slave_t *, uint16_t); +uint16_t ec_slave_sdo_count(const ec_slave_t *); +const ec_pdo_t *ec_slave_find_pdo(const ec_slave_t *, uint16_t); +void ec_slave_attach_pdo_names(ec_slave_t *); + +void ec_slave_calc_port_delays(ec_slave_t *); +void ec_slave_calc_transmission_delays_rec(ec_slave_t *, uint32_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/slave_config.c b/net/ethercat/master/slave_config.c new file mode 100644 index 000000000000..cf5d501f9f0c --- /dev/null +++ b/net/ethercat/master/slave_config.c @@ -0,0 +1,1269 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + * vim: expandtab + * + *****************************************************************************/ + +/** + \file + EtherCAT slave configuration methods. +*/ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/slab.h> + +#include "globals.h" +#include "master.h" +#include "voe_handler.h" + +#include "slave_config.h" + +/*****************************************************************************/ + +/** Slave configuration constructor. + * + * See ecrt_master_slave_config() for the usage of the \a alias and \a + * position parameters. + */ +void ec_slave_config_init( + ec_slave_config_t *sc, /**< Slave configuration. */ + ec_master_t *master, /**< EtherCAT master. */ + uint16_t alias, /**< Slave alias. */ + uint16_t position, /**< Slave position. */ + uint32_t vendor_id, /**< Expected vendor ID. */ + uint32_t product_code /**< Expected product code. */ + ) +{ + unsigned int i; + + sc->master = master; + + sc->alias = alias; + sc->position = position; + sc->vendor_id = vendor_id; + sc->product_code = product_code; + sc->watchdog_divider = 0; // use default + sc->watchdog_intervals = 0; // use default + + sc->slave = NULL; + + for (i = 0; i < EC_MAX_SYNC_MANAGERS; i++) + ec_sync_config_init(&sc->sync_configs[i]); + + sc->used_fmmus = 0; + sc->dc_assign_activate = 0x0000; + sc->dc_sync[0].cycle_time = 0U; + sc->dc_sync[1].cycle_time = 0; + sc->dc_sync[0].shift_time = 0U; + sc->dc_sync[1].shift_time = 0; + + INIT_LIST_HEAD(&sc->sdo_configs); + INIT_LIST_HEAD(&sc->sdo_requests); + INIT_LIST_HEAD(&sc->reg_requests); + INIT_LIST_HEAD(&sc->voe_handlers); + INIT_LIST_HEAD(&sc->soe_configs); + + ec_coe_emerg_ring_init(&sc->emerg_ring, sc); +} + +/*****************************************************************************/ + +/** Slave configuration destructor. + * + * Clears and frees a slave configuration object. + */ +void ec_slave_config_clear( + ec_slave_config_t *sc /**< Slave configuration. */ + ) +{ + unsigned int i; + ec_sdo_request_t *req, *next_req; + ec_voe_handler_t *voe, *next_voe; + ec_reg_request_t *reg, *next_reg; + ec_soe_request_t *soe, *next_soe; + + ec_slave_config_detach(sc); + + // Free sync managers + for (i = 0; i < EC_MAX_SYNC_MANAGERS; i++) + ec_sync_config_clear(&sc->sync_configs[i]); + + // free all SDO configurations + list_for_each_entry_safe(req, next_req, &sc->sdo_configs, list) { + list_del(&req->list); + ec_sdo_request_clear(req); + kfree(req); + } + + // free all SDO requests + list_for_each_entry_safe(req, next_req, &sc->sdo_requests, list) { + list_del(&req->list); + ec_sdo_request_clear(req); + kfree(req); + } + + // free all register requests + list_for_each_entry_safe(reg, next_reg, &sc->reg_requests, list) { + list_del(®->list); + ec_reg_request_clear(reg); + kfree(reg); + } + + // free all VoE handlers + list_for_each_entry_safe(voe, next_voe, &sc->voe_handlers, list) { + list_del(&voe->list); + ec_voe_handler_clear(voe); + kfree(voe); + } + + // free all SoE configurations + list_for_each_entry_safe(soe, next_soe, &sc->soe_configs, list) { + list_del(&soe->list); + ec_soe_request_clear(soe); + kfree(soe); + } + + ec_coe_emerg_ring_clear(&sc->emerg_ring); +} + +/*****************************************************************************/ + +/** Prepares an FMMU configuration. + * + * Configuration data for the FMMU is saved in the slave config structure and + * is written to the slave during the configuration. The FMMU configuration + * is done in a way, that the complete data range of the corresponding sync + * manager is covered. Seperate FMMUs are configured for each domain. If the + * FMMU configuration is already prepared, the function does nothing and + * returns with success. + * + * \retval >=0 Success, logical offset byte address. + * \retval <0 Error code. + */ +int ec_slave_config_prepare_fmmu( + ec_slave_config_t *sc, /**< Slave configuration. */ + ec_domain_t *domain, /**< Domain. */ + uint8_t sync_index, /**< Sync manager index. */ + ec_direction_t dir /**< PDO direction. */ + ) +{ + unsigned int i; + ec_fmmu_config_t *fmmu; + + // FMMU configuration already prepared? + for (i = 0; i < sc->used_fmmus; i++) { + fmmu = &sc->fmmu_configs[i]; + if (fmmu->domain == domain && fmmu->sync_index == sync_index) + return fmmu->logical_start_address; + } + + if (sc->used_fmmus == EC_MAX_FMMUS) { + EC_CONFIG_ERR(sc, "FMMU limit reached!\n"); + return -EOVERFLOW; + } + + fmmu = &sc->fmmu_configs[sc->used_fmmus++]; + + down(&sc->master->master_sem); + ec_fmmu_config_init(fmmu, sc, domain, sync_index, dir); + up(&sc->master->master_sem); + + return fmmu->logical_start_address; +} + +/*****************************************************************************/ + +/** Attaches the configuration to the addressed slave object. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_slave_config_attach( + ec_slave_config_t *sc /**< Slave configuration. */ + ) +{ + ec_slave_t *slave; + + if (sc->slave) + return 0; // already attached + + if (!(slave = ec_master_find_slave( + sc->master, sc->alias, sc->position))) { + EC_CONFIG_DBG(sc, 1, "Failed to find slave for configuration.\n"); + return -ENOENT; + } + + if (slave->config) { + EC_CONFIG_DBG(sc, 1, "Failed to attach configuration. Slave %u" + " already has a configuration!\n", slave->ring_position); + return -EEXIST; + } + + if ( +#ifdef EC_IDENT_WILDCARDS + sc->vendor_id != 0xffffffff && +#endif + slave->sii.vendor_id != sc->vendor_id + ) { + EC_CONFIG_DBG(sc, 1, "Slave %u has no matching vendor ID (0x%08X)" + " for configuration (0x%08X).\n", + slave->ring_position, slave->sii.vendor_id, sc->vendor_id); + return -EINVAL; + } + + if ( +#ifdef EC_IDENT_WILDCARDS + sc->product_code != 0xffffffff && +#endif + slave->sii.product_code != sc->product_code + ) { + EC_CONFIG_DBG(sc, 1, "Slave %u has no matching product code (0x%08X)" + " for configuration (0x%08X).\n", + slave->ring_position, slave->sii.product_code, + sc->product_code); + return -EINVAL; + } + + // attach slave + slave->config = sc; + sc->slave = slave; + + EC_CONFIG_DBG(sc, 1, "Attached slave %u.\n", slave->ring_position); + return 0; +} + +/*****************************************************************************/ + +/** Detaches the configuration from a slave object. + */ +void ec_slave_config_detach( + ec_slave_config_t *sc /**< Slave configuration. */ + ) +{ + if (sc->slave) { + ec_reg_request_t *reg; + + sc->slave->config = NULL; + + // invalidate processing register request + list_for_each_entry(reg, &sc->reg_requests, list) { + if (sc->slave->fsm.reg_request == reg) { + sc->slave->fsm.reg_request = NULL; + break; + } + } + + sc->slave = NULL; + } +} + +/*****************************************************************************/ + +/** Loads the default PDO assignment from the slave object. + */ +void ec_slave_config_load_default_sync_config(ec_slave_config_t *sc) +{ + uint8_t sync_index; + ec_sync_config_t *sync_config; + const ec_sync_t *sync; + + if (!sc->slave) + return; + + for (sync_index = 0; sync_index < EC_MAX_SYNC_MANAGERS; sync_index++) { + sync_config = &sc->sync_configs[sync_index]; + if ((sync = ec_slave_get_sync(sc->slave, sync_index))) { + sync_config->dir = ec_sync_default_direction(sync); + if (sync_config->dir == EC_DIR_INVALID) + EC_SLAVE_WARN(sc->slave, + "SM%u has an invalid direction field!\n", sync_index); + ec_pdo_list_copy(&sync_config->pdos, &sync->pdos); + } + } +} + +/*****************************************************************************/ + +/** Loads the default mapping for a PDO from the slave object. + */ +void ec_slave_config_load_default_mapping( + const ec_slave_config_t *sc, + ec_pdo_t *pdo + ) +{ + unsigned int i; + const ec_sync_t *sync; + const ec_pdo_t *default_pdo; + + if (!sc->slave) + return; + + EC_CONFIG_DBG(sc, 1, "Loading default mapping for PDO 0x%04X.\n", + pdo->index); + + // find PDO in any sync manager (it could be reassigned later) + for (i = 0; i < sc->slave->sii.sync_count; i++) { + sync = &sc->slave->sii.syncs[i]; + + list_for_each_entry(default_pdo, &sync->pdos.list, list) { + if (default_pdo->index != pdo->index) + continue; + + if (default_pdo->name) { + EC_CONFIG_DBG(sc, 1, "Found PDO name \"%s\".\n", + default_pdo->name); + + // take PDO name from assigned one + ec_pdo_set_name(pdo, default_pdo->name); + } + + // copy entries (= default PDO mapping) + if (ec_pdo_copy_entries(pdo, default_pdo)) + return; + + if (sc->master->debug_level) { + const ec_pdo_entry_t *entry; + list_for_each_entry(entry, &pdo->entries, list) { + EC_CONFIG_DBG(sc, 1, "Entry 0x%04X:%02X.\n", + entry->index, entry->subindex); + } + } + + return; + } + } + + EC_CONFIG_DBG(sc, 1, "No default mapping found.\n"); +} + +/*****************************************************************************/ + +/** Get the number of SDO configurations. + * + * \return Number of SDO configurations. + */ +unsigned int ec_slave_config_sdo_count( + const ec_slave_config_t *sc /**< Slave configuration. */ + ) +{ + const ec_sdo_request_t *req; + unsigned int count = 0; + + list_for_each_entry(req, &sc->sdo_configs, list) { + count++; + } + + return count; +} + +/*****************************************************************************/ + +/** Finds an SDO configuration via its position in the list. + * + * Const version. + * + * \return Search result, or NULL. + */ +const ec_sdo_request_t *ec_slave_config_get_sdo_by_pos_const( + const ec_slave_config_t *sc, /**< Slave configuration. */ + unsigned int pos /**< Position in the list. */ + ) +{ + const ec_sdo_request_t *req; + + list_for_each_entry(req, &sc->sdo_configs, list) { + if (pos--) + continue; + return req; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Get the number of IDN configurations. + * + * \return Number of SDO configurations. + */ +unsigned int ec_slave_config_idn_count( + const ec_slave_config_t *sc /**< Slave configuration. */ + ) +{ + const ec_soe_request_t *req; + unsigned int count = 0; + + list_for_each_entry(req, &sc->soe_configs, list) { + count++; + } + + return count; +} + +/*****************************************************************************/ + +/** Finds an IDN configuration via its position in the list. + * + * Const version. + * + * \return Search result, or NULL. + */ +const ec_soe_request_t *ec_slave_config_get_idn_by_pos_const( + const ec_slave_config_t *sc, /**< Slave configuration. */ + unsigned int pos /**< Position in the list. */ + ) +{ + const ec_soe_request_t *req; + + list_for_each_entry(req, &sc->soe_configs, list) { + if (pos--) + continue; + return req; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Finds a CoE handler via its position in the list. + * + * \return Search result, or NULL. + */ +ec_sdo_request_t *ec_slave_config_find_sdo_request( + ec_slave_config_t *sc, /**< Slave configuration. */ + unsigned int pos /**< Position in the list. */ + ) +{ + ec_sdo_request_t *req; + + list_for_each_entry(req, &sc->sdo_requests, list) { + if (pos--) + continue; + return req; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Finds a register handler via its position in the list. + * + * \return Search result, or NULL. + */ +ec_reg_request_t *ec_slave_config_find_reg_request( + ec_slave_config_t *sc, /**< Slave configuration. */ + unsigned int pos /**< Position in the list. */ + ) +{ + ec_reg_request_t *reg; + + list_for_each_entry(reg, &sc->reg_requests, list) { + if (pos--) + continue; + return reg; + } + + return NULL; +} + +/*****************************************************************************/ + +/** Finds a VoE handler via its position in the list. + * + * \return Search result, or NULL. + */ +ec_voe_handler_t *ec_slave_config_find_voe_handler( + ec_slave_config_t *sc, /**< Slave configuration. */ + unsigned int pos /**< Position in the list. */ + ) +{ + ec_voe_handler_t *voe; + + list_for_each_entry(voe, &sc->voe_handlers, list) { + if (pos--) + continue; + return voe; + } + + return NULL; +} + +/****************************************************************************** + * Application interface + *****************************************************************************/ + +int ecrt_slave_config_sync_manager(ec_slave_config_t *sc, uint8_t sync_index, + ec_direction_t dir, ec_watchdog_mode_t watchdog_mode) +{ + ec_sync_config_t *sync_config; + + EC_CONFIG_DBG(sc, 1, "ecrt_slave_config_sync_manager(sc = 0x%p," + " sync_index = %u, dir = %i, watchdog_mode = %i)\n", + sc, sync_index, dir, watchdog_mode); + + if (sync_index >= EC_MAX_SYNC_MANAGERS) { + EC_CONFIG_ERR(sc, "Invalid sync manager index %u!\n", sync_index); + return -ENOENT; + } + + if (dir != EC_DIR_OUTPUT && dir != EC_DIR_INPUT) { + EC_CONFIG_ERR(sc, "Invalid direction %u!\n", (unsigned int) dir); + return -EINVAL; + } + + sync_config = &sc->sync_configs[sync_index]; + sync_config->dir = dir; + sync_config->watchdog_mode = watchdog_mode; + return 0; +} + +/*****************************************************************************/ + +void ecrt_slave_config_watchdog(ec_slave_config_t *sc, + uint16_t divider, uint16_t intervals) +{ + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, divider = %u, intervals = %u)\n", + __func__, sc, divider, intervals); + + sc->watchdog_divider = divider; + sc->watchdog_intervals = intervals; +} + +/*****************************************************************************/ + +int ecrt_slave_config_pdo_assign_add(ec_slave_config_t *sc, + uint8_t sync_index, uint16_t pdo_index) +{ + ec_pdo_t *pdo; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, sync_index = %u, " + "pdo_index = 0x%04X)\n", __func__, sc, sync_index, pdo_index); + + if (sync_index >= EC_MAX_SYNC_MANAGERS) { + EC_CONFIG_ERR(sc, "Invalid sync manager index %u!\n", sync_index); + return -EINVAL; + } + + down(&sc->master->master_sem); + + pdo = ec_pdo_list_add_pdo(&sc->sync_configs[sync_index].pdos, pdo_index); + if (IS_ERR(pdo)) { + up(&sc->master->master_sem); + return PTR_ERR(pdo); + } + pdo->sync_index = sync_index; + + ec_slave_config_load_default_mapping(sc, pdo); + + up(&sc->master->master_sem); + return 0; +} + +/*****************************************************************************/ + +void ecrt_slave_config_pdo_assign_clear(ec_slave_config_t *sc, + uint8_t sync_index) +{ + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, sync_index = %u)\n", + __func__, sc, sync_index); + + if (sync_index >= EC_MAX_SYNC_MANAGERS) { + EC_CONFIG_ERR(sc, "Invalid sync manager index %u!\n", sync_index); + return; + } + + down(&sc->master->master_sem); + ec_pdo_list_clear_pdos(&sc->sync_configs[sync_index].pdos); + up(&sc->master->master_sem); +} + +/*****************************************************************************/ + +int ecrt_slave_config_pdo_mapping_add(ec_slave_config_t *sc, + uint16_t pdo_index, uint16_t entry_index, uint8_t entry_subindex, + uint8_t entry_bit_length) +{ + uint8_t sync_index; + ec_pdo_t *pdo = NULL; + ec_pdo_entry_t *entry; + int retval = 0; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, " + "pdo_index = 0x%04X, entry_index = 0x%04X, " + "entry_subindex = 0x%02X, entry_bit_length = %u)\n", + __func__, sc, pdo_index, entry_index, entry_subindex, + entry_bit_length); + + for (sync_index = 0; sync_index < EC_MAX_SYNC_MANAGERS; sync_index++) + if ((pdo = ec_pdo_list_find_pdo( + &sc->sync_configs[sync_index].pdos, pdo_index))) + break; + + if (pdo) { + down(&sc->master->master_sem); + entry = ec_pdo_add_entry(pdo, entry_index, entry_subindex, + entry_bit_length); + up(&sc->master->master_sem); + if (IS_ERR(entry)) + retval = PTR_ERR(entry); + } else { + EC_CONFIG_ERR(sc, "PDO 0x%04X is not assigned.\n", pdo_index); + retval = -ENOENT; + } + + return retval; +} + +/*****************************************************************************/ + +void ecrt_slave_config_pdo_mapping_clear(ec_slave_config_t *sc, + uint16_t pdo_index) +{ + uint8_t sync_index; + ec_pdo_t *pdo = NULL; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, pdo_index = 0x%04X)\n", + __func__, sc, pdo_index); + + for (sync_index = 0; sync_index < EC_MAX_SYNC_MANAGERS; sync_index++) + if ((pdo = ec_pdo_list_find_pdo( + &sc->sync_configs[sync_index].pdos, pdo_index))) + break; + + if (pdo) { + down(&sc->master->master_sem); + ec_pdo_clear_entries(pdo); + up(&sc->master->master_sem); + } else { + EC_CONFIG_WARN(sc, "PDO 0x%04X is not assigned.\n", pdo_index); + } +} + +/*****************************************************************************/ + +int ecrt_slave_config_pdos(ec_slave_config_t *sc, + unsigned int n_syncs, const ec_sync_info_t syncs[]) +{ + int ret; + unsigned int i, j, k; + const ec_sync_info_t *sync_info; + const ec_pdo_info_t *pdo_info; + const ec_pdo_entry_info_t *entry_info; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, n_syncs = %u, syncs = 0x%p)\n", + __func__, sc, n_syncs, syncs); + + if (!syncs) + return 0; + + for (i = 0; i < n_syncs; i++) { + sync_info = &syncs[i]; + + if (sync_info->index == (uint8_t) EC_END) + break; + + if (sync_info->index >= EC_MAX_SYNC_MANAGERS) { + EC_CONFIG_ERR(sc, "Invalid sync manager index %u!\n", + sync_info->index); + return -ENOENT; + } + + ret = ecrt_slave_config_sync_manager(sc, sync_info->index, + sync_info->dir, sync_info->watchdog_mode); + if (ret) + return ret; + + ecrt_slave_config_pdo_assign_clear(sc, sync_info->index); + + if (sync_info->n_pdos && sync_info->pdos) { + + for (j = 0; j < sync_info->n_pdos; j++) { + pdo_info = &sync_info->pdos[j]; + + ret = ecrt_slave_config_pdo_assign_add( + sc, sync_info->index, pdo_info->index); + if (ret) + return ret; + + ecrt_slave_config_pdo_mapping_clear(sc, pdo_info->index); + + if (pdo_info->n_entries && pdo_info->entries) { + for (k = 0; k < pdo_info->n_entries; k++) { + entry_info = &pdo_info->entries[k]; + + ret = ecrt_slave_config_pdo_mapping_add(sc, + pdo_info->index, entry_info->index, + entry_info->subindex, + entry_info->bit_length); + if (ret) + return ret; + } + } + } + } + } + + return 0; +} + +/*****************************************************************************/ + +int ecrt_slave_config_reg_pdo_entry( + ec_slave_config_t *sc, + uint16_t index, + uint8_t subindex, + ec_domain_t *domain, + unsigned int *bit_position + ) +{ + uint8_t sync_index; + const ec_sync_config_t *sync_config; + unsigned int bit_offset, bit_pos; + ec_pdo_t *pdo; + ec_pdo_entry_t *entry; + int sync_offset; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, index = 0x%04X, " + "subindex = 0x%02X, domain = 0x%p, bit_position = 0x%p)\n", + __func__, sc, index, subindex, domain, bit_position); + + for (sync_index = 0; sync_index < EC_MAX_SYNC_MANAGERS; sync_index++) { + sync_config = &sc->sync_configs[sync_index]; + bit_offset = 0; + + list_for_each_entry(pdo, &sync_config->pdos.list, list) { + list_for_each_entry(entry, &pdo->entries, list) { + if (entry->index != index || entry->subindex != subindex) { + bit_offset += entry->bit_length; + } else { + bit_pos = bit_offset % 8; + if (bit_position) { + *bit_position = bit_pos; + } else if (bit_pos) { + EC_CONFIG_ERR(sc, "PDO entry 0x%04X:%02X does" + " not byte-align.\n", index, subindex); + return -EFAULT; + } + + sync_offset = ec_slave_config_prepare_fmmu( + sc, domain, sync_index, sync_config->dir); + if (sync_offset < 0) + return sync_offset; + + return sync_offset + bit_offset / 8; + } + } + } + } + + EC_CONFIG_ERR(sc, "PDO entry 0x%04X:%02X is not mapped.\n", + index, subindex); + return -ENOENT; +} + +/*****************************************************************************/ + +int ecrt_slave_config_reg_pdo_entry_pos( + ec_slave_config_t *sc, + uint8_t sync_index, + unsigned int pdo_pos, + unsigned int entry_pos, + ec_domain_t *domain, + unsigned int *bit_position + ) +{ + const ec_sync_config_t *sync_config; + unsigned int bit_offset, pp, ep; + ec_pdo_t *pdo; + ec_pdo_entry_t *entry; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, sync_index = %u, pdo_pos = %u," + " entry_pos = %u, domain = 0x%p, bit_position = 0x%p)\n", + __func__, sc, sync_index, pdo_pos, entry_pos, + domain, bit_position); + + if (sync_index >= EC_MAX_SYNC_MANAGERS) { + EC_CONFIG_ERR(sc, "Invalid syncmanager position %u.\n", sync_index); + return -EINVAL; + } + + sync_config = &sc->sync_configs[sync_index]; + bit_offset = 0; + pp = 0; + + list_for_each_entry(pdo, &sync_config->pdos.list, list) { + ep = 0; + list_for_each_entry(entry, &pdo->entries, list) { + if (pp != pdo_pos || ep != entry_pos) { + bit_offset += entry->bit_length; + } else { + unsigned int bit_pos = bit_offset % 8; + int sync_offset; + + if (bit_position) { + *bit_position = bit_pos; + } else if (bit_pos) { + EC_CONFIG_ERR(sc, "PDO entry 0x%04X:%02X does" + " not byte-align.\n", + pdo->index, entry->subindex); + return -EFAULT; + } + + sync_offset = ec_slave_config_prepare_fmmu( + sc, domain, sync_index, sync_config->dir); + if (sync_offset < 0) + return sync_offset; + + return sync_offset + bit_offset / 8; + } + ep++; + } + pp++; + } + + EC_CONFIG_ERR(sc, "PDO entry specification %u/%u/%u out of range.\n", + sync_index, pdo_pos, entry_pos); + return -ENOENT; +} + +/*****************************************************************************/ + +void ecrt_slave_config_dc(ec_slave_config_t *sc, uint16_t assign_activate, + uint32_t sync0_cycle_time, int32_t sync0_shift_time, + uint32_t sync1_cycle_time, int32_t sync1_shift_time) +{ + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, assign_activate = 0x%04X," + " sync0_cycle = %u, sync0_shift = %i," + " sync1_cycle = %u, sync1_shift = %i\n", + __func__, sc, assign_activate, sync0_cycle_time, sync0_shift_time, + sync1_cycle_time, sync1_shift_time); + + sc->dc_assign_activate = assign_activate; + sc->dc_sync[0].cycle_time = sync0_cycle_time; + sc->dc_sync[0].shift_time = sync0_shift_time; + sc->dc_sync[1].cycle_time = sync1_cycle_time; + sc->dc_sync[1].shift_time = sync1_shift_time; +} + +/*****************************************************************************/ + +int ecrt_slave_config_sdo(ec_slave_config_t *sc, uint16_t index, + uint8_t subindex, const uint8_t *data, size_t size) +{ + ec_slave_t *slave = sc->slave; + ec_sdo_request_t *req; + int ret; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, index = 0x%04X, " + "subindex = 0x%02X, data = 0x%p, size = %zu)\n", + __func__, sc, index, subindex, data, size); + + if (slave && !(slave->sii.mailbox_protocols & EC_MBOX_COE)) { + EC_CONFIG_WARN(sc, "Attached slave does not support CoE!\n"); + } + + if (!(req = (ec_sdo_request_t *) + kmalloc(sizeof(ec_sdo_request_t), GFP_KERNEL))) { + EC_CONFIG_ERR(sc, "Failed to allocate memory for" + " SDO configuration!\n"); + return -ENOMEM; + } + + ec_sdo_request_init(req); + ecrt_sdo_request_index(req, index, subindex); + + ret = ec_sdo_request_copy_data(req, data, size); + if (ret < 0) { + ec_sdo_request_clear(req); + kfree(req); + return ret; + } + + down(&sc->master->master_sem); + list_add_tail(&req->list, &sc->sdo_configs); + up(&sc->master->master_sem); + return 0; +} + +/*****************************************************************************/ + +int ecrt_slave_config_sdo8(ec_slave_config_t *sc, uint16_t index, + uint8_t subindex, uint8_t value) +{ + uint8_t data[1]; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, index = 0x%04X, " + "subindex = 0x%02X, value = %u)\n", + __func__, sc, index, subindex, (unsigned int) value); + + EC_WRITE_U8(data, value); + return ecrt_slave_config_sdo(sc, index, subindex, data, 1); +} + +/*****************************************************************************/ + +int ecrt_slave_config_sdo16(ec_slave_config_t *sc, uint16_t index, + uint8_t subindex, uint16_t value) +{ + uint8_t data[2]; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, index = 0x%04X, " + "subindex = 0x%02X, value = %u)\n", + __func__, sc, index, subindex, value); + + EC_WRITE_U16(data, value); + return ecrt_slave_config_sdo(sc, index, subindex, data, 2); +} + +/*****************************************************************************/ + +int ecrt_slave_config_sdo32(ec_slave_config_t *sc, uint16_t index, + uint8_t subindex, uint32_t value) +{ + uint8_t data[4]; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, index = 0x%04X, " + "subindex = 0x%02X, value = %u)\n", + __func__, sc, index, subindex, value); + + EC_WRITE_U32(data, value); + return ecrt_slave_config_sdo(sc, index, subindex, data, 4); +} + +/*****************************************************************************/ + +int ecrt_slave_config_complete_sdo(ec_slave_config_t *sc, uint16_t index, + const uint8_t *data, size_t size) +{ + ec_slave_t *slave = sc->slave; + ec_sdo_request_t *req; + int ret; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, index = 0x%04X, " + "data = 0x%p, size = %zu)\n", __func__, sc, index, data, size); + + if (slave && !(slave->sii.mailbox_protocols & EC_MBOX_COE)) { + EC_CONFIG_WARN(sc, "Attached slave does not support CoE!\n"); + } + + if (!(req = (ec_sdo_request_t *) + kmalloc(sizeof(ec_sdo_request_t), GFP_KERNEL))) { + EC_CONFIG_ERR(sc, "Failed to allocate memory for" + " SDO configuration!\n"); + return -ENOMEM; + } + + ec_sdo_request_init(req); + ecrt_sdo_request_index(req, index, 0); + req->complete_access = 1; + + ret = ec_sdo_request_copy_data(req, data, size); + if (ret < 0) { + ec_sdo_request_clear(req); + kfree(req); + return ret; + } + + down(&sc->master->master_sem); + list_add_tail(&req->list, &sc->sdo_configs); + up(&sc->master->master_sem); + return 0; +} + +/*****************************************************************************/ + +int ecrt_slave_config_emerg_size(ec_slave_config_t *sc, size_t elements) +{ + return ec_coe_emerg_ring_size(&sc->emerg_ring, elements); +} + +/*****************************************************************************/ + +int ecrt_slave_config_emerg_pop(ec_slave_config_t *sc, uint8_t *target) +{ + return ec_coe_emerg_ring_pop(&sc->emerg_ring, target); +} + +/*****************************************************************************/ + +int ecrt_slave_config_emerg_clear(ec_slave_config_t *sc) +{ + return ec_coe_emerg_ring_clear_ring(&sc->emerg_ring); +} + +/*****************************************************************************/ + +int ecrt_slave_config_emerg_overruns(ec_slave_config_t *sc) +{ + return ec_coe_emerg_ring_overruns(&sc->emerg_ring); +} + +/*****************************************************************************/ + +/** Same as ecrt_slave_config_create_sdo_request(), but with ERR_PTR() return + * value. + */ +ec_sdo_request_t *ecrt_slave_config_create_sdo_request_err( + ec_slave_config_t *sc, uint16_t index, uint8_t subindex, size_t size) +{ + ec_sdo_request_t *req; + int ret; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, " + "index = 0x%04X, subindex = 0x%02X, size = %zu)\n", + __func__, sc, index, subindex, size); + + if (!(req = (ec_sdo_request_t *) + kmalloc(sizeof(ec_sdo_request_t), GFP_KERNEL))) { + EC_CONFIG_ERR(sc, "Failed to allocate SDO request memory!\n"); + return ERR_PTR(-ENOMEM); + } + + ec_sdo_request_init(req); + ecrt_sdo_request_index(req, index, subindex); + + ret = ec_sdo_request_alloc(req, size); + if (ret < 0) { + ec_sdo_request_clear(req); + kfree(req); + return ERR_PTR(ret); + } + + // prepare data for optional writing + memset(req->data, 0x00, size); + req->data_size = size; + + down(&sc->master->master_sem); + list_add_tail(&req->list, &sc->sdo_requests); + up(&sc->master->master_sem); + + return req; +} + +/*****************************************************************************/ + +ec_sdo_request_t *ecrt_slave_config_create_sdo_request( + ec_slave_config_t *sc, uint16_t index, uint8_t subindex, size_t size) +{ + ec_sdo_request_t *s = ecrt_slave_config_create_sdo_request_err(sc, index, + subindex, size); + return IS_ERR(s) ? NULL : s; +} + +/*****************************************************************************/ + +/** Same as ecrt_slave_config_create_reg_request(), but with ERR_PTR() return + * value. + */ +ec_reg_request_t *ecrt_slave_config_create_reg_request_err( + ec_slave_config_t *sc, size_t size) +{ + ec_reg_request_t *reg; + int ret; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, size = %zu)\n", + __func__, sc, size); + + if (!(reg = (ec_reg_request_t *) + kmalloc(sizeof(ec_reg_request_t), GFP_KERNEL))) { + EC_CONFIG_ERR(sc, "Failed to allocate register request memory!\n"); + return ERR_PTR(-ENOMEM); + } + + ret = ec_reg_request_init(reg, size); + if (ret) { + kfree(reg); + return ERR_PTR(ret); + } + + down(&sc->master->master_sem); + list_add_tail(®->list, &sc->reg_requests); + up(&sc->master->master_sem); + + return reg; +} + +/*****************************************************************************/ + +ec_reg_request_t *ecrt_slave_config_create_reg_request( + ec_slave_config_t *sc, size_t size) +{ + ec_reg_request_t *reg = + ecrt_slave_config_create_reg_request_err(sc, size); + return IS_ERR(reg) ? NULL : reg; +} + +/*****************************************************************************/ + +/** Same as ecrt_slave_config_create_voe_handler(), but with ERR_PTR() return + * value. + */ +ec_voe_handler_t *ecrt_slave_config_create_voe_handler_err( + ec_slave_config_t *sc, size_t size) +{ + ec_voe_handler_t *voe; + int ret; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, size = %zu)\n", __func__, sc, size); + + if (!(voe = (ec_voe_handler_t *) + kmalloc(sizeof(ec_voe_handler_t), GFP_KERNEL))) { + EC_CONFIG_ERR(sc, "Failed to allocate VoE request memory!\n"); + return ERR_PTR(-ENOMEM); + } + + ret = ec_voe_handler_init(voe, sc, size); + if (ret < 0) { + kfree(voe); + return ERR_PTR(ret); + } + + down(&sc->master->master_sem); + list_add_tail(&voe->list, &sc->voe_handlers); + up(&sc->master->master_sem); + + return voe; +} + +/*****************************************************************************/ + +ec_voe_handler_t *ecrt_slave_config_create_voe_handler( + ec_slave_config_t *sc, size_t size) +{ + ec_voe_handler_t *voe = ecrt_slave_config_create_voe_handler_err(sc, + size); + return IS_ERR(voe) ? NULL : voe; +} + +/*****************************************************************************/ + +void ecrt_slave_config_state(const ec_slave_config_t *sc, + ec_slave_config_state_t *state) +{ + state->online = sc->slave ? 1 : 0; + if (state->online) { + state->operational = + sc->slave->current_state == EC_SLAVE_STATE_OP + && !sc->slave->force_config; + state->al_state = sc->slave->current_state; + } else { + state->operational = 0; + state->al_state = EC_SLAVE_STATE_UNKNOWN; + } +} + +/*****************************************************************************/ + +int ecrt_slave_config_idn(ec_slave_config_t *sc, uint8_t drive_no, + uint16_t idn, ec_al_state_t state, const uint8_t *data, + size_t size) +{ + ec_slave_t *slave = sc->slave; + ec_soe_request_t *req; + int ret; + + EC_CONFIG_DBG(sc, 1, "%s(sc = 0x%p, drive_no = %u, idn = 0x%04X, " + "state = %u, data = 0x%p, size = %zu)\n", + __func__, sc, drive_no, idn, state, data, size); + + if (drive_no > 7) { + EC_CONFIG_ERR(sc, "Invalid drive number %u!\n", + (unsigned int) drive_no); + return -EINVAL; + } + + if (state != EC_AL_STATE_PREOP && state != EC_AL_STATE_SAFEOP) { + EC_CONFIG_ERR(sc, "AL state for IDN config" + " must be PREOP or SAFEOP!\n"); + return -EINVAL; + } + + if (slave && !(slave->sii.mailbox_protocols & EC_MBOX_SOE)) { + EC_CONFIG_WARN(sc, "Attached slave does not support SoE!\n"); + } + + if (!(req = (ec_soe_request_t *) + kmalloc(sizeof(ec_soe_request_t), GFP_KERNEL))) { + EC_CONFIG_ERR(sc, "Failed to allocate memory for" + " IDN configuration!\n"); + return -ENOMEM; + } + + ec_soe_request_init(req); + ec_soe_request_set_drive_no(req, drive_no); + ec_soe_request_set_idn(req, idn); + req->al_state = state; + + ret = ec_soe_request_copy_data(req, data, size); + if (ret < 0) { + ec_soe_request_clear(req); + kfree(req); + return ret; + } + + down(&sc->master->master_sem); + list_add_tail(&req->list, &sc->soe_configs); + up(&sc->master->master_sem); + return 0; +} + +/*****************************************************************************/ + +/** \cond */ + +EXPORT_SYMBOL(ecrt_slave_config_sync_manager); +EXPORT_SYMBOL(ecrt_slave_config_watchdog); +EXPORT_SYMBOL(ecrt_slave_config_pdo_assign_add); +EXPORT_SYMBOL(ecrt_slave_config_pdo_assign_clear); +EXPORT_SYMBOL(ecrt_slave_config_pdo_mapping_add); +EXPORT_SYMBOL(ecrt_slave_config_pdo_mapping_clear); +EXPORT_SYMBOL(ecrt_slave_config_pdos); +EXPORT_SYMBOL(ecrt_slave_config_reg_pdo_entry); +EXPORT_SYMBOL(ecrt_slave_config_dc); +EXPORT_SYMBOL(ecrt_slave_config_sdo); +EXPORT_SYMBOL(ecrt_slave_config_sdo8); +EXPORT_SYMBOL(ecrt_slave_config_sdo16); +EXPORT_SYMBOL(ecrt_slave_config_sdo32); +EXPORT_SYMBOL(ecrt_slave_config_complete_sdo); +EXPORT_SYMBOL(ecrt_slave_config_emerg_size); +EXPORT_SYMBOL(ecrt_slave_config_emerg_pop); +EXPORT_SYMBOL(ecrt_slave_config_emerg_clear); +EXPORT_SYMBOL(ecrt_slave_config_emerg_overruns); +EXPORT_SYMBOL(ecrt_slave_config_create_sdo_request); +EXPORT_SYMBOL(ecrt_slave_config_create_voe_handler); +EXPORT_SYMBOL(ecrt_slave_config_create_reg_request); +EXPORT_SYMBOL(ecrt_slave_config_state); +EXPORT_SYMBOL(ecrt_slave_config_idn); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/master/slave_config.h b/net/ethercat/master/slave_config.h new file mode 100644 index 000000000000..9fd633dcb2b9 --- /dev/null +++ b/net/ethercat/master/slave_config.h @@ -0,0 +1,185 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT slave configuration structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_SLAVE_CONFIG_H__ +#define __EC_SLAVE_CONFIG_H__ + +#include <linux/list.h> + +#include "globals.h" +#include "slave.h" +#include "sync_config.h" +#include "fmmu_config.h" +#include "coe_emerg_ring.h" + +/*****************************************************************************/ + +/** Convenience macro for printing configuration-specific information to + * syslog. + * + * This will print the message in \a fmt with a prefixed + * "EtherCAT <INDEX> <ALIAS>:<POSITION>: ", where INDEX is the master index + * and ALIAS and POSITION identify the configuration. + * + * \param sc EtherCAT slave configuration + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_CONFIG_INFO(sc, fmt, args...) \ + printk(KERN_INFO "EtherCAT %u %u:%u: " fmt, sc->master->index, \ + sc->alias, sc->position, ##args) + +/** Convenience macro for printing configuration-specific errors to syslog. + * + * This will print the message in \a fmt with a prefixed + * "EtherCAT <INDEX> <ALIAS>:<POSITION>: ", where INDEX is the master index + * and ALIAS and POSITION identify the configuration. + * + * \param sc EtherCAT slave configuration + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_CONFIG_ERR(sc, fmt, args...) \ + printk(KERN_ERR "EtherCAT ERROR %u %u:%u: " fmt, sc->master->index, \ + sc->alias, sc->position, ##args) + +/** Convenience macro for printing configuration-specific warnings to syslog. + * + * This will print the message in \a fmt with a prefixed + * "EtherCAT <INDEX> <ALIAS>:<POSITION>: ", where INDEX is the master index + * and ALIAS and POSITION identify the configuration. + * + * \param sc EtherCAT slave configuration + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_CONFIG_WARN(sc, fmt, args...) \ + printk(KERN_WARNING "EtherCAT WARNING %u %u:%u: " fmt, \ + sc->master->index, sc->alias, sc->position, ##args) + +/** Convenience macro for printing configuration-specific debug messages to + * syslog. + * + * This will print the message in \a fmt with a prefixed + * "EtherCAT <INDEX> <ALIAS>:<POSITION>: ", where INDEX is the master index + * and ALIAS and POSITION identify the configuration. + * + * \param sc EtherCAT slave configuration + * \param level Debug level. Master's debug level must be >= \a level for + * output. + * \param fmt format string (like in printf()) + * \param args arguments (optional) + */ +#define EC_CONFIG_DBG(sc, level, fmt, args...) \ + do { \ + if (sc->master->debug_level >= level) { \ + printk(KERN_DEBUG "EtherCAT DEBUG %u %u:%u: " fmt, \ + sc->master->index, sc->alias, sc->position, ##args); \ + } \ + } while (0) + +/*****************************************************************************/ + +/** EtherCAT slave configuration. + */ +struct ec_slave_config { + struct list_head list; /**< List item. */ + ec_master_t *master; /**< Master owning the slave configuration. */ + + uint16_t alias; /**< Slave alias. */ + uint16_t position; /**< Index after alias. If alias is zero, this is the + ring position. */ + uint32_t vendor_id; /**< Slave vendor ID. */ + uint32_t product_code; /**< Slave product code. */ + + uint16_t watchdog_divider; /**< Watchdog divider as a number of 40ns + intervals (see spec. reg. 0x0400). */ + uint16_t watchdog_intervals; /**< Process data watchdog intervals (see + spec. reg. 0x0420). */ + + ec_slave_t *slave; /**< Slave pointer. This is \a NULL, if the slave is + offline. */ + + ec_sync_config_t sync_configs[EC_MAX_SYNC_MANAGERS]; /**< Sync manager + configurations. */ + ec_fmmu_config_t fmmu_configs[EC_MAX_FMMUS]; /**< FMMU configurations. */ + uint8_t used_fmmus; /**< Number of FMMUs used. */ + uint16_t dc_assign_activate; /**< Vendor-specific AssignActivate word. */ + ec_sync_signal_t dc_sync[EC_SYNC_SIGNAL_COUNT]; /**< DC sync signals. */ + + struct list_head sdo_configs; /**< List of SDO configurations. */ + struct list_head sdo_requests; /**< List of SDO requests. */ + struct list_head voe_handlers; /**< List of VoE handlers. */ + struct list_head reg_requests; /**< List of register requests. */ + struct list_head soe_configs; /**< List of SoE configurations. */ + + ec_coe_emerg_ring_t emerg_ring; /**< CoE emergency ring buffer. */ +}; + +/*****************************************************************************/ + +void ec_slave_config_init(ec_slave_config_t *, ec_master_t *, uint16_t, + uint16_t, uint32_t, uint32_t); +void ec_slave_config_clear(ec_slave_config_t *); + +int ec_slave_config_attach(ec_slave_config_t *); +void ec_slave_config_detach(ec_slave_config_t *); + +void ec_slave_config_load_default_sync_config(ec_slave_config_t *); + +unsigned int ec_slave_config_sdo_count(const ec_slave_config_t *); +const ec_sdo_request_t *ec_slave_config_get_sdo_by_pos_const( + const ec_slave_config_t *, unsigned int); +unsigned int ec_slave_config_idn_count(const ec_slave_config_t *); +const ec_soe_request_t *ec_slave_config_get_idn_by_pos_const( + const ec_slave_config_t *, unsigned int); +ec_sdo_request_t *ec_slave_config_find_sdo_request(ec_slave_config_t *, + unsigned int); +ec_reg_request_t *ec_slave_config_find_reg_request(ec_slave_config_t *, + unsigned int); +ec_voe_handler_t *ec_slave_config_find_voe_handler(ec_slave_config_t *, + unsigned int); + +ec_sdo_request_t *ecrt_slave_config_create_sdo_request_err( + ec_slave_config_t *, uint16_t, uint8_t, size_t); +ec_voe_handler_t *ecrt_slave_config_create_voe_handler_err( + ec_slave_config_t *, size_t); +ec_reg_request_t *ecrt_slave_config_create_reg_request_err( + ec_slave_config_t *, size_t); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/soe_errors.c b/net/ethercat/master/soe_errors.c new file mode 100644 index 000000000000..b7d1c5dc619a --- /dev/null +++ b/net/ethercat/master/soe_errors.c @@ -0,0 +1,96 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT SoE errors. +*/ + +/*****************************************************************************/ + +#include "globals.h" + +/*****************************************************************************/ + +/** SoE error codes. + */ +const ec_code_msg_t soe_error_codes[] = { + {0x1001, "No IDN"}, + {0x1009, "Invalid access to element 1"}, + {0x2001, "No name"}, + {0x2002, "Name transmission too short"}, + {0x2003, "Name transmission too long"}, + {0x2004, "Name cannot be changed, read only"}, + {0x2005, "Name is write protected at this time"}, + {0x3002, "Attribute transmission too short"}, + {0x3003, "Attribute transmission too long"}, + {0x3004, "Attribute cannot be changed, read only"}, + {0x3005, "Attribute is write protected at this time"}, + {0x4001, "No unit"}, + {0x4002, "Unit transmission too short"}, + {0x4003, "Unit transmission too long"}, + {0x4004, "Unit cannot be changed, read only"}, + {0x4005, "Unit is write proteced at this time"}, + {0x5001, "No minimum input value"}, + {0x5002, "Minimum input value transmission too short"}, + {0x5003, "Minimum input value transmission too long"}, + {0x5004, "Minimum input value cannot be changed, read only"}, + {0x5005, "Minimum input value is write protected at this time"}, + {0x6001, "No maximum input value"}, + {0x6002, "Maximum input value transmission too short"}, + {0x6003, "Maximum input value transmission too long"}, + {0x6004, "Maximum input value cannot be changed, read only"}, + {0x6005, "Maximum input value is write protected at this time"}, + {0x7002, "Operation data value transmission too short"}, + {0x7003, "Operation data value transmission too long"}, + {0x7004, "Operation data value cannot be changed, read only"}, + {0x7005, "Operation data value is write protected at this time"}, + {0x7006, "Operation data value is smaller than the minimum input value"}, + {0x7007, "Operation data value is greater than the minimum input value"}, + {0x7008, "Invalid operation data"}, + {0x7009, "Operation data is write protected by a password"}, + {0x700A, "Operation data is write protected"}, + {0x700B, "Invalid indirect addressing"}, + {0x700C, "Operation data is write protected due to other settings"}, + {0x700D, "Reserved"}, + {0x7010, "Procedure command already active"}, + {0x7011, "Procedure command not interruptible"}, + {0x7012, "Procedure command is at this time not executable"}, + {0x7013, "Procedure command not executable"}, + {0x7014, "No data state"}, + {0x8001, "No default value"}, + {0x8002, "Default value transmission too long"}, + {0x8004, "Default value cannot be changed, read only"}, + {0x800A, "Invalid drive number"}, + {0x800B, "General error"}, + {0x800C, "No element addressed"}, + {} +}; + +/*****************************************************************************/ diff --git a/net/ethercat/master/soe_request.c b/net/ethercat/master/soe_request.c new file mode 100644 index 000000000000..4ad903533d76 --- /dev/null +++ b/net/ethercat/master/soe_request.c @@ -0,0 +1,254 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * Sercos-over-EtherCAT request functions. + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/slab.h> + +#include "soe_request.h" + +/*****************************************************************************/ + +/** Default timeout in ms to wait for SoE responses. + */ +#define EC_SOE_REQUEST_RESPONSE_TIMEOUT 1000 + +/*****************************************************************************/ + +void ec_soe_request_clear_data(ec_soe_request_t *); + +/*****************************************************************************/ + +/** SoE request constructor. + */ +void ec_soe_request_init( + ec_soe_request_t *req /**< SoE request. */ + ) +{ + INIT_LIST_HEAD(&req->list); + req->drive_no = 0x00; + req->idn = 0x0000; + req->al_state = EC_AL_STATE_INIT; + req->data = NULL; + req->mem_size = 0; + req->data_size = 0; + req->dir = EC_DIR_INVALID; + req->state = EC_INT_REQUEST_INIT; + req->jiffies_sent = 0U; + req->error_code = 0x0000; +} + +/*****************************************************************************/ + +/** SoE request destructor. + */ +void ec_soe_request_clear( + ec_soe_request_t *req /**< SoE request. */ + ) +{ + ec_soe_request_clear_data(req); +} + +/*****************************************************************************/ + +/** Copy another SoE request. + * + * \return Zero on success, otherwise a negative error code. + */ +int ec_soe_request_copy( + ec_soe_request_t *req, /**< SoE request. */ + const ec_soe_request_t *other /**< Other SoE request to copy from. */ + ) +{ + req->drive_no = other->drive_no; + req->idn = other->idn; + req->al_state = other->al_state; + return ec_soe_request_copy_data(req, other->data, other->data_size); +} + +/*****************************************************************************/ + +/** Set drive number. + */ +void ec_soe_request_set_drive_no( + ec_soe_request_t *req, /**< SoE request. */ + uint8_t drive_no /** Drive Number. */ + ) +{ + req->drive_no = drive_no; +} + +/*****************************************************************************/ + +/** Set IDN. + */ +void ec_soe_request_set_idn( + ec_soe_request_t *req, /**< SoE request. */ + uint16_t idn /** IDN. */ + ) +{ + req->idn = idn; +} + +/*****************************************************************************/ + +/** Free allocated memory. + */ +void ec_soe_request_clear_data( + ec_soe_request_t *req /**< SoE request. */ + ) +{ + if (req->data) { + kfree(req->data); + req->data = NULL; + } + + req->mem_size = 0; + req->data_size = 0; +} + +/*****************************************************************************/ + +/** Pre-allocates the data memory. + * + * If the \a mem_size is already bigger than \a size, nothing is done. + * + * \return 0 on success, otherwise -ENOMEM. + */ +int ec_soe_request_alloc( + ec_soe_request_t *req, /**< SoE request. */ + size_t size /**< Data size to allocate. */ + ) +{ + if (size <= req->mem_size) + return 0; + + ec_soe_request_clear_data(req); + + if (!(req->data = (uint8_t *) kmalloc(size, GFP_KERNEL))) { + EC_ERR("Failed to allocate %zu bytes of SoE memory.\n", size); + return -ENOMEM; + } + + req->mem_size = size; + req->data_size = 0; + return 0; +} + +/*****************************************************************************/ + +/** Copies SoE data from an external source. + * + * If the \a mem_size is to small, new memory is allocated. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_soe_request_copy_data( + ec_soe_request_t *req, /**< SoE request. */ + const uint8_t *source, /**< Source data. */ + size_t size /**< Number of bytes in \a source. */ + ) +{ + int ret = ec_soe_request_alloc(req, size); + if (ret < 0) + return ret; + + memcpy(req->data, source, size); + req->data_size = size; + return 0; +} + +/*****************************************************************************/ + +/** Copies SoE data from an external source. + * + * If the \a mem_size is to small, new memory is allocated. + * + * \retval 0 Success. + * \retval <0 Error code. + */ +int ec_soe_request_append_data( + ec_soe_request_t *req, /**< SoE request. */ + const uint8_t *source, /**< Source data. */ + size_t size /**< Number of bytes in \a source. */ + ) +{ + if (req->data_size + size > req->mem_size) { + size_t new_size = req->mem_size ? req->mem_size * 2 : size; + uint8_t *new_data = (uint8_t *) kmalloc(new_size, GFP_KERNEL); + if (!new_data) { + EC_ERR("Failed to allocate %zu bytes of SoE memory.\n", + new_size); + return -ENOMEM; + } + memcpy(new_data, req->data, req->data_size); + kfree(req->data); + req->data = new_data; + req->mem_size = new_size; + } + + memcpy(req->data + req->data_size, source, size); + req->data_size += size; + return 0; +} + +/*****************************************************************************/ + +/** Request a read operation. + */ +void ec_soe_request_read( + ec_soe_request_t *req /**< SoE request. */ + ) +{ + req->dir = EC_DIR_INPUT; + req->state = EC_INT_REQUEST_QUEUED; + req->error_code = 0x0000; +} + +/*****************************************************************************/ + +/** Request a write operation. + */ +void ec_soe_request_write( + ec_soe_request_t *req /**< SoE request. */ + ) +{ + req->dir = EC_DIR_OUTPUT; + req->state = EC_INT_REQUEST_QUEUED; + req->error_code = 0x0000; +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/soe_request.h b/net/ethercat/master/soe_request.h new file mode 100644 index 000000000000..40f61aa677a8 --- /dev/null +++ b/net/ethercat/master/soe_request.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + EtherCAT SoE request structure. +*/ + +/*****************************************************************************/ + +#ifndef __EC_SOE_REQUEST_H__ +#define __EC_SOE_REQUEST_H__ + +#include <linux/list.h> + +#include "globals.h" + +/*****************************************************************************/ + +/** Sercos-over-EtherCAT request. + */ +typedef struct { + struct list_head list; /**< List item. */ + uint8_t drive_no; /**< Drive number. */ + uint16_t idn; /**< Sercos ID-Number. */ + ec_al_state_t al_state; /**< AL state (only valid for IDN config). */ + uint8_t *data; /**< Pointer to SDO data. */ + size_t mem_size; /**< Size of SDO data memory. */ + size_t data_size; /**< Size of SDO data. */ + ec_direction_t dir; /**< Direction. EC_DIR_OUTPUT means writing to the + slave, EC_DIR_INPUT means reading from the slave. */ + ec_internal_request_state_t state; /**< Request state. */ + unsigned long jiffies_sent; /**< Jiffies, when the upload/download + request was sent. */ + uint16_t error_code; /**< SoE error code. */ +} ec_soe_request_t; + +/*****************************************************************************/ + +void ec_soe_request_init(ec_soe_request_t *); +void ec_soe_request_clear(ec_soe_request_t *); + +int ec_soe_request_copy(ec_soe_request_t *, const ec_soe_request_t *); +void ec_soe_request_set_drive_no(ec_soe_request_t *, uint8_t); +void ec_soe_request_set_idn(ec_soe_request_t *, uint16_t); +int ec_soe_request_alloc(ec_soe_request_t *, size_t); +int ec_soe_request_copy_data(ec_soe_request_t *, const uint8_t *, size_t); +int ec_soe_request_append_data(ec_soe_request_t *, const uint8_t *, size_t); +void ec_soe_request_read(ec_soe_request_t *); +void ec_soe_request_write(ec_soe_request_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/sync.c b/net/ethercat/master/sync.c new file mode 100644 index 000000000000..0002e8734044 --- /dev/null +++ b/net/ethercat/master/sync.c @@ -0,0 +1,178 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT sync manager methods. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "slave.h" +#include "master.h" +#include "pdo.h" +#include "sync.h" + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_sync_init( + ec_sync_t *sync, /**< EtherCAT sync manager. */ + ec_slave_t *slave /**< EtherCAT slave. */ + ) +{ + sync->slave = slave; + sync->physical_start_address = 0x0000; + sync->default_length = 0x0000; + sync->control_register = 0x00; + sync->enable = 0x00; + ec_pdo_list_init(&sync->pdos); +} + +/*****************************************************************************/ + +/** Copy constructor. + */ +void ec_sync_init_copy( + ec_sync_t *sync, /**< EtherCAT sync manager. */ + const ec_sync_t *other /**< Sync manager to copy from. */ + ) +{ + sync->slave = other->slave; + sync->physical_start_address = other->physical_start_address; + sync->default_length = other->default_length; + sync->control_register = other->control_register; + sync->enable = other->enable; + ec_pdo_list_init(&sync->pdos); + ec_pdo_list_copy(&sync->pdos, &other->pdos); +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_sync_clear( + ec_sync_t *sync /**< EtherCAT sync manager. */ + ) +{ + ec_pdo_list_clear(&sync->pdos); +} + +/*****************************************************************************/ + +/** Initializes a sync manager configuration page. + * + * The referenced memory (\a data) must be at least \a EC_SYNC_SIZE bytes. + */ +void ec_sync_page( + const ec_sync_t *sync, /**< Sync manager. */ + uint8_t sync_index, /**< Index of the sync manager. */ + uint16_t data_size, /**< Data size. */ + const ec_sync_config_t *sync_config, /**< Configuration. */ + uint8_t pdo_xfer, /**< Non-zero, if PDOs will be transferred via this + sync manager. */ + uint8_t *data /**> Configuration memory. */ + ) +{ + // enable only if (SII enable is set or PDO xfer) + // and size is > 0 and SM is not virtual + uint16_t enable = ((sync->enable & 0x01) || pdo_xfer) + && data_size + && ((sync->enable & 0x04) == 0); + uint8_t control = sync->control_register; + + if (sync_config) { + + switch (sync_config->dir) { + case EC_DIR_OUTPUT: + case EC_DIR_INPUT: + EC_WRITE_BIT(&control, 2, + sync_config->dir == EC_DIR_OUTPUT ? 1 : 0); + EC_WRITE_BIT(&control, 3, 0); + break; + default: + break; + } + + switch (sync_config->watchdog_mode) { + case EC_WD_ENABLE: + case EC_WD_DISABLE: + EC_WRITE_BIT(&control, 6, + sync_config->watchdog_mode == EC_WD_ENABLE); + break; + default: + break; + } + } + + EC_SLAVE_DBG(sync->slave, 1, "SM%u: Addr 0x%04X, Size %3u," + " Ctrl 0x%02X, En %u\n", + sync_index, sync->physical_start_address, + data_size, control, enable); + + EC_WRITE_U16(data, sync->physical_start_address); + EC_WRITE_U16(data + 2, data_size); + EC_WRITE_U8 (data + 4, control); + EC_WRITE_U8 (data + 5, 0x00); // status byte (read only) + EC_WRITE_U16(data + 6, enable); +} + +/*****************************************************************************/ + +/** Adds a PDO to the list of known mapped PDOs. + * + * \return 0 on success, else < 0 + */ +int ec_sync_add_pdo( + ec_sync_t *sync, /**< EtherCAT sync manager. */ + const ec_pdo_t *pdo /**< PDO to map. */ + ) +{ + return ec_pdo_list_add_pdo_copy(&sync->pdos, pdo); +} + +/*****************************************************************************/ + +/** Determines the default direction from the control register. + * + * \return Direction. + */ +ec_direction_t ec_sync_default_direction( + const ec_sync_t *sync /**< EtherCAT sync manager. */ + ) +{ + switch ((sync->control_register & 0x0C) >> 2) { + case 0x0: return EC_DIR_INPUT; + case 0x1: return EC_DIR_OUTPUT; + default: return EC_DIR_INVALID; + } +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/sync.h b/net/ethercat/master/sync.h new file mode 100644 index 000000000000..30f8f3481a58 --- /dev/null +++ b/net/ethercat/master/sync.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT sync manager. + */ + +/*****************************************************************************/ + +#ifndef __EC_SYNC_H__ +#define __EC_SYNC_H__ + +#include "globals.h" +#include "pdo_list.h" +#include "sync_config.h" + +/*****************************************************************************/ + +/** Sync manager. + */ +typedef struct { + ec_slave_t *slave; /**< Slave, the sync manager belongs to. */ + uint16_t physical_start_address; /**< Physical start address. */ + uint16_t default_length; /**< Data length in bytes. */ + uint8_t control_register; /**< Control register value. */ + uint8_t enable; /**< Enable bit. */ + ec_pdo_list_t pdos; /**< Current PDO assignment. */ +} ec_sync_t; + +/*****************************************************************************/ + +void ec_sync_init(ec_sync_t *, ec_slave_t *); +void ec_sync_init_copy(ec_sync_t *, const ec_sync_t *); +void ec_sync_clear(ec_sync_t *); +void ec_sync_page(const ec_sync_t *, uint8_t, uint16_t, + const ec_sync_config_t *, uint8_t, uint8_t *); +int ec_sync_add_pdo(ec_sync_t *, const ec_pdo_t *); +ec_direction_t ec_sync_default_direction(const ec_sync_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/sync_config.c b/net/ethercat/master/sync_config.c new file mode 100644 index 000000000000..e3c441d21326 --- /dev/null +++ b/net/ethercat/master/sync_config.c @@ -0,0 +1,63 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT sync manager configuration methods. + */ + +/*****************************************************************************/ + +#include "globals.h" +#include "sync_config.h" + +/*****************************************************************************/ + +/** Constructor. + */ +void ec_sync_config_init( + ec_sync_config_t *sync_config /**< Sync manager configuration. */ + ) +{ + sync_config->dir = EC_DIR_INVALID; + sync_config->watchdog_mode = EC_WD_DEFAULT; + ec_pdo_list_init(&sync_config->pdos); +} + +/*****************************************************************************/ + +/** Destructor. + */ +void ec_sync_config_clear( + ec_sync_config_t *sync_config /**< Sync manager configuration. */ + ) +{ + ec_pdo_list_clear(&sync_config->pdos); +} + +/*****************************************************************************/ diff --git a/net/ethercat/master/sync_config.h b/net/ethercat/master/sync_config.h new file mode 100644 index 000000000000..c8fc3e88fe41 --- /dev/null +++ b/net/ethercat/master/sync_config.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * EtherCAT sync manager. + */ + +/*****************************************************************************/ + +#ifndef __EC_SYNC_CONFIG_H__ +#define __EC_SYNC_CONFIG_H__ + +#include "globals.h" +#include "pdo_list.h" + +/*****************************************************************************/ + +/** Sync manager configuration. + */ +typedef struct { + ec_direction_t dir; /**< Sync manager direction. */ + ec_watchdog_mode_t watchdog_mode; /**< Watchdog mode. */ + ec_pdo_list_t pdos; /**< Current PDO assignment. */ +} ec_sync_config_t; + +/*****************************************************************************/ + +void ec_sync_config_init(ec_sync_config_t *); +void ec_sync_config_clear(ec_sync_config_t *); + +/*****************************************************************************/ + +#endif diff --git a/net/ethercat/master/voe_handler.c b/net/ethercat/master/voe_handler.c new file mode 100644 index 000000000000..6ad1fc416bf5 --- /dev/null +++ b/net/ethercat/master/voe_handler.c @@ -0,0 +1,560 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** \file + * Vendor specific over EtherCAT protocol handler functions. + */ + +/*****************************************************************************/ + +#include <linux/module.h> + +#include "master.h" +#include "slave_config.h" +#include "mailbox.h" +#include "voe_handler.h" + +/** VoE mailbox type. + */ +#define EC_MBOX_TYPE_VOE 0x0f + +/** VoE header size. + */ +#define EC_VOE_HEADER_SIZE 6 + +/** VoE response timeout in [ms]. + */ +#define EC_VOE_RESPONSE_TIMEOUT 500 + +/*****************************************************************************/ + +void ec_voe_handler_state_write_start(ec_voe_handler_t *); +void ec_voe_handler_state_write_response(ec_voe_handler_t *); + +void ec_voe_handler_state_read_start(ec_voe_handler_t *); +void ec_voe_handler_state_read_check(ec_voe_handler_t *); +void ec_voe_handler_state_read_response(ec_voe_handler_t *); + +void ec_voe_handler_state_read_nosync_start(ec_voe_handler_t *); +void ec_voe_handler_state_read_nosync_response(ec_voe_handler_t *); + +void ec_voe_handler_state_end(ec_voe_handler_t *); +void ec_voe_handler_state_error(ec_voe_handler_t *); + +/*****************************************************************************/ + +/** VoE handler constructor. + * + * \return Return value of ec_datagram_prealloc(). + */ +int ec_voe_handler_init( + ec_voe_handler_t *voe, /**< VoE handler. */ + ec_slave_config_t *sc, /**< Parent slave configuration. */ + size_t size /**< Size of memory to reserve. */ + ) +{ + voe->config = sc; + voe->vendor_id = 0x00000000; + voe->vendor_type = 0x0000; + voe->data_size = 0; + voe->dir = EC_DIR_INVALID; + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_INIT; + + ec_datagram_init(&voe->datagram); + return ec_datagram_prealloc(&voe->datagram, + size + EC_MBOX_HEADER_SIZE + EC_VOE_HEADER_SIZE); +} + +/*****************************************************************************/ + +/** VoE handler destructor. + */ +void ec_voe_handler_clear( + ec_voe_handler_t *voe /**< VoE handler. */ + ) +{ + ec_datagram_clear(&voe->datagram); +} + +/*****************************************************************************/ + +/** Get usable memory size. + * + * \return Memory size. + */ +size_t ec_voe_handler_mem_size( + const ec_voe_handler_t *voe /**< VoE handler. */ + ) +{ + if (voe->datagram.mem_size >= EC_MBOX_HEADER_SIZE + EC_VOE_HEADER_SIZE) + return voe->datagram.mem_size - + (EC_MBOX_HEADER_SIZE + EC_VOE_HEADER_SIZE); + else + return 0; +} + +/***************************************************************************** + * Application interface. + ****************************************************************************/ + +void ecrt_voe_handler_send_header(ec_voe_handler_t *voe, uint32_t vendor_id, + uint16_t vendor_type) +{ + voe->vendor_id = vendor_id; + voe->vendor_type = vendor_type; +} + +/*****************************************************************************/ + +void ecrt_voe_handler_received_header(const ec_voe_handler_t *voe, + uint32_t *vendor_id, uint16_t *vendor_type) +{ + uint8_t *header = voe->datagram.data + EC_MBOX_HEADER_SIZE; + + if (vendor_id) + *vendor_id = EC_READ_U32(header); + if (vendor_type) + *vendor_type = EC_READ_U16(header + 4); +} + +/*****************************************************************************/ + +uint8_t *ecrt_voe_handler_data(ec_voe_handler_t *voe) +{ + return voe->datagram.data + EC_MBOX_HEADER_SIZE + EC_VOE_HEADER_SIZE; +} + +/*****************************************************************************/ + +size_t ecrt_voe_handler_data_size(const ec_voe_handler_t *voe) +{ + return voe->data_size; +} + +/*****************************************************************************/ + +void ecrt_voe_handler_read(ec_voe_handler_t *voe) +{ + voe->dir = EC_DIR_INPUT; + voe->state = ec_voe_handler_state_read_start; + voe->request_state = EC_INT_REQUEST_BUSY; +} + +/*****************************************************************************/ + +void ecrt_voe_handler_read_nosync(ec_voe_handler_t *voe) +{ + voe->dir = EC_DIR_INPUT; + voe->state = ec_voe_handler_state_read_nosync_start; + voe->request_state = EC_INT_REQUEST_BUSY; +} + +/*****************************************************************************/ + +void ecrt_voe_handler_write(ec_voe_handler_t *voe, size_t size) +{ + voe->dir = EC_DIR_OUTPUT; + voe->data_size = size; + voe->state = ec_voe_handler_state_write_start; + voe->request_state = EC_INT_REQUEST_BUSY; +} + +/*****************************************************************************/ + +ec_request_state_t ecrt_voe_handler_execute(ec_voe_handler_t *voe) +{ + if (voe->config->slave) { // FIXME locking? + voe->state(voe); + if (voe->request_state == EC_INT_REQUEST_BUSY) { + ec_master_queue_datagram(voe->config->master, &voe->datagram); + } + } else { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + } + + return ec_request_state_translation_table[voe->request_state]; +} + +/****************************************************************************** + * State functions. + *****************************************************************************/ + +/** Start writing VoE data. + */ +void ec_voe_handler_state_write_start(ec_voe_handler_t *voe) +{ + ec_slave_t *slave = voe->config->slave; + uint8_t *data; + + if (slave->master->debug_level) { + EC_SLAVE_DBG(slave, 0, "Writing %zu bytes of VoE data.\n", + voe->data_size); + ec_print_data(ecrt_voe_handler_data(voe), voe->data_size); + } + + if (!(slave->sii.mailbox_protocols & EC_MBOX_VOE)) { + EC_SLAVE_ERR(slave, "Slave does not support VoE!\n"); + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + return; + } + + data = ec_slave_mbox_prepare_send(slave, &voe->datagram, + EC_MBOX_TYPE_VOE, EC_VOE_HEADER_SIZE + voe->data_size); + if (IS_ERR(data)) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + return; + } + + EC_WRITE_U32(data, voe->vendor_id); + EC_WRITE_U16(data + 4, voe->vendor_type); + /* data already in datagram */ + + voe->retries = EC_FSM_RETRIES; + voe->jiffies_start = jiffies; + voe->state = ec_voe_handler_state_write_response; +} + +/*****************************************************************************/ + +/** Wait for the mailbox response. + */ +void ec_voe_handler_state_write_response(ec_voe_handler_t *voe) +{ + ec_datagram_t *datagram = &voe->datagram; + ec_slave_t *slave = voe->config->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && voe->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Failed to receive VoE write request datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + if (!datagram->working_counter) { + unsigned long diff_ms = + (jiffies - voe->jiffies_start) * 1000 / HZ; + if (diff_ms < EC_VOE_RESPONSE_TIMEOUT) { + EC_SLAVE_DBG(slave, 1, "Slave did not respond to" + " VoE write request. Retrying after %lu ms...\n", + diff_ms); + // no response; send request datagram again + return; + } + } + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Reception of VoE write request failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + + EC_CONFIG_DBG(voe->config, 1, "VoE write request successful.\n"); + + voe->request_state = EC_INT_REQUEST_SUCCESS; + voe->state = ec_voe_handler_state_end; +} + +/*****************************************************************************/ + +/** Start reading VoE data. + */ +void ec_voe_handler_state_read_start(ec_voe_handler_t *voe) +{ + ec_datagram_t *datagram = &voe->datagram; + ec_slave_t *slave = voe->config->slave; + + EC_SLAVE_DBG(slave, 1, "Reading VoE data.\n"); + + if (!(slave->sii.mailbox_protocols & EC_MBOX_VOE)) { + EC_SLAVE_ERR(slave, "Slave does not support VoE!\n"); + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + + voe->jiffies_start = jiffies; + voe->retries = EC_FSM_RETRIES; + voe->state = ec_voe_handler_state_read_check; +} + +/*****************************************************************************/ + +/** Check for new data in the mailbox. + */ +void ec_voe_handler_state_read_check(ec_voe_handler_t *voe) +{ + ec_datagram_t *datagram = &voe->datagram; + ec_slave_t *slave = voe->config->slave; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && voe->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Failed to receive VoE mailbox check datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Reception of VoE mailbox check" + " datagram failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + + if (!ec_slave_mbox_check(datagram)) { + unsigned long diff_ms = + (datagram->jiffies_received - voe->jiffies_start) * 1000 / HZ; + if (diff_ms >= EC_VOE_RESPONSE_TIMEOUT) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Timeout while waiting for VoE data.\n"); + return; + } + + ec_slave_mbox_prepare_check(slave, datagram); // can not fail. + voe->retries = EC_FSM_RETRIES; + return; + } + + // Fetch response + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + voe->retries = EC_FSM_RETRIES; + voe->state = ec_voe_handler_state_read_response; +} + +/*****************************************************************************/ + +/** Read the pending mailbox data. + */ +void ec_voe_handler_state_read_response(ec_voe_handler_t *voe) +{ + ec_datagram_t *datagram = &voe->datagram; + ec_slave_t *slave = voe->config->slave; + ec_master_t *master = voe->config->master; + uint8_t *data, mbox_prot; + size_t rec_size; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && voe->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Failed to receive VoE read datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter != 1) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Reception of VoE read response failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + + data = ec_slave_mbox_fetch(slave, datagram, &mbox_prot, &rec_size); + if (IS_ERR(data)) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + return; + } + + if (mbox_prot != EC_MBOX_TYPE_VOE) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_WARN(slave, "Received mailbox protocol 0x%02X" + " as response.\n", mbox_prot); + ec_print_data(data, rec_size); + return; + } + + if (rec_size < EC_VOE_HEADER_SIZE) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Received VoE header is" + " incomplete (%zu bytes)!\n", rec_size); + return; + } + + if (master->debug_level) { + EC_CONFIG_DBG(voe->config, 0, "VoE data:\n"); + ec_print_data(data, rec_size); + } + + voe->data_size = rec_size - EC_VOE_HEADER_SIZE; + voe->request_state = EC_INT_REQUEST_SUCCESS; + voe->state = ec_voe_handler_state_end; // success +} + +/*****************************************************************************/ + +/** Start reading VoE data without sending a sync message before. + */ +void ec_voe_handler_state_read_nosync_start(ec_voe_handler_t *voe) +{ + ec_datagram_t *datagram = &voe->datagram; + ec_slave_t *slave = voe->config->slave; + + EC_SLAVE_DBG(slave, 1, "Reading VoE data.\n"); + + if (!(slave->sii.mailbox_protocols & EC_MBOX_VOE)) { + EC_SLAVE_ERR(slave, "Slave does not support VoE!\n"); + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + return; + } + + ec_slave_mbox_prepare_fetch(slave, datagram); // can not fail. + + voe->jiffies_start = jiffies; + voe->retries = EC_FSM_RETRIES; + voe->state = ec_voe_handler_state_read_nosync_response; +} + +/*****************************************************************************/ + +/** Read the pending mailbox data without sending a sync message before. This + * might lead to an empty reponse from the client. + */ +void ec_voe_handler_state_read_nosync_response(ec_voe_handler_t *voe) +{ + ec_datagram_t *datagram = &voe->datagram; + ec_slave_t *slave = voe->config->slave; + ec_master_t *master = voe->config->master; + uint8_t *data, mbox_prot; + size_t rec_size; + + if (datagram->state == EC_DATAGRAM_TIMED_OUT && voe->retries--) + return; + + if (datagram->state != EC_DATAGRAM_RECEIVED) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Failed to receive VoE read datagram: "); + ec_datagram_print_state(datagram); + return; + } + + if (datagram->working_counter == 0) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_DBG(slave, 1, "Slave did not send VoE data.\n"); + return; + } + + if (datagram->working_counter != 1) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_WARN(slave, "Reception of VoE read response failed: "); + ec_datagram_print_wc_error(datagram); + return; + } + + if (!(data = ec_slave_mbox_fetch(slave, datagram, + &mbox_prot, &rec_size))) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + return; + } + + if (mbox_prot != EC_MBOX_TYPE_VOE) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_WARN(slave, "Received mailbox protocol 0x%02X" + " as response.\n", mbox_prot); + ec_print_data(data, rec_size); + return; + } + + if (rec_size < EC_VOE_HEADER_SIZE) { + voe->state = ec_voe_handler_state_error; + voe->request_state = EC_INT_REQUEST_FAILURE; + EC_SLAVE_ERR(slave, "Received VoE header is" + " incomplete (%zu bytes)!\n", rec_size); + return; + } + + if (master->debug_level) { + EC_CONFIG_DBG(voe->config, 1, "VoE data:\n"); + ec_print_data(data, rec_size); + } + + voe->data_size = rec_size - EC_VOE_HEADER_SIZE; + voe->request_state = EC_INT_REQUEST_SUCCESS; + voe->state = ec_voe_handler_state_end; // success +} + +/*****************************************************************************/ + +/** Successful termination state function. + */ +void ec_voe_handler_state_end(ec_voe_handler_t *voe) +{ +} + +/*****************************************************************************/ + +/** Failure termination state function. + */ +void ec_voe_handler_state_error(ec_voe_handler_t *voe) +{ +} + +/*****************************************************************************/ + +/** \cond */ + +EXPORT_SYMBOL(ecrt_voe_handler_send_header); +EXPORT_SYMBOL(ecrt_voe_handler_received_header); +EXPORT_SYMBOL(ecrt_voe_handler_data); +EXPORT_SYMBOL(ecrt_voe_handler_data_size); +EXPORT_SYMBOL(ecrt_voe_handler_read); +EXPORT_SYMBOL(ecrt_voe_handler_write); +EXPORT_SYMBOL(ecrt_voe_handler_execute); + +/** \endcond */ + +/*****************************************************************************/ diff --git a/net/ethercat/master/voe_handler.h b/net/ethercat/master/voe_handler.h new file mode 100644 index 000000000000..e1b7eff80616 --- /dev/null +++ b/net/ethercat/master/voe_handler.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * + * $Id$ + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT Master. + * + * The IgH EtherCAT Master is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * The IgH EtherCAT Master is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the IgH EtherCAT Master; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * --- + * + * The license mentioned above concerns the source code only. Using the + * EtherCAT technology and brand is only permitted in compliance with the + * industrial property and similar rights of Beckhoff Automation GmbH. + * + *****************************************************************************/ + +/** + \file + Vendor specific over EtherCAT protocol handler. +*/ + +/*****************************************************************************/ + +#ifndef __EC_VOE_HANDLER_H__ +#define __EC_VOE_HANDLER_H__ + +#include <linux/list.h> + +#include "globals.h" +#include "datagram.h" + +/*****************************************************************************/ + +/** Vendor specific over EtherCAT handler. + */ +struct ec_voe_handler { + struct list_head list; /**< List item. */ + ec_slave_config_t *config; /**< Parent slave configuration. */ + ec_datagram_t datagram; /**< State machine datagram. */ + uint32_t vendor_id; /**< Vendor ID for the header. */ + uint16_t vendor_type; /**< Vendor type for the header. */ + size_t data_size; /**< Size of VoE data. */ + ec_direction_t dir; /**< Direction. EC_DIR_OUTPUT means writing to + the slave, EC_DIR_INPUT means reading from the + slave. */ + void (*state)(ec_voe_handler_t *); /**< State function */ + ec_internal_request_state_t request_state; /**< Handler state. */ + unsigned int retries; /**< retries upon datagram timeout */ + unsigned long jiffies_start; /**< Timestamp for timeout calculation. */ +}; + +/*****************************************************************************/ + +int ec_voe_handler_init(ec_voe_handler_t *, ec_slave_config_t *, size_t); +void ec_voe_handler_clear(ec_voe_handler_t *); +size_t ec_voe_handler_mem_size(const ec_voe_handler_t *); + +/*****************************************************************************/ + +#endif |