master/domain.c
changeset 2589 2b9c78543663
parent 2101 01b30593e942
child 2610 f0fdcce9874b
--- a/master/domain.c	Thu Sep 06 14:21:02 2012 +0200
+++ b/master/domain.c	Mon Nov 03 15:20:05 2014 +0100
@@ -41,6 +41,11 @@
 #include "slave_config.h"
 
 #include "domain.h"
+#include "datagram_pair.h"
+
+/** Extra debug output for redundancy functions.
+ */
+#define DEBUG_REDUNDANCY 0
 
 /*****************************************************************************/
 
@@ -56,18 +61,23 @@
         unsigned int index /**< Index. */
         )
 {
+    unsigned int dev_idx;
+
     domain->master = master;
     domain->index = index;
     INIT_LIST_HEAD(&domain->fmmu_configs);
     domain->data_size = 0;
-    domain->tx_size = 0;
     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);
+    for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master);
+            dev_idx++) {
+        domain->working_counter[dev_idx] = 0x0000;
+    }
     domain->expected_working_counter = 0x0000;
     domain->working_counter_changes = 0;
+    domain->redundancy_active = 0;
     domain->notify_jiffies = 0;
 }
 
@@ -77,12 +87,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);
@@ -99,6 +110,7 @@
     if (domain->data_origin == EC_ORIG_INTERNAL && domain->data) {
         kfree(domain->data);
     }
+
     domain->data = NULL;
     domain->data_origin = EC_ORIG_INTERNAL;
 }
@@ -115,7 +127,6 @@
     fmmu->domain = domain;
 
     domain->data_size += fmmu->data_size;
-    domain->tx_size += fmmu->tx_size;
     list_add_tail(&fmmu->list, &domain->fmmu_configs);
 
     EC_MASTER_DBG(domain->master, 1, "Domain %u:"
@@ -125,15 +136,15 @@
 
 /*****************************************************************************/
 
-/** 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. */
@@ -141,48 +152,31 @@
         const unsigned int used[] /**< Slave config counter for in/out. */
         )
 {
-    ec_datagram_t *datagram;
+    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);
-    datagram->domain = domain;
+    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;
 }
 
@@ -195,6 +189,8 @@
  *
  * Walks through the list of all FMMU configurations for the current datagram
  * and ends before the current datagram.
