Merged redundancy branch to stable-1.5. stable-1.5
authorFlorian Pose <fp@igh-essen.com>
Thu, 06 Sep 2012 18:28:57 +0200
branchstable-1.5
changeset 2419 fdb85a806585
parent 2417 63bef67e812b (current diff)
parent 2418 8cbb68315d29 (diff)
child 2420 69056c46aa4d
Merged redundancy branch to stable-1.5.
NEWS
configure.ac
devices/Makefile.am
devices/e100-2.6.32-ethercat.c
devices/e1000/e1000_ethtool-2.6.29-orig.c
devices/e1000/e1000_main-2.6.29-orig.c
devices/e1000e/Makefile.am
devices/e1000e/e1000-2.6.34-ethercat.h
documentation/ethercat_doc.tex
include/ecrt.h
lib/master.c
master/cdev.c
master/ethernet.c
master/fsm_master.c
master/fsm_slave.c
master/globals.h
master/master.c
master/master.h
master/module.c
master/slave_config.c
script/sysconfig/ethercat
--- a/NEWS	Thu Sep 06 14:40:10 2012 +0200
+++ b/NEWS	Thu Sep 06 18:28:57 2012 +0200
@@ -37,7 +37,7 @@
     - Added e100 driver for 2.6.29, thanks to Andre Puschmann.
     - Added e100 driver for 2.6.31.
     - Added e100 driver for 2.6.32.
-    - Added e100 driver for 2.6.33, thanks for J. Kunz.
+    - Added e100 driver for 2.6.33, thanks to J. Kunz.
     - Added e100 driver for 2.6.37.
     - Added e100 driver for 3.0.
 * Added 8139too driver for kernels 2.6.25 (F. Pose), 2.6.26 (M. Luescher),
@@ -90,7 +90,7 @@
   supports ranges like '0,3,8-10'.
 * A sync manager is always enabled, if it contains registered process data.
 * Added a configuration switch --enable-wildcards to use 0xffffffff as a
-* wildcard for vendor ID and product code.
+  wildcard for vendor ID and product code.
 * Added support for systemd.
 
 Changes in 1.4.0:
--- a/include/ecrt.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/include/ecrt.h	Thu Sep 06 18:28:57 2012 +0200
@@ -39,6 +39,14 @@
  * request a master, to map process data, to communicate with slaves via CoE
  * and to configure and activate the bus.
  *
+ * Changed since 1.5:
+ *
+ * - 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.
+ *
  * Changes in version 1.5:
  *
  * - Added the distributed clocks feature and the respective method
@@ -119,6 +127,17 @@
  */
 #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
+
 /*****************************************************************************/
 
 /** End of list marker.
@@ -179,7 +198,8 @@
  * \see ecrt_master_state().
  */
 typedef struct {
-    unsigned int slaves_responding; /**< Number of slaves in the bus. */
+    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
@@ -189,11 +209,36 @@
                                   - Bit 1: \a PREOP
                                   - Bit 2: \a SAFEOP
                                   - Bit 3: \a OP */
-    unsigned int link_up : 1; /**< \a true, if the network link is up. */
+    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().
@@ -304,6 +349,7 @@
 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;
 
 /*****************************************************************************/
@@ -848,12 +894,29 @@
 /** 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
--- a/lib/master.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/lib/master.c	Thu Sep 06 18:28:57 2012 +0200
@@ -256,8 +256,9 @@
 {
     ec_ioctl_slave_sync_t data;
 
-    if (sync_index >= EC_MAX_SYNC_MANAGERS)
+    if (sync_index >= EC_MAX_SYNC_MANAGERS) {
         return -ENOENT;
+    }
 
     memset(&data, 0x00, sizeof(ec_ioctl_slave_sync_t));
     data.slave_position = slave_position;
@@ -552,6 +553,21 @@
 
 /*****************************************************************************/
 
+int ecrt_master_link_state(const ec_master_t *master, unsigned int dev_idx,
+        ec_master_link_state_t *state)
+{
+    ec_ioctl_link_state_t io;
+
+    io.dev_idx = dev_idx;
+    io.state = state;
+    if (ioctl(master->fd, EC_IOCTL_MASTER_LINK_STATE, &io) == -1) {
+        fprintf(stderr, "Failed to get link state: %s\n", strerror(errno));
+        return -errno;
+    }
+}
+
+/*****************************************************************************/
+
 void ecrt_master_application_time(ec_master_t *master, uint64_t app_time)
 {
     ec_ioctl_app_time_t data;
--- a/master/Kbuild.in	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/Kbuild.in	Thu Sep 06 18:28:57 2012 +0200
@@ -36,6 +36,7 @@
 ec_master-objs := \
 	cdev.o \
 	datagram.o \
+	datagram_pair.o \
 	device.o \
 	domain.o \
 	fmmu_config.o \
--- a/master/Makefile.am	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/Makefile.am	Thu Sep 06 18:28:57 2012 +0200
@@ -31,6 +31,7 @@
 noinst_HEADERS = \
 	cdev.c cdev.h \
 	datagram.c datagram.h \
+	datagram_pair.c datagram_pair.h \
 	debug.c	debug.h \
 	device.c device.h \
 	domain.c domain.h \
--- a/master/cdev.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/cdev.c	Thu Sep 06 18:28:57 2012 +0200
@@ -190,10 +190,11 @@
         )
 {
     ec_ioctl_master_t data;
-    unsigned int i;
+    unsigned int i, j;
 
     if (down_interruptible(&master->master_sem))
         return -EINTR;
+
     data.slave_count = master->slave_count;
     data.config_count = ec_master_config_count(master);
     data.domain_count = ec_master_domain_count(master);
@@ -203,49 +204,55 @@
     data.phase = (uint8_t) master->phase;
     data.active = (uint8_t) master->active;
     data.scan_busy = master->scan_busy;
+
     up(&master->master_sem);
 
     if (down_interruptible(&master->device_sem))
         return -EINTR;
 
-    if (master->main_device.dev) {
-        memcpy(data.devices[0].address,
-                master->main_device.dev->dev_addr, ETH_ALEN);
-    } else {
-        memcpy(data.devices[0].address, master->main_mac, ETH_ALEN); 
-    }
-    data.devices[0].attached = master->main_device.dev ? 1 : 0;
-    data.devices[0].link_state = master->main_device.link_state ? 1 : 0;
-    data.devices[0].tx_count = master->main_device.tx_count;
-    data.devices[0].rx_count = master->main_device.rx_count;
-    data.devices[0].tx_bytes = master->main_device.tx_bytes;
-    data.devices[0].tx_errors = master->main_device.tx_errors;
+    for (i = 0; i < EC_NUM_DEVICES; i++) {
+        ec_device_t *device = &master->devices[i];
+
+        if (device->dev) {
+            memcpy(data.devices[i].address,
+                    device->dev->dev_addr, ETH_ALEN);
+        } else {
+            memcpy(data.devices[i].address, master->macs[i], ETH_ALEN);
+        }
+        data.devices[i].attached = device->dev ? 1 : 0;
+        data.devices[i].link_state = device->link_state ? 1 : 0;
+        data.devices[i].tx_count = device->tx_count;
+        data.devices[i].rx_count = device->rx_count;
+        data.devices[i].tx_bytes = device->tx_bytes;
+        data.devices[i].rx_bytes = device->rx_bytes;
+        data.devices[i].tx_errors = device->tx_errors;
+        for (j = 0; j < EC_RATE_COUNT; j++) {
+            data.devices[i].tx_frame_rates[j] =
+                device->tx_frame_rates[j];
+            data.devices[i].rx_frame_rates[j] =
+                device->rx_frame_rates[j];
+            data.devices[i].tx_byte_rates[j] =
+                device->tx_byte_rates[j];
+            data.devices[i].rx_byte_rates[j] =
+                device->rx_byte_rates[j];
+        }
+    }
+
+    data.tx_count = master->device_stats.tx_count;
+    data.rx_count = master->device_stats.rx_count;
+    data.tx_bytes = master->device_stats.tx_bytes;
+    data.rx_bytes = master->device_stats.rx_bytes;
     for (i = 0; i < EC_RATE_COUNT; i++) {
-        data.devices[0].tx_frame_rates[i] =
-            master->main_device.tx_frame_rates[i];
-        data.devices[0].tx_byte_rates[i] =
-            master->main_device.tx_byte_rates[i];
-        data.devices[0].loss_rates[i] = master->main_device.loss_rates[i];
-    }
-
-    if (master->backup_device.dev) {
-        memcpy(data.devices[1].address,
-                master->backup_device.dev->dev_addr, ETH_ALEN); 
-    } else {
-        memcpy(data.devices[1].address, master->backup_mac, ETH_ALEN); 
-    }
-    data.devices[1].attached = master->backup_device.dev ? 1 : 0;
-    data.devices[1].link_state = master->backup_device.link_state ? 1 : 0;
-    data.devices[1].tx_count = master->backup_device.tx_count;
-    data.devices[1].rx_count = master->backup_device.rx_count;
-    data.devices[1].tx_bytes = master->backup_device.tx_bytes;
-    data.devices[1].tx_errors = master->backup_device.tx_errors;
-    for (i = 0; i < EC_RATE_COUNT; i++) {
-        data.devices[1].tx_frame_rates[i] =
-            master->backup_device.tx_frame_rates[i];
-        data.devices[1].tx_byte_rates[i] =
-            master->backup_device.tx_byte_rates[i];
-        data.devices[1].loss_rates[i] = master->backup_device.loss_rates[i];
+        data.tx_frame_rates[i] =
+            master->device_stats.tx_frame_rates[i];
+        data.rx_frame_rates[i] =
+            master->device_stats.rx_frame_rates[i];
+        data.tx_byte_rates[i] =
+            master->device_stats.tx_byte_rates[i];
+        data.rx_byte_rates[i] =
+            master->device_stats.rx_byte_rates[i];
+        data.loss_rates[i] =
+            master->device_stats.loss_rates[i];
     }
 
     up(&master->device_sem);
@@ -287,6 +294,7 @@
         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;
@@ -530,6 +538,7 @@
 {
     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;
@@ -546,7 +555,9 @@
 
     data.data_size = domain->data_size;
     data.logical_base_address = domain->logical_base_address;
-    data.working_counter = domain->working_counter;
+    for (dev_idx = 0; dev_idx < EC_NUM_DEVICES; 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);
 
@@ -1818,6 +1829,40 @@
 
 /** Get the master state.
  */
+int ec_cdev_ioctl_master_link_state(
+        ec_master_t *master, /**< EtherCAT master. */
+        unsigned long arg, /**< ioctl() argument. */
+        ec_cdev_priv_t *priv /**< Private data structure of file handle. */
+        )
+{
+    ec_ioctl_link_state_t ioctl;
+    ec_master_link_state_t state;
+    int ret;
+
+    if (unlikely(!priv->requested)) {
+        return -EPERM;
+    }
+
+    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;
+}
+
+/*****************************************************************************/
+
+/** Get the master state.
+ */
 int ec_cdev_ioctl_app_time(
         ec_master_t *master, /**< EtherCAT master. */
         unsigned long arg, /**< ioctl() argument. */
@@ -3618,6 +3663,9 @@
         case EC_IOCTL_MASTER_STATE:
             ret = ec_cdev_ioctl_master_state(master, arg, priv);
             break;
+        case EC_IOCTL_MASTER_LINK_STATE:
+            ret = ec_cdev_ioctl_master_link_state(master, arg, priv);
+            break;
         case EC_IOCTL_APP_TIME:
             if (!(filp->f_mode & FMODE_WRITE)) {
                 ret = -EPERM;
--- a/master/datagram.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/datagram.c	Thu Sep 06 18:28:57 2012 +0200
@@ -88,6 +88,7 @@
 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;
@@ -427,12 +428,69 @@
 
 /** 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(
+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. */
@@ -450,14 +508,14 @@
 
 /*****************************************************************************/
 
-/** Initializes an EtherCAT LWR datagram.
+/** 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(
+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. */
@@ -475,14 +533,14 @@
 
 /*****************************************************************************/
 
-/** Initializes an EtherCAT LRW datagram.
+/** 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(
+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. */
--- a/master/datagram.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/datagram.h	Thu Sep 06 18:28:57 2012 +0200
@@ -85,9 +85,10 @@
 /** EtherCAT datagram.
  */
 typedef struct {
-    struct list_head list; /**< Needed by domain datagram lists. */
     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. */
@@ -130,9 +131,12 @@
 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, uint8_t *);
-int ec_datagram_lwr(ec_datagram_t *, uint32_t, size_t, uint8_t *);
-int ec_datagram_lrw(ec_datagram_t *, uint32_t, size_t, uint8_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 *);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/master/datagram_pair.c	Thu Sep 06 18:28:57 2012 +0200
@@ -0,0 +1,173 @@
+/******************************************************************************
+ *
+ *  $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.
+ */
+int ec_datagram_pair_init(
+        ec_datagram_pair_t *pair, /**< Datagram pair. */
+        ec_domain_t *domain, /**< Parent domain. */
+        uint32_t logical_offset,
+        uint8_t *data,
+        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_NUM_DEVICES; 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]);
+        pair->datagrams[dev_idx].device_index = dev_idx;
+    }
+
+    pair->expected_working_counter = 0U;
+
+    /* backup datagram has its own memory */
+    ret = ec_datagram_prealloc(&pair->datagrams[EC_DEVICE_BACKUP],
+            data_size);
+    if (ret) {
+        goto out_datagrams;
+    }
+
+    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;
+    }
+
+    /* 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);
+        ec_datagram_lrw(&pair->datagrams[EC_DEVICE_BACKUP],
+                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);
+        ec_datagram_lwr(&pair->datagrams[EC_DEVICE_BACKUP],
+                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);
+        ec_datagram_lrd(&pair->datagrams[EC_DEVICE_BACKUP],
+                logical_offset, data_size);
+
+        pair->expected_working_counter = used[EC_DIR_INPUT];
+    }
+
+    for (dev_idx = 0; dev_idx < EC_NUM_DEVICES; dev_idx++) {
+        ec_datagram_zero(&pair->datagrams[dev_idx]);
+    }
+
+    return 0;
+
+out_datagrams:
+    for (dev_idx = 0; dev_idx < EC_NUM_DEVICES; 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 = 0; dev_idx < EC_NUM_DEVICES; dev_idx++) {
+        ec_datagram_clear(&pair->datagrams[dev_idx]);
+    }
+
+    if (pair->send_buffer) {
+        kfree(pair->send_buffer);
+    }
+}
+
+/*****************************************************************************/
+
+/** Process received data.
+ */
+uint16_t ec_datagram_pair_process(
+        ec_datagram_pair_t *pair, /**< Datagram pair. */
+        uint16_t wc_sum[EC_NUM_DEVICES] /**< Working counter sums. */
+        )
+{
+    unsigned int dev_idx;
+    uint16_t pair_wc = 0;
+
+    for (dev_idx = 0; dev_idx < EC_NUM_DEVICES; dev_idx++) {
+        ec_datagram_t *datagram = &pair->datagrams[dev_idx];
+
+        ec_datagram_output_stats(datagram);
+
+        if (datagram->state == EC_DATAGRAM_RECEIVED) {
+            pair_wc += datagram->working_counter;
+            wc_sum[dev_idx] += datagram->working_counter;
+        }
+    }
+
+    return pair_wc;
+}
+
+/*****************************************************************************/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/master/datagram_pair.h	Thu Sep 06 18:28:57 2012 +0200
@@ -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;
+    ec_datagram_t datagrams[EC_NUM_DEVICES]; /**< Main and backup datagram.
+                                               */
+    uint8_t *send_buffer;
+    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[EC_NUM_DEVICES]);
+
+/*****************************************************************************/
+
+#endif
--- a/master/device.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/device.c	Thu Sep 06 18:28:57 2012 +0200
@@ -54,12 +54,6 @@
     } while (0)
 #endif
 
-/** List of intervals for frame statistics [s].
- */
-static const unsigned int rate_intervals[] = {
-    1, 10, 60
-};
-
 /*****************************************************************************/
 
 /** Constructor.
@@ -309,30 +303,6 @@
 {
     struct sk_buff *skb = device->tx_skb[device->tx_ring_index];
 
-    // frame statistics
-    if (unlikely(jiffies - device->stats_jiffies >= HZ)) {
-        unsigned int i;
-        u32 tx_frame_rate =
-            (u32) (device->tx_count - device->last_tx_count) * 1000;
-        u32 tx_byte_rate =
-            (device->tx_bytes - device->last_tx_bytes);
-        u64 loss = device->tx_count - device->rx_count;
-        s32 loss_rate = (s32) (loss - device->last_loss) * 1000;
-        for (i = 0; i < EC_RATE_COUNT; i++) {
-            unsigned int n = rate_intervals[i];
-            device->tx_frame_rates[i] =
-                (device->tx_frame_rates[i] * (n - 1) + tx_frame_rate) / n;
-            device->tx_byte_rates[i] =
-                (device->tx_byte_rates[i] * (n - 1) + tx_byte_rate) / n;
-            device->loss_rates[i] =
-                (device->loss_rates[i] * (n - 1) + loss_rate) / n;
-        }
-        device->last_tx_count = device->tx_count;
-        device->last_tx_bytes = device->tx_bytes;
-        device->last_loss = loss;
-        device->stats_jiffies = jiffies;
-    }
-
     // set the right length for the data
     skb->len = ETH_HLEN + size;
 
@@ -350,7 +320,9 @@
 #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
@@ -375,16 +347,20 @@
 
     // 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;
-    device->tx_bytes = 0;
-    device->last_tx_count = 0;
-    device->last_tx_bytes = 0;
-    device->last_loss = 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->loss_rates[i] = 0;
+        device->rx_byte_rates[i] = 0;
     }
 }
 
@@ -479,6 +455,43 @@
     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
  *****************************************************************************/
@@ -496,10 +509,21 @@
 void ecdev_withdraw(ec_device_t *device /**< EtherCAT device */)
 {
     ec_master_t *master = device->master;
-    char str[20];
-
-    ec_mac_print(device->dev->dev_addr, str);
-    EC_MASTER_INFO(master, "Releasing main device %s.\n", str);
+    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);
@@ -516,17 +540,22 @@
 int ecdev_open(ec_device_t *device /**< EtherCAT device */)
 {
     int ret;
+    ec_master_t *master = device->master;
 
     ret = ec_device_open(device);
     if (ret) {
-        EC_MASTER_ERR(device->master, "Failed to open device!\n");
+        EC_MASTER_ERR(master, "Failed to open device!\n");
         return ret;
     }
 
-    ret = ec_master_enter_idle_phase(device->master);
-    if (ret) {
-        EC_MASTER_ERR(device->master, "Failed to enter IDLE phase!\n");
-        return ret;
+    if (master->devices[EC_DEVICE_MAIN].open &&
+            (ec_mac_is_zero(master->macs[EC_DEVICE_BACKUP]) ||
+             master->devices[EC_DEVICE_BACKUP].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;
@@ -541,10 +570,15 @@
  */
 void ecdev_close(ec_device_t *device /**< EtherCAT device */)
 {
-    ec_master_leave_idle_phase(device->master);
-
-    if (ec_device_close(device))
-        EC_MASTER_WARN(device->master, "Failed to close device!\n");
+    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");
+    }
 }
 
 /*****************************************************************************/
@@ -572,6 +606,9 @@
     }
 
     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");
@@ -603,14 +640,15 @@
         )
 {
     if (unlikely(!device)) {
-        EC_MASTER_WARN(device->master, "ecdev_set_link(): No device!\n");
+        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 changed to %s.\n", (state ? "UP" : "DOWN"));
+                "Link state of %s changed to %s.\n",
+                device->dev->name, (state ? "UP" : "DOWN"));
     }
 }
 
@@ -625,7 +663,7 @@
         )
 {
     if (unlikely(!device)) {
-        EC_MASTER_WARN(device->master, "ecdev_get_link(): No device!\n");
+        EC_WARN("ecdev_get_link() called with null device!\n");
         return 0;
     }
 
--- a/master/device.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/device.h	Thu Sep 06 18:28:57 2012 +0200
@@ -101,19 +101,24 @@
     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 tx_bytes; /**< Number of frames sent. */
+    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. */
-    u64 last_loss; /**< Tx/Rx difference of last statistics cycle. */
-    unsigned int tx_frame_rates[EC_RATE_COUNT]; /**< Transmit rates in
-                                                  frames/s for different
-                                                  statistics cycle periods. */
-    unsigned int tx_byte_rates[EC_RATE_COUNT]; /**< Transmit rates in byte/s
-                                                 for different statistics
-                                                 cycle periods. */
-    int loss_rates[EC_RATE_COUNT]; /**< Frame loss rates for different
-                                     statistics cycle periods. */
-    unsigned long stats_jiffies; /**< Jiffies of last statistic 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. */
 
 #ifdef EC_DEBUG_IF
     ec_debug_t dbg; /**< debug device */
@@ -141,6 +146,7 @@
 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,
--- a/master/domain.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/domain.c	Thu Sep 06 18:28:57 2012 +0200
@@ -41,6 +41,9 @@
 #include "slave_config.h"
 
 #include "domain.h"
+#include "datagram_pair.h"
+
+#define DEBUG_REDUNDANCY 0
 
 /*****************************************************************************/
 
@@ -63,10 +66,12 @@
     domain->data = NULL;
     domain->data_origin = EC_ORIG_INTERNAL;
     domain->logical_base_address = 0x00000000;
-    INIT_LIST_HEAD(&domain->datagrams);
-    domain->working_counter = 0x0000;
+    INIT_LIST_HEAD(&domain->datagram_pairs);
+    domain->working_counter[EC_DEVICE_MAIN] = 0x0000;
+    domain->working_counter[EC_DEVICE_BACKUP] = 0x0000;
     domain->expected_working_counter = 0x0000;
     domain->working_counter_changes = 0;
+    domain->redundancy_active = 0;
     domain->notify_jiffies = 0;
 }
 
@@ -76,12 +81,13 @@
  */
 void ec_domain_clear(ec_domain_t *domain /**< EtherCAT domain */)
 {
-    ec_datagram_t *datagram, *next;
+    ec_datagram_pair_t *datagram_pair, *next_pair;
 
     // dequeue and free datagrams
-    list_for_each_entry_safe(datagram, next, &domain->datagrams, list) {
-        ec_datagram_clear(datagram);
-        kfree(datagram);
+    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);
@@ -95,8 +101,10 @@
         ec_domain_t *domain /**< EtherCAT domain. */
         )
 {
-    if (domain->data_origin == EC_ORIG_INTERNAL && domain->data)
+    if (domain->data_origin == EC_ORIG_INTERNAL && domain->data) {
         kfree(domain->data);
+    }
+
     domain->data = NULL;
     domain->data_origin = EC_ORIG_INTERNAL;
 }
@@ -122,68 +130,81 @@
 
 /*****************************************************************************/
 