+ *
+ * \return Non-zero if slave connfig was already counted.
  */
 int shall_count(
         const ec_fmmu_config_t *cur_fmmu, /**< Current FMMU with direction to
@@ -238,7 +234,7 @@
     unsigned int datagram_used[EC_DIR_COUNT];
     ec_fmmu_config_t *fmmu;
     const ec_fmmu_config_t *datagram_first_fmmu = NULL;
-    const ec_datagram_t *datagram;
+    const ec_datagram_pair_t *datagram_pair;
     int ret;
 
     domain->logical_base_address = base_address;
@@ -272,9 +268,8 @@
 
         // Correct logical FMMU address
         fmmu->logical_start_address += base_address;
-        fmmu->domain_address += base_address;
-
-        // Increment input/output counter to determine datagram types
+
+        // Increment Input/Output counter to determine datagram types
         // and calculate expected working counters
         if (shall_count(fmmu, datagram_first_fmmu)) {
             datagram_used[fmmu->dir]++;
@@ -282,8 +277,8 @@
 
         // If the current FMMU's data do not fit in the current datagram,
         // allocate a new one.
-        if (datagram_size + fmmu->tx_size > EC_MAX_DATA_SIZE) {
-            ret = ec_domain_add_datagram(domain,
+        if (datagram_size + fmmu->data_size > EC_MAX_DATA_SIZE) {
+            ret = ec_domain_add_datagram_pair(domain,
                     domain->logical_base_address + datagram_offset,
                     datagram_size, domain->data + datagram_offset,
                     datagram_used);
@@ -298,13 +293,13 @@
             datagram_first_fmmu = fmmu;
         }
 
-        datagram_size += fmmu->tx_size;
-    }
-
-    // Allocate last datagram, if data are left (this is also the case if the
-    // process data fit into a single datagram)
+        datagram_size += fmmu->data_size;
+    }
+
+    /* Allocate last datagram pair, if data are left (this is also the case if
+     * the process data fit into a single datagram) */
     if (datagram_size) {
-        ret = ec_domain_add_datagram(domain,
+        ret = ec_domain_add_datagram_pair(domain,
                 domain->logical_base_address + datagram_offset,
                 datagram_size, domain->data + datagram_offset,
                 datagram_used);
@@ -317,13 +312,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;
 }
 
@@ -346,6 +344,8 @@
 /*****************************************************************************/
 
 /** Get a certain FMMU configuration via its position in the list.
+ *
+ * \return FMMU at position \a pos, or NULL.
  */
 const ec_fmmu_config_t *ec_domain_find_fmmu(
         const ec_domain_t *domain, /**< EtherCAT domain. */
@@ -363,6 +363,34 @@
     return NULL;
 }
 
+/*****************************************************************************/
+
+#if EC_MAX_NUM_DEVICES > 1
+
+/** Process received data.
+ */
+int data_changed(
+        uint8_t *send_buffer,
+        const ec_datagram_t *datagram,
+        size_t offset,
+        size_t size
+        )
+{
+    uint8_t *sent = send_buffer + offset;
+    uint8_t *recv = datagram->data + offset;
+    size_t i;
+
+    for (i = 0; i < size; i++) {
+        if (recv[i] != sent[i]) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+#endif
+
 /******************************************************************************
  *  Application interface
  *****************************************************************************/
@@ -373,7 +401,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);
 
@@ -408,14 +436,14 @@
     EC_MASTER_DBG(domain->master, 1, "ecrt_domain_external_memory("
             "domain = 0x%p, mem = 0x%p)\n", domain, mem);
 
-    ec_mutex_lock(&domain->master->master_mutex);
+    down(&domain->master->master_sem);
 
     ec_domain_clear_data(domain);
 
     domain->data = mem;
     domain->data_origin = EC_ORIG_EXTERNAL;
 
-    ec_mutex_unlock(&domain->master->master_mutex);
+    up(&domain->master->master_sem);
 }
 
 /*****************************************************************************/
@@ -429,20 +457,156 @@
 
 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_MAX_NUM_DEVICES] = {}, wc_total;
+    ec_datagram_pair_t *pair;
+#if EC_MAX_NUM_DEVICES > 1
+    uint16_t datagram_pair_wc, redundant_wc;
+    unsigned int datagram_offset;
+    ec_fmmu_config_t *fmmu = list_first_entry(&domain->fmmu_configs,
+            ec_fmmu_config_t, list);
+    unsigned int redundancy;
+#endif
+    unsigned int dev_idx;
+#ifdef EC_RT_SYSLOG
+    unsigned int wc_change;
+#endif
+
+#if DEBUG_REDUNDANCY
+    EC_MASTER_DBG(domain->master, 1, "domain %u process\n", domain->index);
+#endif
+
+    list_for_each_entry(pair, &domain->datagram_pairs, list) {
+#if EC_MAX_NUM_DEVICES > 1
+        datagram_pair_wc = ec_datagram_pair_process(pair, wc_sum);
+#else
+        ec_datagram_pair_process(pair, wc_sum);
+#endif
+
+#if EC_MAX_NUM_DEVICES > 1
+        if (ec_master_num_devices(domain->master) > 1) {
+            ec_datagram_t *main_datagram = &pair->datagrams[EC_DEVICE_MAIN];
+            uint32_t logical_datagram_address =
+                EC_READ_U32(main_datagram->address);
+            size_t datagram_size = main_datagram->data_size;
+
+#if DEBUG_REDUNDANCY
+            EC_MASTER_DBG(domain->master, 1, "dgram %s log=%u\n",
+                    main_datagram->name, logical_datagram_address);
+#endif
+
+            /* Redundancy: Go through FMMU configs to detect data changes. */
+            list_for_each_entry_from(fmmu, &domain->fmmu_configs, list) {
+                ec_datagram_t *backup_datagram =
+                    &pair->datagrams[EC_DEVICE_BACKUP];
+
+                if (fmmu->dir != EC_DIR_INPUT) {
+                    continue;
+                }
+
+                if (fmmu->logical_start_address >=
+                        logical_datagram_address + datagram_size) {
+                    // fmmu data contained in next datagram pair
+                    break;
+                }
+
+                datagram_offset =
+                    fmmu->logical_start_address - logical_datagram_address;
+
+#if DEBUG_REDUNDANCY
+                EC_MASTER_DBG(domain->master, 1,
+                        "input fmmu log=%u size=%u offset=%u\n",
+                        fmmu->logical_start_address, fmmu->data_size,
+                        datagram_offset);
+                if (domain->master->debug_level > 0) {
+                    ec_print_data(pair->send_buffer + datagram_offset,
+                            fmmu->data_size);
+                    ec_print_data(main_datagram->data + datagram_offset,
+                            fmmu->data_size);
+                    ec_print_data(backup_datagram->data + datagram_offset,
+                            fmmu->data_size);
+                }
+#endif
+
+                if (data_changed(pair->send_buffer, main_datagram,
+                            datagram_offset, fmmu->data_size)) {
+                    /* data changed on main link: no copying necessary. */
+#if DEBUG_REDUNDANCY
+                    EC_MASTER_DBG(domain->master, 1, "main changed\n");
+#endif
+                } else if (data_changed(pair->send_buffer, backup_datagram,
+                            datagram_offset, fmmu->data_size)) {
+                    /* data changed on backup link: copy to main memory. */
+#if DEBUG_REDUNDANCY
+                    EC_MASTER_DBG(domain->master, 1, "backup changed\n");
+#endif
+                    memcpy(main_datagram->data + datagram_offset,
+                            backup_datagram->data + datagram_offset,
+                            fmmu->data_size);
+                } else if (datagram_pair_wc ==
+                        pair->expected_working_counter) {
+                    /* no change, but WC complete: use main data. */
+#if DEBUG_REDUNDANCY
+                    EC_MASTER_DBG(domain->master, 1,
+                            "no change but complete\n");
+#endif
+                } else {
+                    /* no change and WC incomplete: mark WC as zero to avoid
+                     * data.dependent WC flickering. */
+                    datagram_pair_wc = 0;
+#if DEBUG_REDUNDANCY
+                    EC_MASTER_DBG(domain->master, 1,
+                            "no change and incomplete\n");
+#endif
+                }
+            }
+        }
+#endif // EC_MAX_NUM_DEVICES > 1
+    }
+
+#if EC_MAX_NUM_DEVICES > 1
+    redundant_wc = 0;
+    for (dev_idx = EC_DEVICE_BACKUP;
+            dev_idx < ec_master_num_devices(domain->master); dev_idx++) {
+        redundant_wc += wc_sum[dev_idx];
+    }
+
+    redundancy = redundant_wc > 0;
+    if (redundancy != domain->redundancy_active) {
+#ifdef EC_RT_SYSLOG
+        if (redundancy) {
+            EC_MASTER_WARN(domain->master,
+                    "Domain %u: Redundant link in use!\n",
+                    domain->index);
+        } else {
+            EC_MASTER_INFO(domain->master,
+                    "Domain %u: Redundant link unused again.\n",
+                    domain->index);
+        }
+#endif
+        domain->redundancy_active = redundancy;
+    }
+#else
+    domain->redundancy_active = 0;
+#endif
+
+#ifdef EC_RT_SYSLOG
+    wc_change = 0;
+#endif
+    wc_total = 0;
+    for (dev_idx = EC_DEVICE_MAIN;
+            dev_idx < ec_master_num_devices(domain->master); dev_idx++) {
+        if (wc_sum[dev_idx] != domain->working_counter[dev_idx]) {
+#ifdef EC_RT_SYSLOG
+            wc_change = 1;
+#endif
+            domain->working_counter[dev_idx] = wc_sum[dev_idx];
+        }
+        wc_total += wc_sum[dev_idx];
+    }
+
+#ifdef EC_RT_SYSLOG
+    if (wc_change) {
         domain->working_counter_changes++;
-        domain->working_counter = working_counter_sum;
     }
 
     if (domain->working_counter_changes &&
@@ -450,26 +614,62 @@
         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", domain->index,
+                    wc_total, domain->expected_working_counter);
         } 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", domain->index,