-/** Allocates a domain datagram and appends it to the list.
- *
- * The datagram type and expected working counters are determined by the
- * number of input and output fmmus that share the datagram.
+/** 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(
+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[] /**< Used by inputs/outputs. */
-        )
-{
-    ec_datagram_t *datagram;
+        const unsigned int used[] /**< Slave config counter for in/out. */
+        )
+{
+    ec_datagram_pair_t *datagram_pair;
     int ret;
 
-    if (!(datagram = kmalloc(sizeof(ec_datagram_t), GFP_KERNEL))) {
+    if (!(datagram_pair = kmalloc(sizeof(ec_datagram_pair_t), GFP_KERNEL))) {
         EC_MASTER_ERR(domain->master,
-                "Failed to allocate domain datagram!\n");
+                "Failed to allocate domain datagram pair!\n");
         return -ENOMEM;
     }
 
-    ec_datagram_init(datagram);
-    snprintf(datagram->name, EC_DATAGRAM_NAME_SIZE,
-            "domain%u-%u", domain->index, logical_offset);
-
-    if (used[EC_DIR_OUTPUT] && used[EC_DIR_INPUT]) { // inputs and outputs
-        ret = ec_datagram_lrw(datagram, logical_offset, data_size, data);
-        if (ret < 0) {
-            kfree(datagram);
-            return ret;
-        }
-        // If LRW is used, output FMMUs increment the working counter by 2,
-        // while input FMMUs increment it by 1.
-        domain->expected_working_counter +=
-            used[EC_DIR_OUTPUT] * 2 + used[EC_DIR_INPUT];
-    } else if (used[EC_DIR_OUTPUT]) { // outputs only
-        ret = ec_datagram_lwr(datagram, logical_offset, data_size, data);
-        if (ret < 0) {
-            kfree(datagram);
-            return ret;
-        }
-        domain->expected_working_counter += used[EC_DIR_OUTPUT];
-    } else { // inputs only (or nothing)
-        ret = ec_datagram_lrd(datagram, logical_offset, data_size, data);
-        if (ret < 0) {
-            kfree(datagram);
-            return ret;
-        }
-        domain->expected_working_counter += used[EC_DIR_INPUT];
-    }
-
-    ec_datagram_zero(datagram);
-    list_add_tail(&datagram->list, &domain->datagrams);
+    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.
+ */
+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
@@ -204,8 +225,8 @@
     unsigned int datagram_count;
     unsigned int datagram_used[EC_DIR_COUNT];
     ec_fmmu_config_t *fmmu;
-    ec_fmmu_config_t *fmmu_temp;
-    const ec_datagram_t *datagram;
+    const ec_fmmu_config_t *datagram_first_fmmu = NULL;
+    const ec_datagram_pair_t *datagram_pair;
     int ret;
 
     domain->logical_base_address = base_address;
@@ -220,60 +241,57 @@
         }
     }
 
-    // Cycle through all domain FMMUS and
+    // 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;
 
-    list_for_each_entry(fmmu_temp, &domain->fmmu_configs, list) {
-        // we have to remove the constness, sorry FIXME
-        ec_slave_config_t *sc = (ec_slave_config_t *) fmmu_temp->sc;
-        sc->used_for_fmmu_datagram[fmmu_temp->dir] = 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;
 
         // Increment Input/Output counter to determine datagram types
         // and calculate expected working counters
-        if (fmmu->sc->used_for_fmmu_datagram[fmmu->dir] == 0) {
-            ec_slave_config_t *sc = (ec_slave_config_t *)fmmu->sc;
+        if (shall_count(fmmu, datagram_first_fmmu)) {
             datagram_used[fmmu->dir]++;
-            sc->used_for_fmmu_datagram[fmmu->dir] = 1;
         }
 
         // 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(domain,
+            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;
-            list_for_each_entry(fmmu_temp, &domain->fmmu_configs, list) {
-                ec_slave_config_t *sc = (ec_slave_config_t *)fmmu_temp->sc;
-               sc->used_for_fmmu_datagram[fmmu_temp->dir] = 0;
-            }
+            datagram_first_fmmu = fmmu;
         }
 
         datagram_size += fmmu->data_size;
     }
 
-    // Allocate last datagram, if data are left (this is also the case if the
-    // process data fit into a single datagram)
+    /* 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(domain,
+        ret = ec_domain_add_datagram_pair(domain,
                 domain->logical_base_address + datagram_offset,
                 datagram_size, domain->data + datagram_offset,
                 datagram_used);
@@ -286,13 +304,16 @@
             " %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, &domain->datagrams, list) {
+
+    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;
 }
 
@@ -332,8 +353,32 @@
     return NULL;
 }
 
+/*****************************************************************************/
+
+/** 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;
+}
+
 /******************************************************************************
- *  Realtime interface
+ *  Application interface
  *****************************************************************************/
 
 int ecrt_domain_reg_pdo_entry_list(ec_domain_t *domain,
@@ -342,7 +387,7 @@
     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);
 
@@ -398,20 +443,118 @@
 
 void ecrt_domain_process(ec_domain_t *domain)
 {
-    uint16_t working_counter_sum;
-    ec_datagram_t *datagram;
-
-    working_counter_sum = 0x0000;
-    list_for_each_entry(datagram, &domain->datagrams, list) {
-        ec_datagram_output_stats(datagram);
-        if (datagram->state == EC_DATAGRAM_RECEIVED) {
-            working_counter_sum += datagram->working_counter;
-        }
-    }
-
-    if (working_counter_sum != domain->working_counter) {
+    uint16_t wc_sum[EC_NUM_DEVICES] = {};
+    ec_datagram_pair_t *pair;
+    ec_datagram_t *main_datagram, *backup_datagram;
+    uint32_t logical_datagram_address;
+    size_t datagram_size;
+    uint16_t datagram_pair_wc;
+    unsigned int datagram_offset;
+    ec_fmmu_config_t *fmmu =
+        list_first_entry(&domain->fmmu_configs, ec_fmmu_config_t, list);
+    unsigned int redundancy;
+
+#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) {
+
+        main_datagram = &pair->datagrams[EC_DEVICE_MAIN];
+        backup_datagram = &pair->datagrams[EC_DEVICE_BACKUP];
+        logical_datagram_address = EC_READ_U32(main_datagram->address);
+        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
+
+        datagram_pair_wc = ec_datagram_pair_process(pair, wc_sum);
+
+        /* Go through all FMMU configs to detect data changes. */
+        list_for_each_entry_from(fmmu, &domain->fmmu_configs, list) {
+
+            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
+            }
+        }
+    }
+
+    redundancy = wc_sum[EC_DEVICE_BACKUP] > 0;
+    if (redundancy != domain->redundancy_active) {
+        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);
+        }
+        domain->redundancy_active = redundancy;
+    }
+
+    if ((wc_sum[EC_DEVICE_MAIN] != domain->working_counter[EC_DEVICE_MAIN])
+            || (wc_sum[EC_DEVICE_BACKUP]
+                != domain->working_counter[EC_DEVICE_BACKUP])) {
         domain->working_counter_changes++;
-        domain->working_counter = working_counter_sum;
+        domain->working_counter[EC_DEVICE_MAIN] = wc_sum[EC_DEVICE_MAIN];
+        domain->working_counter[EC_DEVICE_BACKUP] = wc_sum[EC_DEVICE_BACKUP];
     }
 
     if (domain->working_counter_changes &&
@@ -419,13 +562,19 @@
         domain->notify_jiffies = jiffies;
         if (domain->working_counter_changes == 1) {
             EC_MASTER_INFO(domain->master, "Domain %u: Working counter"
-                    " changed to %u/%u.\n", domain->index,
-                    domain->working_counter, domain->expected_working_counter);
+                    " changed to %u/%u (%u+%u).\n", domain->index,
+                    domain->working_counter[EC_DEVICE_MAIN] +
+                    domain->working_counter[EC_DEVICE_BACKUP],
+                    domain->expected_working_counter,
+                    wc_sum[EC_DEVICE_MAIN], wc_sum[EC_DEVICE_BACKUP]);
         } else {
             EC_MASTER_INFO(domain->master, "Domain %u: %u working counter"
-                    " changes - now %u/%u.\n", domain->index,
-                    domain->working_counter_changes, domain->working_counter,
-                    domain->expected_working_counter);
+                    " changes - now %u/%u (%u+%u).\n", domain->index,
+                    domain->working_counter_changes,
+                    domain->working_counter[EC_DEVICE_MAIN] +
+                    domain->working_counter[EC_DEVICE_BACKUP],
+                    domain->expected_working_counter,
+                    wc_sum[EC_DEVICE_MAIN], wc_sum[EC_DEVICE_BACKUP]);
         }
         domain->working_counter_changes = 0;
     }
@@ -435,10 +584,25 @@
 
 void ecrt_domain_queue(ec_domain_t *domain)
 {
-    ec_datagram_t *datagram;
-
-    list_for_each_entry(datagram, &domain->datagrams, list) {
-        ec_master_queue_datagram(domain->master, datagram);
+    ec_datagram_pair_t *datagram_pair;
+    ec_device_index_t dev_idx;
+
+    list_for_each_entry(datagram_pair, &domain->datagram_pairs, list) {
+
+        /* 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);
+
+        /* copy main data to backup datagram */
+        memcpy(datagram_pair->datagrams[EC_DEVICE_BACKUP].data,
+                datagram_pair->datagrams[EC_DEVICE_MAIN].data,
+                datagram_pair->datagrams[EC_DEVICE_MAIN].data_size);
+
+        for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_NUM_DEVICES; dev_idx++) {
+            ec_master_queue_datagram(domain->master,
+                    &datagram_pair->datagrams[dev_idx]);
+        }
     }
 }
 
@@ -446,10 +610,12 @@
 
 void ecrt_domain_state(const ec_domain_t *domain, ec_domain_state_t *state)
 {
-    state->working_counter = domain->working_counter;
-
-    if (domain->working_counter) {
-        if (domain->working_counter == domain->expected_working_counter) {
+    state->working_counter =
+        domain->working_counter[EC_DEVICE_MAIN]
+        + domain->working_counter[EC_DEVICE_BACKUP];
+
+    if (state->working_counter) {
+        if (state->working_counter == domain->expected_working_counter) {
             state->wc_state = EC_WC_COMPLETE;
         } else {
             state->wc_state = EC_WC_INCOMPLETE;
@@ -457,6 +623,8 @@
     } else {
         state->wc_state = EC_WC_ZERO;
     }
+
+    state->redundancy_active = domain->redundancy_active;
 }
 
 /*****************************************************************************/
--- a/master/domain.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/domain.h	Thu Sep 06 18:28:57 2012 +0200
@@ -63,12 +63,15 @@
     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 datagrams; /**< Datagrams for process data exchange. */
+    struct list_head datagram_pairs; /**< Datagrams pairs (main/backup) for
+                                       process data exchange. */
 
-    uint16_t working_counter; /**< Last working counter value. */
+    uint16_t working_counter[EC_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. */
 };
 
--- a/master/ethernet.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/ethernet.c	Thu Sep 06 18:28:57 2012 +0200
@@ -384,11 +384,13 @@
  *
  * Starts a new receiving sequence by queueing a datagram that checks the
  * slave's mailbox for a new EoE datagram.
+ *
+ * \fixme Use both devices.
  */
 void ec_eoe_state_rx_start(ec_eoe_t *eoe /**< EoE handler */)
 {
     if (eoe->slave->error_flag ||
-            !eoe->slave->master->main_device.link_state) {
+            !eoe->slave->master->devices[EC_DEVICE_MAIN].link_state) {
         eoe->rx_idle = 1;
         eoe->tx_idle = 1;
         return;
@@ -611,6 +613,8 @@
  *
  * Starts a new transmit sequence. If no data is available, a new receive
  * sequence is started instead.
+ *
+ * \fixme Use both devices.
  */
 void ec_eoe_state_tx_start(ec_eoe_t *eoe /**< EoE handler */)
 {
@@ -619,7 +623,7 @@
 #endif
 
     if (eoe->slave->error_flag ||
-            !eoe->slave->master->main_device.link_state) {
+            !eoe->slave->master->devices[EC_DEVICE_MAIN].link_state) {
         eoe->rx_idle = 1;
         eoe->tx_idle = 1;
         return;
--- a/master/fsm_master.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/fsm_master.c	Thu Sep 06 18:28:57 2012 +0200
@@ -67,6 +67,7 @@
 void ec_fsm_master_state_sdo_request(ec_fsm_master_t *);
 void ec_fsm_master_state_reg_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 *);
 
 /*****************************************************************************/
@@ -79,14 +80,19 @@
         ec_datagram_t *datagram /**< Datagram object to use. */
         )
 {
+    ec_device_index_t dev_idx;
+
     fsm->master = master;
     fsm->datagram = datagram;
     fsm->state = ec_fsm_master_state_start;
     fsm->idle = 0;
-    fsm->link_state = 0;
-    fsm->slaves_responding = 0;
+    fsm->dev_idx = EC_DEVICE_MAIN;
+    for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_NUM_DEVICES; 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;
-    fsm->slave_states = EC_SLAVE_STATE_UNKNOWN;
 
     // init sub-state-machines
     ec_fsm_coe_init(&fsm->fsm_coe, fsm->datagram);
@@ -159,6 +165,7 @@
         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
 }
@@ -178,6 +185,7 @@
     fsm->idle = 1;
     ec_datagram_brd(fsm->datagram, 0x0130, 2);
     ec_datagram_zero(fsm->datagram);
+    fsm->datagram->device_index = fsm->dev_idx;
     fsm->state = ec_fsm_master_state_broadcast;
 }
 
@@ -196,48 +204,58 @@
     ec_slave_t *slave;
     ec_master_t *master = fsm->master;
 
-    if (datagram->state == EC_DATAGRAM_TIMED_OUT)
-        return; // always retry
-
     // bus topology change?
-    if (datagram->working_counter != fsm->slaves_responding) {
+    if (datagram->working_counter != fsm->slaves_responding[fsm->dev_idx]) {
         fsm->rescan_required = 1;
-        fsm->slaves_responding = datagram->working_counter;
-        EC_MASTER_INFO(master, "%u slave(s) responding.\n",
-                fsm->slaves_responding);
-    }
-
-    if (fsm->link_state && !master->main_device.link_state) {
-        // link went down
+        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]);
+    }
+
+    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. Clearing slave list.\n");
+                "link down on %s device. Clearing slave list.\n",
+                ec_device_names[fsm->dev_idx]);
 
 #ifdef EC_EOE
         ec_master_eoe_stop(master);
         ec_master_clear_eoe_handlers(master);
 #endif
         ec_master_clear_slaves(master);
-        fsm->slave_states = 0x00;
-        fsm->slaves_responding = 0; /* reset to trigger rescan on next link
-                                       up. */
-    }
-    fsm->link_state = master->main_device.link_state;
-
-    if (datagram->state != EC_DATAGRAM_RECEIVED) {
-        ec_fsm_master_restart(fsm);
-        return;
-    }
-
-    if (fsm->slaves_responding) {
+
+        for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_NUM_DEVICES; 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) { // slave states changed?
+        if (states != fsm->slave_states[fsm->dev_idx]) {
+            // slave states changed
             char state_str[EC_STATE_STRING_SIZE];
-            fsm->slave_states = states;
-            ec_state_string(fsm->slave_states, state_str, 1);
-            EC_MASTER_INFO(master, "Slave states: %s.\n", state_str);
+            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], state_str);
         }
     } else {
-        fsm->slave_states = 0x00;
+        fsm->slave_states[fsm->dev_idx] = 0x00;
+    }
+
+    fsm->dev_idx++;
+    if (fsm->dev_idx < EC_NUM_DEVICES) {
+        // 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) {
@@ -245,6 +263,9 @@
         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);
 
@@ -259,9 +280,12 @@
 #endif
             ec_master_clear_slaves(master);
 
-            master->slave_count = fsm->slaves_responding;
-
-            if (!master->slave_count) {
+            for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_NUM_DEVICES;
+                    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);
@@ -269,12 +293,11 @@
                 return;
             }
 
-            size = sizeof(ec_slave_t) * master->slave_count;
+            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->slave_count = 0; // TODO avoid retrying scan!
                 master->scan_busy = 0;
                 wake_up_interruptible(&master->scan_queue);
                 ec_fsm_master_restart(fsm);
@@ -282,21 +305,35 @@
             }
 
             // init slaves
-            for (i = 0; i < master->slave_count; i++) {
+            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;
-                ec_slave_init(slave, master, i, i + 1);
+                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)
+                if (master->phase != EC_OPERATION) {
                     slave->force_config = 1;
+                }
             }
-
-            // broadcast clear all station addresses
-            ec_datagram_bwr(datagram, 0x0010, 2);
-            EC_WRITE_U16(datagram->data, 0x0000);
-            fsm->retries = EC_FSM_RETRIES;
-            fsm->state = ec_fsm_master_state_clear_addresses;
+            master->slave_count = count;
+
+            /* 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;
         }
     }
@@ -318,6 +355,7 @@
             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;
         }
@@ -411,6 +449,7 @@
                     request->offset, request->length);
             memcpy(fsm->datagram->data, request->data, request->length);
         }
+        fsm->datagram->device_index = request->slave->device_index;
         fsm->retries = EC_FSM_RETRIES;
         fsm->state = ec_fsm_master_state_reg_request;
         return 1;
@@ -485,8 +524,9 @@
     ec_slave_t *slave;
 
     // Check for pending internal SDO requests
-    if (ec_fsm_master_action_process_sdo(fsm))
-        return;
+    if (ec_fsm_master_action_process_sdo(fsm)) {
+        return;
+    }
 
     // enable processing of requests
     for (slave = master->slaves;
@@ -518,6 +558,7 @@
         fsm->state = ec_fsm_master_state_sdo_dictionary;
         ec_fsm_coe_dictionary(&fsm->fsm_coe, slave);
         ec_fsm_coe_exec(&fsm->fsm_coe); // execute immediately
+        fsm->datagram->device_index = fsm->slave->device_index;
         return;
     }
 
@@ -550,6 +591,7 @@
         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;
@@ -607,6 +649,7 @@
         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;
     }
 
@@ -680,8 +723,9 @@
 {
     ec_slave_t *slave = fsm->slave;
 
-    if (ec_fsm_change_exec(&fsm->fsm_change))
-        return;
+    if (ec_fsm_change_exec(&fsm->fsm_change)) {
+        return;
+    }
 
     if (!ec_fsm_change_success(&fsm->fsm_change)) {
         fsm->slave->error_flag = 1;
@@ -693,6 +737,22 @@
 
 /*****************************************************************************/
 
+/** 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(
@@ -702,12 +762,14 @@
     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_TIMED_OUT && fsm->retries--) {
+        return;
+    }
 
     if (datagram->state != EC_DATAGRAM_RECEIVED) {
         EC_MASTER_ERR(master, "Failed to receive address"
-                " clearing datagram: ");
+                " clearing datagram on %s link: ",
+                ec_device_names[fsm->dev_idx]);
         ec_datagram_print_state(datagram);
         master->scan_busy = 0;
         wake_up_interruptible(&master->scan_queue);
@@ -715,17 +777,20 @@
         return;
     }
 
-    if (datagram->working_counter != master->slave_count) {
-        EC_MASTER_WARN(master, "Failed to clear all station addresses:"
+    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",
-                datagram->working_counter, master->slave_count);
+                ec_device_names[fsm->dev_idx], datagram->working_counter,
+                fsm->slaves_responding[fsm->dev_idx]);
     }
 
     EC_MASTER_DBG(master, 1, "Sending broadcast-write"
-            " to measure transmission delays.\n");
+            " to measure transmission delays on %s link.\n",
+            ec_device_names[fsm->dev_idx]);
 
     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;
 }
@@ -745,7 +810,8 @@
         return;
 
     if (datagram->state != EC_DATAGRAM_RECEIVED) {
-        EC_MASTER_ERR(master, "Failed to receive delay measuring datagram: ");
+        EC_MASTER_ERR(master, "Failed to receive delay measuring datagram"
+                " on %s link: ", ec_device_names[fsm->dev_idx]);
         ec_datagram_print_state(datagram);
         master->scan_busy = 0;
         wake_up_interruptible(&master->scan_queue);
@@ -753,16 +819,30 @@
         return;
     }
 
-    EC_MASTER_DBG(master, 1, "%u slaves responded to delay measuring.\n",
-            datagram->working_counter);
+    EC_MASTER_DBG(master, 1, "%u slaves responded to delay measuring"
+            " on %s link.\n",
+            datagram->working_counter, ec_device_names[fsm->dev_idx]);
+
+    do {
+        fsm->dev_idx++;
+    } while (fsm->dev_idx < EC_NUM_DEVICES &&
+            !fsm->slaves_responding[fsm->dev_idx]);
+    if (fsm->dev_idx < EC_NUM_DEVICES) {
+        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]);
     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;
 }
 
 /*****************************************************************************/
@@ -779,8 +859,10 @@
 #ifdef EC_EOE
     ec_slave_t *slave = fsm->slave;
 #endif
-    if (ec_fsm_slave_scan_exec(&fsm->fsm_slave_scan))
-        return;
+
+    if (ec_fsm_slave_scan_exec(&fsm->fsm_slave_scan)) {
+        return;
+    }
 
 #ifdef EC_EOE
     if (slave->sii.mailbox_protocols & EC_MBOX_EOE) {
@@ -800,8 +882,12 @@
     // 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]);
         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;
     }
 
@@ -843,8 +929,9 @@
 {
     ec_master_t *master = fsm->master;
 
-    if (ec_fsm_slave_config_exec(&fsm->fsm_slave_config))
-        return;
+    if (ec_fsm_slave_config_exec(&fsm->fsm_slave_config)) {
+        return;
+    }
 
     fsm->slave->force_config = 0;
 
@@ -886,6 +973,7 @@
             //     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;
@@ -1031,6 +1119,7 @@
     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;
 }
@@ -1135,7 +1224,9 @@
     ec_slave_t *slave = fsm->slave;
     ec_master_t *master = fsm->master;
 