+                    domain->working_counter_changes,
+                    wc_total, domain->expected_working_counter);
+        }
+#if EC_MAX_NUM_DEVICES > 1
+        if (ec_master_num_devices(domain->master) > 1) {
+            printk(" (");
+            for (dev_idx = EC_DEVICE_MAIN;
+                    dev_idx < ec_master_num_devices(domain->master);
+                    dev_idx++) {
+                printk("%u", domain->working_counter[dev_idx]);
+                if (dev_idx + 1 < ec_master_num_devices(domain->master)) {
+                    printk("+");
+                }
+            }
+            printk(")");
+        }
+#endif
+        printk(".\n");
+
         domain->working_counter_changes = 0;
     }
+#endif
 }
 
 /*****************************************************************************/
 
 void ecrt_domain_queue(ec_domain_t *domain)
 {
-    ec_datagram_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) {
+
+#if EC_MAX_NUM_DEVICES > 1
+        /* copy main data to send buffer */
+        memcpy(datagram_pair->send_buffer,
+                datagram_pair->datagrams[EC_DEVICE_MAIN].data,
+                datagram_pair->datagrams[EC_DEVICE_MAIN].data_size);
+#endif
+        ec_master_queue_datagram(domain->master,
+                &datagram_pair->datagrams[EC_DEVICE_MAIN]);
+
+        /* copy main data to backup datagram */
+        for (dev_idx = EC_DEVICE_BACKUP;
+                dev_idx < ec_master_num_devices(domain->master); dev_idx++) {
+            memcpy(datagram_pair->datagrams[dev_idx].data,
+                    datagram_pair->datagrams[EC_DEVICE_MAIN].data,
+                    datagram_pair->datagrams[EC_DEVICE_MAIN].data_size);
+            ec_master_queue_datagram(domain->master,
+                    &datagram_pair->datagrams[dev_idx]);
+        }
     }
 }
 
@@ -477,10 +677,18 @@
 
 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) {
+    unsigned int dev_idx;
+    uint16_t wc = 0;
+
+    for (dev_idx = EC_DEVICE_MAIN;
+            dev_idx < ec_master_num_devices(domain->master); dev_idx++) {
+        wc += domain->working_counter[dev_idx];
+    }
+
+    state->working_counter = wc;
+
+    if (wc) {
+        if (wc == domain->expected_working_counter) {
             state->wc_state = EC_WC_COMPLETE;
         } else {
             state->wc_state = EC_WC_INCOMPLETE;
@@ -488,6 +696,8 @@
     } else {
         state->wc_state = EC_WC_ZERO;
     }
+
+    state->redundancy_active = domain->redundancy_active;
 }
 
 /*****************************************************************************/