-    if (ec_fsm_coe_exec(&fsm->fsm_coe)) return;
+    if (ec_fsm_coe_exec(&fsm->fsm_coe)) {
+        return;
+    }
 
     if (!ec_fsm_coe_success(&fsm->fsm_coe)) {
         ec_fsm_master_restart(fsm);
--- a/master/fsm_master.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/fsm_master.h	Thu Sep 06 18:28:57 2012 +0200
@@ -115,12 +115,19 @@
     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; /**< Last main device link state. */
-    unsigned int slaves_responding; /**< number of responding slaves */
+    uint8_t link_state[EC_NUM_DEVICES]; /**< Last link state for every device.
+                                         */
+    unsigned int slaves_responding[EC_NUM_DEVICES]; /**< Number of responding
+                                                      slaves for every device.
+                                                     */
     unsigned int rescan_required; /**< A bus rescan is required. */
-    ec_slave_state_t slave_states; /**< states of responding slaves */
+    ec_slave_state_t slave_states[EC_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 */
--- a/master/fsm_slave.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/fsm_slave.c	Thu Sep 06 18:28:57 2012 +0200
@@ -145,16 +145,19 @@
         )
 {
     // Check for pending external SDO requests
-    if (ec_fsm_slave_action_process_sdo(fsm))
-        return;
+    if (ec_fsm_slave_action_process_sdo(fsm)) {
+        return;
+    }
 
     // Check for pending FoE requests
-    if (ec_fsm_slave_action_process_foe(fsm))
-        return;
+    if (ec_fsm_slave_action_process_foe(fsm)) {
+        return;
+    }
 
     // Check for pending SoE requests
-    if (ec_fsm_slave_action_process_soe(fsm))
-        return;
+    if (ec_fsm_slave_action_process_soe(fsm)) {
+        return;
+    }
 }
 
 /*****************************************************************************/
--- a/master/globals.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/globals.h	Thu Sep 06 18:28:57 2012 +0200
@@ -196,6 +196,16 @@
     EC_SDO_ENTRY_ACCESS_COUNT /**< Number of states. */
 };
 
+/** Master devices.
+ */
+typedef enum {
+    EC_DEVICE_MAIN, /**< Main device. */
+    EC_DEVICE_BACKUP, /**< Backup device */
+    EC_NUM_DEVICES /**< Number of devices. */
+} ec_device_index_t;
+
+extern const char *ec_device_names[EC_NUM_DEVICES];
+
 /*****************************************************************************/
 
 /** Convenience macro for printing EtherCAT-specific information to syslog.
--- a/master/ioctl.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/ioctl.h	Thu Sep 06 18:28:57 2012 +0200
@@ -56,7 +56,7 @@
  *
  * Increment this when changing the ioctl interface!
  */
-#define EC_IOCTL_VERSION_MAGIC 12
+#define EC_IOCTL_VERSION_MAGIC 18
 
 // Command-line tool
 #define EC_IOCTL_MODULE                EC_IOR(0x00, ec_ioctl_module_t)
@@ -101,42 +101,43 @@
 #define EC_IOCTL_SEND                   EC_IO(0x23)
 #define EC_IOCTL_RECEIVE                EC_IO(0x24)
 #define EC_IOCTL_MASTER_STATE          EC_IOR(0x25, ec_master_state_t)
-#define EC_IOCTL_APP_TIME              EC_IOW(0x26, ec_ioctl_app_time_t)
-#define EC_IOCTL_SYNC_REF               EC_IO(0x27)
-#define EC_IOCTL_SYNC_SLAVES            EC_IO(0x28)
-#define EC_IOCTL_SYNC_MON_QUEUE         EC_IO(0x29)
-#define EC_IOCTL_SYNC_MON_PROCESS      EC_IOR(0x2a, uint32_t)
-#define EC_IOCTL_RESET                  EC_IO(0x2b)
-#define EC_IOCTL_SC_SYNC               EC_IOW(0x2c, ec_ioctl_config_t)
-#define EC_IOCTL_SC_WATCHDOG           EC_IOW(0x2d, ec_ioctl_config_t)
-#define EC_IOCTL_SC_ADD_PDO            EC_IOW(0x2e, ec_ioctl_config_pdo_t)
-#define EC_IOCTL_SC_CLEAR_PDOS         EC_IOW(0x2f, ec_ioctl_config_pdo_t)
-#define EC_IOCTL_SC_ADD_ENTRY          EC_IOW(0x30, ec_ioctl_add_pdo_entry_t)
-#define EC_IOCTL_SC_CLEAR_ENTRIES      EC_IOW(0x31, ec_ioctl_config_pdo_t)
-#define EC_IOCTL_SC_REG_PDO_ENTRY     EC_IOWR(0x32, ec_ioctl_reg_pdo_entry_t)
-#define EC_IOCTL_SC_DC                 EC_IOW(0x33, ec_ioctl_config_t)
-#define EC_IOCTL_SC_SDO                EC_IOW(0x34, ec_ioctl_sc_sdo_t)
-#define EC_IOCTL_SC_SDO_REQUEST       EC_IOWR(0x35, ec_ioctl_sdo_request_t)
-#define EC_IOCTL_SC_VOE               EC_IOWR(0x36, ec_ioctl_voe_t)
-#define EC_IOCTL_SC_STATE             EC_IOWR(0x37, ec_ioctl_sc_state_t)
-#define EC_IOCTL_SC_IDN                EC_IOW(0x38, ec_ioctl_sc_idn_t)
-#define EC_IOCTL_DOMAIN_OFFSET          EC_IO(0x39)
-#define EC_IOCTL_DOMAIN_PROCESS         EC_IO(0x3a)
-#define EC_IOCTL_DOMAIN_QUEUE           EC_IO(0x3b)
-#define EC_IOCTL_DOMAIN_STATE         EC_IOWR(0x3c, ec_ioctl_domain_state_t)
-#define EC_IOCTL_SDO_REQUEST_TIMEOUT  EC_IOWR(0x3d, ec_ioctl_sdo_request_t)
-#define EC_IOCTL_SDO_REQUEST_STATE    EC_IOWR(0x3e, ec_ioctl_sdo_request_t)
-#define EC_IOCTL_SDO_REQUEST_READ     EC_IOWR(0x3f, ec_ioctl_sdo_request_t)
-#define EC_IOCTL_SDO_REQUEST_WRITE    EC_IOWR(0x40, ec_ioctl_sdo_request_t)
-#define EC_IOCTL_SDO_REQUEST_DATA     EC_IOWR(0x41, ec_ioctl_sdo_request_t)
-#define EC_IOCTL_VOE_SEND_HEADER       EC_IOW(0x42, ec_ioctl_voe_t)
-#define EC_IOCTL_VOE_REC_HEADER       EC_IOWR(0x43, ec_ioctl_voe_t)
-#define EC_IOCTL_VOE_READ              EC_IOW(0x44, ec_ioctl_voe_t)
-#define EC_IOCTL_VOE_READ_NOSYNC       EC_IOW(0x45, ec_ioctl_voe_t)
-#define EC_IOCTL_VOE_WRITE            EC_IOWR(0x46, ec_ioctl_voe_t)
-#define EC_IOCTL_VOE_EXEC             EC_IOWR(0x47, ec_ioctl_voe_t)
-#define EC_IOCTL_VOE_DATA             EC_IOWR(0x48, ec_ioctl_voe_t)
-#define EC_IOCTL_SET_SEND_INTERVAL     EC_IOW(0x49, size_t)
+#define EC_IOCTL_MASTER_LINK_STATE    EC_IOWR(0x26, ec_ioctl_link_state_t)
+#define EC_IOCTL_APP_TIME              EC_IOW(0x27, ec_ioctl_app_time_t)
+#define EC_IOCTL_SYNC_REF               EC_IO(0x28)
+#define EC_IOCTL_SYNC_SLAVES            EC_IO(0x29)
+#define EC_IOCTL_SYNC_MON_QUEUE         EC_IO(0x2a)
+#define EC_IOCTL_SYNC_MON_PROCESS      EC_IOR(0x2b, uint32_t)
+#define EC_IOCTL_RESET                  EC_IO(0x2c)
+#define EC_IOCTL_SC_SYNC               EC_IOW(0x2d, ec_ioctl_config_t)
+#define EC_IOCTL_SC_WATCHDOG           EC_IOW(0x2e, ec_ioctl_config_t)
+#define EC_IOCTL_SC_ADD_PDO            EC_IOW(0x2f, ec_ioctl_config_pdo_t)
+#define EC_IOCTL_SC_CLEAR_PDOS         EC_IOW(0x20, ec_ioctl_config_pdo_t)
+#define EC_IOCTL_SC_ADD_ENTRY          EC_IOW(0x31, ec_ioctl_add_pdo_entry_t)
+#define EC_IOCTL_SC_CLEAR_ENTRIES      EC_IOW(0x32, ec_ioctl_config_pdo_t)
+#define EC_IOCTL_SC_REG_PDO_ENTRY     EC_IOWR(0x33, ec_ioctl_reg_pdo_entry_t)
+#define EC_IOCTL_SC_DC                 EC_IOW(0x34, ec_ioctl_config_t)
+#define EC_IOCTL_SC_SDO                EC_IOW(0x35, ec_ioctl_sc_sdo_t)
+#define EC_IOCTL_SC_SDO_REQUEST       EC_IOWR(0x36, ec_ioctl_sdo_request_t)
+#define EC_IOCTL_SC_VOE               EC_IOWR(0x37, ec_ioctl_voe_t)
+#define EC_IOCTL_SC_STATE             EC_IOWR(0x38, ec_ioctl_sc_state_t)
+#define EC_IOCTL_SC_IDN                EC_IOW(0x39, ec_ioctl_sc_idn_t)
+#define EC_IOCTL_DOMAIN_OFFSET          EC_IO(0x3a)
+#define EC_IOCTL_DOMAIN_PROCESS         EC_IO(0x3b)
+#define EC_IOCTL_DOMAIN_QUEUE           EC_IO(0x3c)
+#define EC_IOCTL_DOMAIN_STATE         EC_IOWR(0x3d, ec_ioctl_domain_state_t)
+#define EC_IOCTL_SDO_REQUEST_TIMEOUT  EC_IOWR(0x3e, ec_ioctl_sdo_request_t)
+#define EC_IOCTL_SDO_REQUEST_STATE    EC_IOWR(0x3f, ec_ioctl_sdo_request_t)
+#define EC_IOCTL_SDO_REQUEST_READ     EC_IOWR(0x30, ec_ioctl_sdo_request_t)
+#define EC_IOCTL_SDO_REQUEST_WRITE    EC_IOWR(0x41, ec_ioctl_sdo_request_t)
+#define EC_IOCTL_SDO_REQUEST_DATA     EC_IOWR(0x42, ec_ioctl_sdo_request_t)
+#define EC_IOCTL_VOE_SEND_HEADER       EC_IOW(0x43, ec_ioctl_voe_t)
+#define EC_IOCTL_VOE_REC_HEADER       EC_IOWR(0x44, ec_ioctl_voe_t)
+#define EC_IOCTL_VOE_READ              EC_IOW(0x45, ec_ioctl_voe_t)
+#define EC_IOCTL_VOE_READ_NOSYNC       EC_IOW(0x46, ec_ioctl_voe_t)
+#define EC_IOCTL_VOE_WRITE            EC_IOWR(0x47, ec_ioctl_voe_t)
+#define EC_IOCTL_VOE_EXEC             EC_IOWR(0x48, ec_ioctl_voe_t)
+#define EC_IOCTL_VOE_DATA             EC_IOWR(0x49, ec_ioctl_voe_t)
+#define EC_IOCTL_SET_SEND_INTERVAL     EC_IOW(0x4a, size_t)
 
 /*****************************************************************************/
 
@@ -168,11 +169,22 @@
         uint64_t tx_count;
         uint64_t rx_count;
         uint64_t tx_bytes;
+        uint64_t rx_bytes;
         uint64_t tx_errors;
-        uint32_t tx_frame_rates[EC_RATE_COUNT];
-        uint32_t tx_byte_rates[EC_RATE_COUNT];
-        int32_t loss_rates[EC_RATE_COUNT];
-    } devices[2];
+        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_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;
     uint16_t ref_clock;
 } ec_ioctl_master_t;
@@ -184,6 +196,7 @@
     uint16_t position;
 
     // outputs
+    unsigned int device_index;
     uint32_t vendor_id;
     uint32_t product_code;
     uint32_t revision_number;
@@ -279,7 +292,7 @@
     // outputs
     uint32_t data_size;
     uint32_t logical_base_address;
-    uint16_t working_counter;
+    uint16_t working_counter[EC_NUM_DEVICES];
     uint16_t expected_working_counter;
     uint32_t fmmu_count;
 } ec_ioctl_domain_t;
@@ -661,6 +674,16 @@
 
 typedef struct {
     // inputs
+    uint32_t dev_idx;
+
+    // outputs
+    ec_master_link_state_t *state;
+} ec_ioctl_link_state_t;
+
+/*****************************************************************************/
+
+typedef struct {
+    // inputs
     uint64_t app_time;
 } ec_ioctl_app_time_t;
 
--- a/master/master.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/master.c	Thu Sep 06 18:28:57 2012 +0200
@@ -82,6 +82,12 @@
 
 #endif
 
+/** List of intervals for statistics [s].
+ */
+const unsigned int rate_intervals[] = {
+    1, 10, 60
+};
+
 /*****************************************************************************/
 
 void ec_master_clear_slave_configs(ec_master_t *);
@@ -92,6 +98,8 @@
 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 *);
 
 /*****************************************************************************/
 
@@ -101,11 +109,13 @@
 {
 #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);
+    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);
+    ext_injection_timeout_jiffies =
+        max(EC_SDO_INJECTION_TIMEOUT * HZ / 1000000, 1);
 #endif
 }
 
@@ -132,8 +142,9 @@
 
     sema_init(&master->master_sem, 1);
 
-    master->main_mac = main_mac;
-    master->backup_mac = backup_mac;
+    master->macs[EC_DEVICE_MAIN] = main_mac;
+    master->macs[EC_DEVICE_BACKUP] = backup_mac;
+    ec_master_clear_device_stats(master);
 
     sema_init(&master->device_sem, 1);
 
@@ -201,11 +212,11 @@
     init_waitqueue_head(&master->reg_queue);
 
     // init devices
-    ret = ec_device_init(&master->main_device, master);
+    ret = ec_device_init(&master->devices[EC_DEVICE_MAIN], master);
     if (ret < 0)
         goto out_return;
 
-    ret = ec_device_init(&master->backup_device, master);
+    ret = ec_device_init(&master->devices[EC_DEVICE_BACKUP], master);
     if (ret < 0)
         goto out_clear_main;
 
@@ -224,7 +235,8 @@
 
     // init reference sync datagram
     ec_datagram_init(&master->ref_sync_datagram);
-    snprintf(master->ref_sync_datagram.name, EC_DATAGRAM_NAME_SIZE, "refsync");
+    snprintf(master->ref_sync_datagram.name, EC_DATAGRAM_NAME_SIZE,
+            "refsync");
     ret = ec_datagram_apwr(&master->ref_sync_datagram, 0, 0x0910, 8);
     if (ret < 0) {
         ec_datagram_clear(&master->ref_sync_datagram);
@@ -246,7 +258,8 @@
 
     // init sync monitor datagram
     ec_datagram_init(&master->sync_mon_datagram);
-    snprintf(master->sync_mon_datagram.name, EC_DATAGRAM_NAME_SIZE, "syncmon");
+    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);
@@ -299,9 +312,9 @@
     ec_fsm_master_clear(&master->fsm);
     ec_datagram_clear(&master->fsm_datagram);
 out_clear_backup:
-    ec_device_clear(&master->backup_device);
+    ec_device_clear(&master->devices[EC_DEVICE_BACKUP]);
 out_clear_main:
-    ec_device_clear(&master->main_device);
+    ec_device_clear(&master->devices[EC_DEVICE_MAIN]);
 out_return:
     return ret;
 }
@@ -334,8 +347,8 @@
     ec_datagram_clear(&master->ref_sync_datagram);
     ec_fsm_master_clear(&master->fsm);
     ec_datagram_clear(&master->fsm_datagram);
-    ec_device_clear(&master->backup_device);
-    ec_device_clear(&master->main_device);
+    ec_device_clear(&master->devices[EC_DEVICE_BACKUP]);
+    ec_device_clear(&master->devices[EC_DEVICE_MAIN]);
 }
 
 /*****************************************************************************/
@@ -541,6 +554,7 @@
         )
 {
     int ret;
+    ec_device_index_t dev_idx;
 
     EC_MASTER_DBG(master, 1, "ORPHANED -> IDLE.\n");
 
@@ -551,7 +565,9 @@
     master->phase = EC_IDLE;
 
     // reset number of responding slaves to trigger scanning
-    master->fsm.slaves_responding = 0;
+    for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_NUM_DEVICES; dev_idx++) {
+        master->fsm.slaves_responding[dev_idx] = 0;
+    }
 
     ret = ec_master_thread_start(master, ec_master_idle_thread,
             "EtherCAT-IDLE");
@@ -585,7 +601,9 @@
 
 /** Transition function from IDLE to OPERATION phase.
  */
-int ec_master_enter_operation_phase(ec_master_t *master /**< EtherCAT master */)
+int ec_master_enter_operation_phase(
+        ec_master_t *master /**< EtherCAT master */
+        )
 {
     int ret = 0;
     ec_slave_t *slave;
@@ -622,7 +640,8 @@
         up(&master->scan_sem);
 
         // wait for slave scan to complete
-        ret = wait_event_interruptible(master->scan_queue, !master->scan_busy);
+        ret = wait_event_interruptible(master->scan_queue,
+                !master->scan_busy);
         if (ret) {
             EC_MASTER_INFO(master, "Waiting for slave scan"
                     " interrupted by signal.\n");
@@ -712,8 +731,7 @@
 #endif
             datagram->jiffies_sent = 0;
             ec_master_queue_datagram(master, datagram);
-        }
-        else {
+        } else {
             if (datagram->data_size > master->max_queue_size) {
                 list_del_init(&datagram->queue);
                 datagram->state = EC_DATAGRAM_ERROR;
@@ -880,14 +898,17 @@
 
 /*****************************************************************************/
 
-/** Sends the datagrams in the queue.
+/** Sends the datagrams in the queue for a certain device.
  *
  */
-void ec_master_send_datagrams(ec_master_t *master /**< EtherCAT master */)
+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;
+    uint8_t *frame_data, *cur_data = NULL;
     void *follows_word;
 #ifdef EC_HAVE_CYCLES
     cycles_t cycles_start, cycles_sent, cycles_end;
@@ -902,18 +923,27 @@
     frame_count = 0;
     INIT_LIST_HEAD(&sent_datagrams);
 
-    EC_MASTER_DBG(master, 2, "ec_master_send_datagrams\n");
+    EC_MASTER_DBG(master, 2, "%s(device_index = %u)\n",
+            __func__, device_index);
 
     do {
-        // fetch pointer to transmit socket buffer
-        frame_data = ec_device_tx_data(&master->main_device);
-        cur_data = frame_data + EC_FRAME_HEADER_SIZE;
+        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) continue;
+            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
@@ -926,15 +956,17 @@
             list_add_tail(&datagram->sent, &sent_datagrams);
             datagram->index = master->datagram_index++;
 
-            EC_MASTER_DBG(master, 2, "adding datagram 0x%02X\n",
+            EC_MASTER_DBG(master, 2, "Adding datagram 0x%02X\n",
                     datagram->index);
 
-            // set "datagram following" flag in previous frame
-            if (follows_word)
-                EC_WRITE_U16(follows_word, EC_READ_U16(follows_word) | 0x8000);
+            // 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, 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);
@@ -958,7 +990,7 @@
 
         // EtherCAT frame header
         EC_WRITE_U16(frame_data, ((cur_data - frame_data
-                                   - EC_FRAME_HEADER_SIZE) & 0x7FF) | 0x1000);
+                        - EC_FRAME_HEADER_SIZE) & 0x7FF) | 0x1000);
 
         // pad frame
         while (cur_data - frame_data < ETH_ZLEN - ETH_HLEN)
@@ -967,7 +999,8 @@
         EC_MASTER_DBG(master, 2, "frame size: %zu\n", cur_data - frame_data);
 
         // send frame
-        ec_device_send(&master->main_device, cur_data - frame_data);
+        ec_device_send(&master->devices[device_index],
+                cur_data - frame_data);
 #ifdef EC_HAVE_CYCLES
         cycles_sent = get_cycles();
 #endif
@@ -990,8 +1023,8 @@
 #ifdef EC_HAVE_CYCLES
     if (unlikely(master->debug_level > 1)) {
         cycles_end = get_cycles();
-        EC_MASTER_DBG(master, 0, "ec_master_send_datagrams"
-                " sent %u frames in %uus.\n", frame_count,
+        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
@@ -1090,7 +1123,7 @@
                         EC_DATAGRAM_HEADER_SIZE + data_size
                         + EC_DATAGRAM_FOOTER_SIZE);
 #ifdef EC_DEBUG_RING
-                ec_device_debug_ring_print(&master->main_device);
+                ec_device_debug_ring_print(&master->devices[EC_DEVICE_MAIN]);
 #endif
             }
 
@@ -1115,9 +1148,11 @@
         // dequeue the received datagram
         datagram->state = EC_DATAGRAM_RECEIVED;
 #ifdef EC_HAVE_CYCLES
-        datagram->cycles_received = master->main_device.cycles_poll;
-#endif
-        datagram->jiffies_received = master->main_device.jiffies_poll;
+        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);
     }
 }
@@ -1155,6 +1190,83 @@
     }
 }
 
+/*****************************************************************************/
+
+/** 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.tx_byte_rates[i] = 0;
+        master->device_stats.loss_rates[i] = 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;
+
+    // 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;
+
+    ec_device_update_stats(&master->devices[EC_DEVICE_MAIN]);
+    ec_device_update_stats(&master->devices[EC_DEVICE_BACKUP]);
+
+    s->jiffies = jiffies;
+}
 
 /*****************************************************************************/
 
@@ -1278,8 +1390,8 @@
         }
         ecrt_master_send(master);
 #ifdef EC_USE_HRTIMER
-        sent_bytes = master->main_device.tx_skb[
-            master->main_device.tx_ring_index]->len;
+        sent_bytes = master->devices[EC_DEVICE_MAIN].tx_skb[
+            master->devices[EC_DEVICE_MAIN].tx_ring_index]->len;
 #endif
         up(&master->io_sem);
 
@@ -1932,7 +2044,8 @@
     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))) {
+    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);
     }
@@ -2115,32 +2228,42 @@
 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 datagrams produced by master & slave FSMs
+        // inject datagrams produced by master and slave FSMs
         ec_master_queue_datagram(master, &master->fsm_datagram);
         master->injection_seq_rt = master->injection_seq_fsm;
     }
 
     ec_master_inject_external_datagrams(master);
 
-    if (unlikely(!master->main_device.link_state)) {
-        // link is down, no datagram can be sent
-        list_for_each_entry_safe(datagram, n, &master->datagram_queue, queue) {
-            datagram->state = EC_DATAGRAM_ERROR;
-            list_del_init(&datagram->queue);
-        }
-
-        // query link state
-        ec_device_poll(&master->main_device);
-
-        // clear frame statistics
-        ec_device_clear_stats(&master->main_device);
-        return;
-    }
-
-    // send frames
-    ec_master_send_datagrams(master);
+    for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_NUM_DEVICES; 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);
+    }
 }
 
 /*****************************************************************************/
@@ -2150,18 +2273,22 @@
     ec_datagram_t *datagram, *next;
 
     // receive datagrams
-    ec_device_poll(&master->main_device);
+    ec_device_poll(&master->devices[EC_DEVICE_MAIN]);
+    if (master->devices[EC_DEVICE_BACKUP].dev) {
+        ec_device_poll(&master->devices[EC_DEVICE_BACKUP]);
+    }
+    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->main_device.cycles_poll - datagram->cycles_sent
-                > timeout_cycles) {
+        if (master->devices[EC_DEVICE_MAIN].cycles_poll -
+                datagram->cycles_sent > timeout_cycles) {
 #else
-        if (master->main_device.jiffies_poll - datagram->jiffies_sent
-                > timeout_jiffies) {
+        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;
@@ -2171,10 +2298,12 @@
             if (unlikely(master->debug_level > 0)) {
                 unsigned int time_us;
 #ifdef EC_HAVE_CYCLES
-                time_us = (unsigned int) (master->main_device.cycles_poll -
+                time_us = (unsigned int)
+                    (master->devices[EC_DEVICE_MAIN].cycles_poll -
                         datagram->cycles_sent) * 1000 / cpu_khz;
 #else
-                time_us = (unsigned int) ((master->main_device.jiffies_poll -
+                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,"
@@ -2279,7 +2408,7 @@
             " master_info = 0x%p)\n", master, master_info);
 
     master_info->slave_count = master->slave_count;
-    master_info->link_up = master->main_device.link_state;
+    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;
@@ -2358,9 +2487,38 @@
 
 void ecrt_master_state(const ec_master_t *master, ec_master_state_t *state)
 {
-    state->slaves_responding = master->fsm.slaves_responding;
-    state->al_states = master->fsm.slave_states;
-    state->link_up = master->main_device.link_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_NUM_DEVICES; 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_NUM_DEVICES) {
+        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;
 }
 
 /*****************************************************************************/
@@ -2838,6 +2996,7 @@
 EXPORT_SYMBOL(ecrt_master_get_slave);
 EXPORT_SYMBOL(ecrt_master_slave_config);
 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_slave_clocks);
--- a/master/master.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/master.h	Thu Sep 06 18:28:57 2012 +0200
@@ -137,6 +137,37 @@
 
 /*****************************************************************************/
 
+/** 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;
+
+/*****************************************************************************/
+
 /** EtherCAT master.
  *
  * Manages slaves, domains and IO.
@@ -154,11 +185,10 @@
 
     struct semaphore master_sem; /**< Master semaphore. */
 
-    ec_device_t main_device; /**< EtherCAT main device. */
-    const uint8_t *main_mac; /**< MAC address of main device. */
-    ec_device_t backup_device; /**< EtherCAT backup device. */
-    const uint8_t *backup_mac; /**< MAC address of backup device. */
+    ec_device_t devices[EC_NUM_DEVICES]; /**< EtherCAT devices. */
+    const uint8_t *macs[EC_NUM_DEVICES]; /**< Device MAC addresses. */
     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. */
@@ -210,8 +240,8 @@
                                       ext_datagram_queue. */
 
     struct list_head external_datagram_queue; /**< External Datagram queue. */
-    unsigned int send_interval; /**< Interval between calls to
-                                  ecrt_master_send */
+    unsigned int send_interval; /**< Interval between two calls to
+                                  ecrt_master_send(). */
     size_t max_queue_size; /**< Maximum size of datagram queue */
 
     unsigned int debug_level; /**< Master debug level. */
@@ -310,6 +340,8 @@
 void ec_master_internal_send_cb(void *);
 void ec_master_internal_receive_cb(void *);
 
-/*****************************************************************************/
-
-#endif
+extern const unsigned int rate_intervals[EC_RATE_COUNT]; // see master.c
+
+/*****************************************************************************/
+
+#endif
--- a/master/module.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/module.c	Thu Sep 06 18:28:57 2012 +0200
@@ -229,13 +229,19 @@
 
 /*****************************************************************************/
 
+/** 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 */
+        char *buffer /**< Target buffer. */
         )
 {
     off_t off = 0;
@@ -289,7 +295,7 @@
 
 /** Parse a MAC address from a string.
  *
- * The MAC address must follow the regexp
+ * 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
@@ -303,8 +309,7 @@
     if (!strlen(src)) {
         if (allow_empty){
             return 0;
-        }
-        else {
+        } else {
             EC_ERR("MAC address may not be empty.\n");
             return -EINVAL;
         }
@@ -319,8 +324,9 @@
             return -EINVAL;
         }
         mac[i] = value;
-        if (i < ETH_ALEN - 1)
+        if (i < ETH_ALEN - 1) {
             src = rem + 1; // skip colon
+        }
     }
 
     return 0;
@@ -445,6 +451,13 @@
  *  Device interface
  *****************************************************************************/
 
+/** Device names.
+ */
+const char *ec_device_names[EC_NUM_DEVICES] = {
+    "main",
+    "backup"
+};
+
 /** Offers an EtherCAT device to a certain master.
  *
  * The master decides, if it wants to use the device for EtherCAT operation,
@@ -462,47 +475,43 @@
         )
 {
     ec_master_t *master;
-    char str[20];
-    unsigned int i;
+    char str[EC_MAX_MAC_STRING_SIZE];
+    unsigned int i, j;
 
     for (i = 0; i < master_count; i++) {
         master = &masters[i];
+        ec_mac_print(net_dev->dev_addr, str);
 
         down(&master->device_sem);
-        if (master->main_device.dev) { // master already has a device
-            up(&master->device_sem);
-            continue;
-        }
-
-        if (ec_mac_equal(master->main_mac, net_dev->dev_addr)
-                || ec_mac_is_broadcast(master->main_mac)) {
-            ec_mac_print(net_dev->dev_addr, str);
-            EC_INFO("Accepting device %s for master %u.\n",
-                    str, master->index);
-
-            ec_device_attach(&master->main_device, net_dev, poll, module);
-            up(&master->device_sem);
-
-            snprintf(net_dev->name, IFNAMSIZ, "ec%u", master->index);
-
-            return &master->main_device; // offer accepted
-        }
-        else {
-            up(&master->device_sem);
-
-            if (master->debug_level) {
-                ec_mac_print(net_dev->dev_addr, str);
-                EC_MASTER_DBG(master, 0, "Master declined device %s.\n",
-                        str);
+
+        for (j = 0; j < EC_NUM_DEVICES; j++) {
+            if (!master->devices[j].dev
+                && (ec_mac_equal(master->macs[j], net_dev->dev_addr)
+                    || ec_mac_is_broadcast(master->macs[j]))) {
+
+                EC_INFO("Accepting %s as %s device for master %u.\n",
+                        str, ec_device_names[j], master->index);
+
+                ec_device_attach(&master->devices[j], net_dev, poll, module);
+                up(&master->device_sem);
+
+                snprintf(net_dev->name, IFNAMSIZ, "ec%c%u",
+                        ec_device_names[j][0], master->index);
+
+                return &master->devices[j]; // offer accepted
             }
         }
+
+        up(&master->device_sem);
+
+        EC_MASTER_DBG(master, 1, "Master declined device %s.\n", str);
     }
 
     return NULL; // offer declined
 }
 
 /******************************************************************************
- *  Realtime interface
+ * Application interface
  *****************************************************************************/
 
 /** Request a master.
@@ -514,6 +523,7 @@
         )
 {
     ec_master_t *master, *errptr = NULL;
+    unsigned int i, got_modules = 0;
 
     EC_INFO("Requesting master %u...\n", master_index);
 
@@ -550,11 +560,17 @@
         goto out_release;
     }
 
-    if (!try_module_get(master->main_device.module)) {
-        up(&master->device_sem);
-        EC_ERR("Device module is unloading!\n");
-        errptr = ERR_PTR(-ENODEV);
-        goto out_release;
+    for (i = 0; i < EC_NUM_DEVICES; i++) {
+        ec_device_t *device = &master->devices[i];
+        if (device->dev) {
+            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;
+            }
+        }
+        got_modules++;
     }
 
     up(&master->device_sem);
@@ -569,7 +585,12 @@
     return master;
 
  out_module_put:
-    module_put(master->main_device.module);
+    for (; got_modules > 0; got_modules--) {
+        ec_device_t *device = &master->devices[i - 1];
+        if (device->dev) {
+            module_put(device->module);
+        }
+    }
  out_release:
     master->reserved = 0;
  out_return:
@@ -588,6 +609,8 @@
 
 void ecrt_release_master(ec_master_t *master)
 {
+    unsigned int i;
+
     EC_MASTER_INFO(master, "Releasing master...\n");
 
     if (!master->reserved) {
@@ -598,7 +621,12 @@
 
     ec_master_leave_operation_phase(master);
 
-    module_put(master->main_device.module);
+    for (i = 0; i < EC_NUM_DEVICES; i++) {
+        if (master->devices[i].dev) {
+            module_put(master->devices[i].module);
+        }
+    }
+
     master->reserved = 0;
 
     EC_MASTER_INFO(master, "Released.\n");
--- a/master/slave.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/slave.c	Thu Sep 06 18:28:57 2012 +0200
@@ -62,6 +62,7 @@
 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 */
         )
@@ -70,6 +71,7 @@
     int ret;
 
     slave->master = master;
+    slave->device_index = dev_idx;
     slave->ring_position = ring_position;
     slave->station_address = station_address;
     slave->effective_alias = 0x0000;
--- a/master/slave.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/slave.h	Thu Sep 06 18:28:57 2012 +0200
@@ -174,6 +174,8 @@
 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. */
@@ -238,7 +240,8 @@
 /*****************************************************************************/
 
 // slave construction/destruction
-void ec_slave_init(ec_slave_t *, ec_master_t *, uint16_t, uint16_t);
+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 *);
--- a/master/slave_config.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/slave_config.c	Thu Sep 06 18:28:57 2012 +0200
@@ -78,8 +78,6 @@
         ec_sync_config_init(&sc->sync_configs[i]);
 
     sc->used_fmmus = 0;
-    sc->used_for_fmmu_datagram[EC_DIR_INPUT] = 0;
-    sc->used_for_fmmu_datagram[EC_DIR_OUTPUT] = 0;
     sc->dc_assign_activate = 0x0000;
     sc->dc_sync[0].cycle_time = 0x00000000;
     sc->dc_sync[1].cycle_time = 0x00000000;
--- a/master/slave_config.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/slave_config.h	Thu Sep 06 18:28:57 2012 +0200
@@ -134,9 +134,6 @@
                                                    configurations. */
     ec_fmmu_config_t fmmu_configs[EC_MAX_FMMUS]; /**< FMMU configurations. */
     uint8_t used_fmmus; /**< Number of FMMUs used. */
-    unsigned int used_for_fmmu_datagram[EC_DIR_COUNT]; /**< Number of FMMUs
-                                                         used for process data
-                                                         exchange datagrams. */
     uint16_t dc_assign_activate; /**< Vendor-specific AssignActivate word. */
     ec_sync_signal_t dc_sync[EC_SYNC_SIGNAL_COUNT]; /**< DC sync signals. */
 
--- a/master/voe_handler.c	Thu Sep 06 14:40:10 2012 +0200
+++ b/master/voe_handler.c	Thu Sep 06 18:28:57 2012 +0200
@@ -190,8 +190,9 @@
 {
     if (voe->config->slave) { // FIXME locking?
         voe->state(voe);
-        if (voe->request_state == EC_INT_REQUEST_BUSY)
+        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;
--- a/script/sysconfig/ethercat	Thu Sep 06 14:40:10 2012 +0200
+++ b/script/sysconfig/ethercat	Thu Sep 06 18:28:57 2012 +0200
@@ -4,10 +4,12 @@
 #
 #  $Id$
 #
+#  vim: spelllang=en spell tw=78
+#
 #------------------------------------------------------------------------------
 
 #
-# Master devices.
+# Main Ethernet devices.
 #
 # The MASTER<X>_DEVICE variable specifies the Ethernet device for a master
 # with index 'X'.
@@ -26,6 +28,15 @@
 #MASTER1_DEVICE=""
 
 #
+# Backup Ethernet devices
+#
+# The MASTER<X>_BACKUP variables specify the devices used for redundancy. They
+# behaves nearly the same as the MASTER<X>_DEVICE variable, except that it
+# does not interpret the ff:ff:ff:ff:ff:ff address.
+#
+#MASTER0_BACKUP=""
+
+#
 # Ethernet driver modules to use for EtherCAT operation.
 #
 # Specify a non-empty list of Ethernet drivers, that shall be used for EtherCAT
--- a/tool/CommandDomains.cpp	Thu Sep 06 14:40:10 2012 +0200
+++ b/tool/CommandDomains.cpp	Thu Sep 06 18:28:57 2012 +0200
@@ -136,6 +136,11 @@
     ec_ioctl_domain_fmmu_t fmmu;
     unsigned int dataOffset;
     string indent(doIndent ? "  " : "");
+    unsigned int wc_sum = 0, dev_idx;
+
+    for (dev_idx = 0; dev_idx < EC_NUM_DEVICES; dev_idx++) {
+        wc_sum += domain.working_counter[dev_idx];
+    }
 
     cout << indent << "Domain" << dec << domain.index << ":"
         << " LogBaseAddr 0x"
@@ -144,8 +149,14 @@
         << ", Size " << dec << setfill(' ')
         << setw(3) << domain.data_size
         << ", WorkingCounter "
-        << domain.working_counter << "/"
-        << domain.expected_working_counter << endl;
+        << wc_sum << "/"
+        << domain.expected_working_counter;
+    if (EC_NUM_DEVICES == 2) {
+        cout << " (" << domain.working_counter[EC_DEVICE_MAIN]
+            << "+" << domain.working_counter[EC_DEVICE_BACKUP]
+            << ")";
+    }
+    cout << endl;
 
     if (!domain.data_size || getVerbosity() != Verbose)
         return;
--- a/tool/CommandMaster.cpp	Thu Sep 06 14:40:10 2012 +0200
+++ b/tool/CommandMaster.cpp	Thu Sep 06 18:28:57 2012 +0200
@@ -93,7 +93,7 @@
             << "  Phase: ";
 
         switch (data.phase) {
-            case 0:  cout << "Waiting for device..."; break;
+            case 0:  cout << "Waiting for device(s)..."; break;
             case 1:  cout << "Idle"; break;
             case 2:  cout << "Operation"; break;
             default: cout << "???";
@@ -114,12 +114,6 @@
                     && data.devices[i].address[5] == 0x00) {
                 cout << "None.";
             } else {
-                unsigned int lost =
-                    data.devices[i].tx_count - data.devices[i].rx_count;
-                if (lost == 1) {
-                    // allow one frame travelling
-                    lost = 0;
-                }
                 cout << hex << setfill('0')
                     << setw(2) << (unsigned int) data.devices[i].address[0]
                     << ":"
@@ -139,11 +133,12 @@
                     << (data.devices[i].link_state ? "UP" : "DOWN") << endl
                     << "      Tx frames:   "
                     << data.devices[i].tx_count << endl
+                    << "      Tx bytes:    "
+                    << data.devices[i].tx_bytes << endl
                     << "      Rx frames:   "
                     << data.devices[i].rx_count << endl
-                    << "      Lost frames: " << lost << endl
-                    << "      Tx bytes:    "
-                    << data.devices[i].tx_bytes << endl
+                    << "      Rx bytes:    "
+                    << data.devices[i].rx_bytes << endl
                     << "      Tx errors:   "
                     << data.devices[i].tx_errors << endl
                     << "      Tx frame rate [1/s]: "
@@ -166,33 +161,106 @@
                     }
                 }
                 cout << endl
-                    << "      Loss rate [1/s]:     "
-                    << setprecision(0) << fixed;
-                for (j = 0; j < EC_RATE_COUNT; j++) {
-                    cout << setw(ColWidth)
-                        << data.devices[i].loss_rates[j] / 1000.0;
+                    << "      Rx frame rate [1/s]: "
+                    << setfill(' ') << setprecision(0) << fixed;
+                for (j = 0; j < EC_RATE_COUNT; j++) {
+                    cout << setw(ColWidth)
+                        << data.devices[i].rx_frame_rates[j] / 1000.0;
                     if (j < EC_RATE_COUNT - 1) {
                         cout << " ";
                     }
                 }
                 cout << endl
-                    << "      Frame loss [%]:      "
+                    << "      Rx rate [KByte/s]:   "
                     << setprecision(1) << fixed;
                 for (j = 0; j < EC_RATE_COUNT; j++) {
-                    double perc = 0.0;
-                    if (data.devices[i].tx_frame_rates[j]) {
-                        perc = 100.0 * data.devices[i].loss_rates[j] /
-                            data.devices[i].tx_frame_rates[j];
-                    }
-                    cout << setw(ColWidth) << perc;
+                    cout << setw(ColWidth)
+                        << data.devices[i].rx_byte_rates[j] / 1024.0;
                     if (j < EC_RATE_COUNT - 1) {
                         cout << " ";
                     }
                 }
                 cout << setprecision(0) << endl;
             }
-            cout << endl;
-        }
+        }
+        unsigned int lost = data.tx_count - data.rx_count;
+        if (lost == 1) {
+            // allow one frame travelling
+            lost = 0;
+        }
+        cout << "    Common:" << endl
+            << "      Tx frames:   "
+            << data.tx_count << endl
+            << "      Tx bytes:    "
+            << data.tx_bytes << endl
+            << "      Rx frames:   "
+            << data.rx_count << endl
+            << "      Rx bytes:    "
+            << data.rx_bytes << endl
+            << "      Lost frames: " << lost << endl
+            << "      Tx frame rate [1/s]: "
+            << setfill(' ') << setprecision(0) << fixed;
+        for (j = 0; j < EC_RATE_COUNT; j++) {
+            cout << setw(ColWidth)
+                << data.tx_frame_rates[j] / 1000.0;
+            if (j < EC_RATE_COUNT - 1) {
+                cout << " ";
+            }
+        }
+        cout << endl
+            << "      Tx rate [KByte/s]:   "
+            << setprecision(1) << fixed;
+        for (j = 0; j < EC_RATE_COUNT; j++) {
+            cout << setw(ColWidth)
+                << data.tx_byte_rates[j] / 1024.0;
+            if (j < EC_RATE_COUNT - 1) {
+                cout << " ";
+            }
+        }
+        cout << endl
+            << "      Rx frame rate [1/s]: "
+            << setfill(' ') << setprecision(0) << fixed;
+        for (j = 0; j < EC_RATE_COUNT; j++) {
+            cout << setw(ColWidth)
+                << data.rx_frame_rates[j] / 1000.0;
+            if (j < EC_RATE_COUNT - 1) {
+                cout << " ";
+            }
+        }
+        cout << endl
+            << "      Rx rate [KByte/s]:   "
+            << setprecision(1) << fixed;
+        for (j = 0; j < EC_RATE_COUNT; j++) {
+            cout << setw(ColWidth)
+                << data.rx_byte_rates[j] / 1024.0;
+            if (j < EC_RATE_COUNT - 1) {
+                cout << " ";
+            }
+        }
+        cout << endl
+            << "      Loss rate [1/s]:     "
+            << setprecision(0) << fixed;
+        for (j = 0; j < EC_RATE_COUNT; j++) {
+            cout << setw(ColWidth)
+                << data.loss_rates[j] / 1000.0;
+            if (j < EC_RATE_COUNT - 1) {
+                cout << " ";
+            }
+        }
+        cout << endl
+            << "      Frame loss [%]:      "
+            << setprecision(1) << fixed;
+        for (j = 0; j < EC_RATE_COUNT; j++) {
+            double perc = 0.0;
+            if (data.tx_frame_rates[j]) {
+                perc = 100.0 * data.loss_rates[j] / data.tx_frame_rates[j];
+            }
+            cout << setw(ColWidth) << perc;
+            if (j < EC_RATE_COUNT - 1) {
+                cout << " ";
+            }
+        }
+        cout << setprecision(0) << endl;
 
         cout << "  Distributed clocks:" << endl
             << "    Reference clock: ";
--- a/tool/CommandSlaves.cpp	Thu Sep 06 14:40:10 2012 +0200
+++ b/tool/CommandSlaves.cpp	Thu Sep 06 18:28:57 2012 +0200
@@ -144,7 +144,7 @@
         )
 {
     ec_ioctl_master_t master;
-    unsigned int i;
+    unsigned int i, lastDevice;
     ec_ioctl_slave_t slave;
     uint16_t lastAlias, aliasIndex;
     Info info;
@@ -184,6 +184,7 @@
 
             info.state = alStateString(slave.al_state);
             info.flag = (slave.error_flag ? 'E' : '+');
+            info.device = slave.device_index;
 
             if (strlen(slave.name)) {
                 info.name = slave.name;
@@ -215,7 +216,12 @@
         cout << "Master" << dec << m.getIndex() << endl;
     }
 
+    lastDevice = EC_DEVICE_MAIN;
     for (iter = infoList.begin(); iter != infoList.end(); iter++) {
+        if (iter->device != lastDevice) {
+            lastDevice = iter->device;
+            cout << "xxx LINK FAILURE xxx" << endl;
+        }
         cout << indent << setfill(' ') << right
             << setw(maxPosWidth) << iter->pos << "  "
             << setw(maxAliasWidth) << iter->alias
@@ -245,6 +251,7 @@
             cout << "Alias: " << si->alias << endl;
 
         cout
+            << "Device: " << (si->device_index ? "Backup" : "Main") << endl
             << "State: " << alStateString(si->al_state) << endl
             << "Flag: " << (si->error_flag ? 'E' : '+') << endl
             << "Identity:" << endl
@@ -332,7 +339,8 @@
                 }
                 cout << "  " << setw(10);
                 if (!si->ports[i].link.loop_closed) {
-                    cout << si->ports[i].receive_time - si->ports[0].receive_time;
+                    cout << si->ports[i].receive_time -
+                        si->ports[0].receive_time;
                 } else {
                     cout << "-";
                 }
@@ -406,7 +414,8 @@
                     << "    Enable SDO: "
                     << (si->coe_details.enable_sdo ? "yes" : "no") << endl
                     << "    Enable SDO Info: "
-                    << (si->coe_details.enable_sdo_info ? "yes" : "no") << endl
+                    << (si->coe_details.enable_sdo_info ? "yes" : "no")
+                    << endl
                     << "    Enable PDO Assign: "
                     << (si->coe_details.enable_pdo_assign
                             ? "yes" : "no") << endl
--- a/tool/CommandSlaves.h	Thu Sep 06 14:40:10 2012 +0200
+++ b/tool/CommandSlaves.h	Thu Sep 06 18:28:57 2012 +0200
@@ -51,6 +51,7 @@
             string state;
             string flag;
             string name;
+            unsigned int device;
         };
 
         void listSlaves(MasterDevice &, const SlaveList &, bool);