Skip to content
Snippets Groups Projects
master.c 49.6 KiB
Newer Older
/******************************************************************************
Florian Pose's avatar
Florian Pose committed
 *
Florian Pose's avatar
Florian Pose committed
 *
 *  Copyright (C) 2006  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
 *  as published by the Free Software Foundation; either version 2 of the
 *  License, or (at your option) any later version.
 *
 *  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 right to use EtherCAT Technology is granted and comes free of
 *  charge under condition of compatibility of product made by
 *  Licensee. People intending to distribute/sell products based on the
 *  code, have to sign an agreement to guarantee that products using
 *  software based on IgH EtherCAT master stay compatible with the actual
 *  EtherCAT specification (which are released themselves as an open
 *  standard) as the (only) precondition to have the right to use EtherCAT
 *  Technology, IP and trade marks.
 *
 *****************************************************************************/
/**
   \file
   EtherCAT master methods.
*/

/*****************************************************************************/

#include <linux/module.h>
Florian Pose's avatar
Florian Pose committed
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>

#include "../include/ecrt.h"
#include "globals.h"
#include "master.h"
#include "slave.h"
#include "datagram.h"
#include "ethernet.h"
/*****************************************************************************/
void ec_master_clear(struct kobject *);
void ec_master_destroy_domains(ec_master_t *);
void ec_master_sync_io(ec_master_t *);
static int ec_master_thread(void *);
void ec_master_eoe_run(unsigned long);
void ec_master_check_sdo(unsigned long);
Florian Pose's avatar
Florian Pose committed
int ec_master_measure_bus_time(ec_master_t *);
ssize_t ec_show_master_attribute(struct kobject *, struct attribute *, char *);
ssize_t ec_store_master_attribute(struct kobject *, struct attribute *,
                                  const char *, size_t);

/*****************************************************************************/
EC_SYSFS_READ_ATTR(info);
EC_SYSFS_READ_WRITE_ATTR(eeprom_write_enable);
EC_SYSFS_READ_WRITE_ATTR(debug_level);

static struct attribute *ec_def_attrs[] = {
    &attr_info,
    &attr_eeprom_write_enable,
    &attr_debug_level,
    NULL,
};

static struct sysfs_ops ec_sysfs_ops = {
    .show = &ec_show_master_attribute,
    .store = ec_store_master_attribute
};

static struct kobj_type ktype_ec_master = {
    .release = ec_master_clear,
    .sysfs_ops = &ec_sysfs_ops,
    .default_attrs = ec_def_attrs
};

/*****************************************************************************/

Florian Pose's avatar
Florian Pose committed
/**
   Master constructor.
   \return 0 in case of success, else < 0
int ec_master_init(ec_master_t *master, /**< EtherCAT master */
                   unsigned int index, /**< master index */
                   unsigned int eoeif_count, /**< number of EoE interfaces */
                   dev_t dev_num /**< number for XML cdev's */
    ec_eoe_t *eoe, *next_eoe;
    unsigned int i;
    EC_INFO("Initializing master %i.\n", index);
Florian Pose's avatar
Florian Pose committed
    atomic_set(&master->available, 1);
Florian Pose's avatar
Florian Pose committed

    master->mode = EC_MASTER_MODE_ORPHANED;

    INIT_LIST_HEAD(&master->slaves);
Florian Pose's avatar
Florian Pose committed
    master->slave_count = 0;

    INIT_LIST_HEAD(&master->datagram_queue);
Florian Pose's avatar
Florian Pose committed
    master->datagram_index = 0;

    INIT_LIST_HEAD(&master->domains);
Florian Pose's avatar
Florian Pose committed
    master->debug_level = 0;

    master->stats.timeouts = 0;
    master->stats.corrupted = 0;
    master->stats.skipped = 0;
    master->stats.unmatched = 0;
    master->stats.output_jiffies = 0;

    for (i = 0; i < HZ; i++) {
        master->idle_cycle_times[i] = 0;
        master->eoe_cycle_times[i] = 0;
    }
    master->idle_cycle_time_pos = 0;
    master->eoe_cycle_time_pos = 0;
Florian Pose's avatar
Florian Pose committed

    init_timer(&master->eoe_timer);
    master->eoe_timer.function = ec_master_eoe_run;
    master->eoe_timer.data = (unsigned long) master;
    master->eoe_running = 0;
    master->eoe_checked = 0;
    INIT_LIST_HEAD(&master->eoe_handlers);

    master->internal_lock = SPIN_LOCK_UNLOCKED;
    master->request_cb = NULL;
    master->release_cb = NULL;
    master->cb_data = NULL;

    master->eeprom_write_enable = 0;

    master->sdo_request = NULL;
    master->sdo_seq_user = 0;
    master->sdo_seq_master = 0;
    init_MUTEX(&master->sdo_sem);
    init_timer(&master->sdo_timer);
    master->sdo_timer.function = ec_master_check_sdo;
    master->sdo_timer.data = (unsigned long) master;
    init_completion(&master->sdo_complete);
    // init XML character device
    if (ec_xmldev_init(&master->xmldev, master, dev_num)) {
        EC_ERR("Failed to init XML character device.\n");
        goto out_return;
    for (i = 0; i < eoeif_count; i++) {
        if (!(eoe = (ec_eoe_t *) kmalloc(sizeof(ec_eoe_t), GFP_KERNEL))) {
            EC_ERR("Failed to allocate EoE-Object.\n");
            goto out_clear_eoe;
        }
        if (ec_eoe_init(eoe)) {
            kfree(eoe);
            goto out_clear_eoe;
        }
        list_add_tail(&eoe->list, &master->eoe_handlers);
    }

    // create state machine object
    if (ec_fsm_init(&master->fsm, master)) goto out_clear_eoe;
    // init kobject and add it to the hierarchy
    memset(&master->kobj, 0x00, sizeof(struct kobject));
    kobject_init(&master->kobj);
    master->kobj.ktype = &ktype_ec_master;
    if (kobject_set_name(&master->kobj, "ethercat%i", index)) {
        EC_ERR("Failed to set kobj name.\n");
        kobject_put(&master->kobj);
        return -1;
    if (kobject_add(&master->kobj)) {
        EC_ERR("Failed to add master kobj.\n");
        kobject_put(&master->kobj);
        return -1;
    }
 out_clear_eoe:
    list_for_each_entry_safe(eoe, next_eoe, &master->eoe_handlers, list) {
        list_del(&eoe->list);
        ec_eoe_clear(eoe);
        kfree(eoe);
    }
    ec_xmldev_clear(&master->xmldev);
 out_return:
    return -1;
/*****************************************************************************/
   Clears the kobj-hierarchy bottom up and frees the master.
*/

void ec_master_destroy(ec_master_t *master /**< EtherCAT master */)
{
    ec_master_destroy_slaves(master);
    ec_master_destroy_domains(master);

    // destroy self
    kobject_del(&master->kobj);
    kobject_put(&master->kobj); // free master
}

/*****************************************************************************/

/**
   Clear and free master.
   This method is called by the kobject,
   once there are no more references to it.
void ec_master_clear(struct kobject *kobj /**< kobject of the master */)
    ec_master_t *master = container_of(kobj, ec_master_t, kobj);
Florian Pose's avatar
Florian Pose committed
    ec_datagram_t *datagram, *next_datagram;
Florian Pose's avatar
Florian Pose committed
    // dequeue all datagrams
    list_for_each_entry_safe(datagram, next_datagram,
Florian Pose's avatar
Florian Pose committed
                             &master->datagram_queue, queue) {
        datagram->state = EC_DATAGRAM_ERROR;
        list_del_init(&datagram->queue);
    }
    ec_fsm_clear(&master->fsm);
    ec_xmldev_clear(&master->xmldev);
    // clear EoE objects
    list_for_each_entry_safe(eoe, next_eoe, &master->eoe_handlers, list) {
        list_del(&eoe->list);
        ec_eoe_clear(eoe);
        kfree(eoe);
    }

/*****************************************************************************/
void ec_master_destroy_slaves(ec_master_t *master)
Florian Pose's avatar
Florian Pose committed
    ec_slave_t *slave, *next_slave;
Florian Pose's avatar
Florian Pose committed
    list_for_each_entry_safe(slave, next_slave, &master->slaves, list) {
        list_del(&slave->list);
Florian Pose's avatar
Florian Pose committed
    master->slave_count = 0;
}
Florian Pose's avatar
Florian Pose committed
/*****************************************************************************/
Florian Pose's avatar
Florian Pose committed

/**
   Destroy all domains.
*/

void ec_master_destroy_domains(ec_master_t *master)
{
    ec_domain_t *domain, *next_d;

    list_for_each_entry_safe(domain, next_d, &master->domains, list) {
        list_del(&domain->list);
        ec_domain_destroy(domain);
    }
}

/*****************************************************************************/

Florian Pose's avatar
Florian Pose committed
/**
   Flushes the SDO request queue.
*/
Florian Pose's avatar
Florian Pose committed
void ec_master_flush_sdo_requests(ec_master_t *master)
{
    del_timer_sync(&master->sdo_timer);
    complete(&master->sdo_complete);
    master->sdo_request = NULL;
    master->sdo_seq_user = 0;
    master->sdo_seq_master = 0;
}
Florian Pose's avatar
Florian Pose committed
/*****************************************************************************/
/**
   Internal locking callback.
*/

int ec_master_request_cb(void *master /**< callback data */)
{
    spin_lock(&((ec_master_t *) master)->internal_lock);
    return 0;
}

/*****************************************************************************/

/**
   Internal unlocking callback.
*/

void ec_master_release_cb(void *master /**< callback data */)
{
    spin_unlock(&((ec_master_t *) master)->internal_lock);
}

/*****************************************************************************/

/**
 * Starts the master thread.
*/

int ec_master_thread_start(ec_master_t *master /**< EtherCAT master */)
{
    init_completion(&master->thread_exit);
    
    if (!(master->thread_id =
                kernel_thread(ec_master_thread, master, CLONE_KERNEL)))
        return -1;
    
    return 0;
}

/*****************************************************************************/

/**
 * Stops the master thread.
*/

void ec_master_thread_stop(ec_master_t *master /**< EtherCAT master */)
{
    if (master->thread_id) {
        kill_proc(master->thread_id, SIGTERM, 1);
        wait_for_completion(&master->thread_exit);
        EC_DBG("Master thread exited.\n");
    }    
}

/*****************************************************************************/

Florian Pose's avatar
Florian Pose committed
/**
Florian Pose's avatar
Florian Pose committed
 * Transition function from ORPHANED to IDLE mode.
Florian Pose's avatar
Florian Pose committed
*/
Florian Pose's avatar
Florian Pose committed
int ec_master_enter_idle_mode(ec_master_t *master /**< EtherCAT master */)
{
    master->request_cb = ec_master_request_cb;
    master->release_cb = ec_master_release_cb;
    master->cb_data = master;
	
Florian Pose's avatar
Florian Pose committed
    master->mode = EC_MASTER_MODE_IDLE;
    if (ec_master_thread_start(master)) {
        master->mode = EC_MASTER_MODE_ORPHANED;
        return -1;
    }

Florian Pose's avatar
Florian Pose committed
    return 0;
}

/*****************************************************************************/

/**
Florian Pose's avatar
Florian Pose committed
 * Transition function from IDLE to ORPHANED mode.
Florian Pose's avatar
Florian Pose committed
void ec_master_leave_idle_mode(ec_master_t *master /**< EtherCAT master */)
Florian Pose's avatar
Florian Pose committed
    master->mode = EC_MASTER_MODE_ORPHANED;
    
    ec_master_eoe_stop(master);
    ec_master_thread_stop(master);
Florian Pose's avatar
Florian Pose committed
    ec_master_flush_sdo_requests(master);
}

/*****************************************************************************/

Florian Pose's avatar
Florian Pose committed
 * Transition function from IDLE to OPERATION mode.
Florian Pose's avatar
Florian Pose committed
int ec_master_enter_operation_mode(ec_master_t *master /**< EtherCAT master */)
Florian Pose's avatar
Florian Pose committed
    ec_slave_t *slave;
    ec_datagram_t *datagram = &master->fsm.datagram;

    ec_master_eoe_stop(master); // stop EoE timer
    master->eoe_checked = 1; // prevent from starting again by FSM
    ec_master_thread_stop(master);
Florian Pose's avatar
Florian Pose committed
    master->mode = EC_MASTER_MODE_OPERATION;

    // wait for FSM datagram
Florian Pose's avatar
Florian Pose committed
    if (datagram->state == EC_DATAGRAM_SENT) {
Florian Pose's avatar
Florian Pose committed
        while (get_cycles() - datagram->cycles_sent
               < (cycles_t) EC_IO_TIMEOUT /* us */ * (cpu_khz / 1000)) {}
        ecrt_master_receive(master);
    }

    // finish running master FSM
    if (ec_fsm_running(&master->fsm)) {
        while (ec_fsm_exec(&master->fsm)) {
            ec_master_sync_io(master);
        }
Florian Pose's avatar
Florian Pose committed
    }

    if (master->debug_level) {
        if (ec_master_measure_bus_time(master)) {
            EC_ERR("Bus time measuring failed!\n");
            goto out_idle;
        }
    }

    // set initial slave states
    list_for_each_entry(slave, &master->slaves, list) {
        if (ec_slave_is_coupler(slave)) {
            ec_slave_request_state(slave, EC_SLAVE_STATE_OP);
        }
        else {
            ec_slave_request_state(slave, EC_SLAVE_STATE_PREOP);
        }
    }

    master->eoe_checked = 0; // allow starting EoE again
Florian Pose's avatar
Florian Pose committed
    return 0;

 out_idle:
    master->mode = EC_MASTER_MODE_IDLE;
    if (ec_master_thread_start(master)) {
        EC_WARN("Failed to start master thread again!\n");
    }
Florian Pose's avatar
Florian Pose committed
    return -1;
}

/*****************************************************************************/

/**
Florian Pose's avatar
Florian Pose committed
 * Transition function from OPERATION to IDLE mode.
Florian Pose's avatar
Florian Pose committed
*/

void ec_master_leave_operation_mode(ec_master_t *master
                                    /**< EtherCAT master */)
{
    ec_slave_t *slave;
    ec_fsm_t *fsm = &master->fsm;
    ec_datagram_t *datagram = &master->fsm.datagram;

    ec_master_eoe_stop(master); // stop EoE timer
    master->eoe_checked = 1; // prevent from starting again by FSM
    // wait for FSM datagram
    if (datagram->state == EC_DATAGRAM_SENT) {
        // active waiting
               < (cycles_t) EC_IO_TIMEOUT /* us */ * (cpu_khz / 1000));
    // finish running master FSM
    if (ec_fsm_running(fsm)) {
        while (ec_fsm_exec(fsm)) {
            ec_master_sync_io(master);
        }
    }

    // set states for all slaves
    list_for_each_entry(slave, &master->slaves, list) {
        ec_slave_request_state(slave, EC_SLAVE_STATE_PREOP);

        fsm->slave_state = ec_fsm_slaveconf_state_start;
        while (fsm->slave_state != ec_fsm_slave_state_end
               && fsm->slave_state != ec_fsm_slave_state_error);
    master->request_cb = ec_master_request_cb;
    master->release_cb = ec_master_release_cb;
    master->cb_data = master;
    master->eoe_checked = 0; // allow EoE again

Florian Pose's avatar
Florian Pose committed
    master->mode = EC_MASTER_MODE_IDLE;
    if (ec_master_thread_start(master)) {
        EC_WARN("Failed to start master thread!\n");
    }
}

/*****************************************************************************/

   Places a datagram in the datagram queue.
void ec_master_queue_datagram(ec_master_t *master, /**< EtherCAT master */
                              ec_datagram_t *datagram /**< datagram */
                              )
    ec_datagram_t *queued_datagram;
    // check, if the datagram is already queued
    list_for_each_entry(queued_datagram, &master->datagram_queue, queue) {
        if (queued_datagram == datagram) {
            master->stats.skipped++;
            ec_master_output_stats(master);
            datagram->state = EC_DATAGRAM_QUEUED;
    list_add_tail(&datagram->queue, &master->datagram_queue);
    datagram->state = EC_DATAGRAM_QUEUED;
}

/*****************************************************************************/

/**
   Sends the datagrams in the queue.
   \return 0 in case of success, else < 0
void ec_master_send_datagrams(ec_master_t *master /**< EtherCAT master */)
    ec_datagram_t *datagram, *next;
    size_t datagram_size;
    uint8_t *frame_data, *cur_data;
    void *follows_word;
    cycles_t cycles_start, cycles_sent, cycles_end;
    unsigned long jiffies_sent;
    unsigned int frame_count, more_datagrams_waiting;
    struct list_head sent_datagrams;
    cycles_start = get_cycles();
    frame_count = 0;
    INIT_LIST_HEAD(&sent_datagrams);
    if (unlikely(master->debug_level > 1))
        EC_DBG("ec_master_send_datagrams\n");
Florian Pose's avatar
Florian Pose committed
    do {
        // fetch pointer to transmit socket buffer
Florian Pose's avatar
Florian Pose committed
        frame_data = ec_device_tx_data(master->device);
        cur_data = frame_data + EC_FRAME_HEADER_SIZE;
        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;
            // does the current datagram fit in the frame?
            datagram_size = EC_DATAGRAM_HEADER_SIZE + datagram->data_size
                + EC_DATAGRAM_FOOTER_SIZE;
            if (cur_data - frame_data + datagram_size > ETH_DATA_LEN) {
                more_datagrams_waiting = 1;
            list_add_tail(&datagram->sent, &sent_datagrams);
            datagram->index = master->datagram_index++;
            if (unlikely(master->debug_level > 1))
                EC_DBG("adding datagram 0x%02X\n", datagram->index);
            // set "datagram following" flag in previous frame
Florian Pose's avatar
Florian Pose committed
            if (follows_word)
                EC_WRITE_U16(follows_word, EC_READ_U16(follows_word) | 0x8000);

            // EtherCAT datagram header
            EC_WRITE_U8 (cur_data,     datagram->type);
            EC_WRITE_U8 (cur_data + 1, datagram->index);
            EC_WRITE_U32(cur_data + 2, datagram->address.logical);
            EC_WRITE_U16(cur_data + 6, datagram->data_size & 0x7FF);
Florian Pose's avatar
Florian Pose committed
            EC_WRITE_U16(cur_data + 8, 0x0000);
            follows_word = cur_data + 6;
            cur_data += EC_DATAGRAM_HEADER_SIZE;
            // EtherCAT datagram data
            memcpy(cur_data, datagram->data, datagram->data_size);
            cur_data += datagram->data_size;
            // EtherCAT datagram footer
            EC_WRITE_U16(cur_data, 0x0000); // reset working counter
            cur_data += EC_DATAGRAM_FOOTER_SIZE;
        if (list_empty(&sent_datagrams)) {
            if (unlikely(master->debug_level > 1))
Florian Pose's avatar
Florian Pose committed
                EC_DBG("nothing to send.\n");
            break;
        }
Florian Pose's avatar
Florian Pose committed
        // EtherCAT frame header
        EC_WRITE_U16(frame_data, ((cur_data - frame_data
                                   - EC_FRAME_HEADER_SIZE) & 0x7FF) | 0x1000);
        while (cur_data - frame_data < ETH_ZLEN - ETH_HLEN)
Florian Pose's avatar
Florian Pose committed
            EC_WRITE_U8(cur_data++, 0x00);
        if (unlikely(master->debug_level > 1))
Florian Pose's avatar
Florian Pose committed
            EC_DBG("frame size: %i\n", cur_data - frame_data);
Florian Pose's avatar
Florian Pose committed
        ec_device_send(master->device, cur_data - frame_data);
        cycles_sent = get_cycles();
        jiffies_sent = jiffies;

        // set datagram states and sending timestamps
        list_for_each_entry_safe(datagram, next, &sent_datagrams, sent) {
            datagram->state = EC_DATAGRAM_SENT;
            datagram->cycles_sent = cycles_sent;
            datagram->jiffies_sent = jiffies_sent;
            list_del_init(&datagram->sent); // empty list of sent datagrams
        }

Florian Pose's avatar
Florian Pose committed
        frame_count++;
    while (more_datagrams_waiting);
    if (unlikely(master->debug_level > 1)) {
        cycles_end = get_cycles();
        EC_DBG("ec_master_send_datagrams sent %i frames in %ius.\n",
               frame_count,
               (unsigned int) (cycles_end - cycles_start) * 1000 / cpu_khz);
    }
}

/*****************************************************************************/

/**
Florian Pose's avatar
Florian Pose committed
   Processes a received frame.
   This function is called by the network driver for every received frame.
   \return 0 in case of success, else < 0
void ec_master_receive_datagrams(ec_master_t *master, /**< EtherCAT master */
                                 const uint8_t *frame_data, /**< frame data */
                                 size_t size /**< size of the received data */
                                 )
    uint8_t datagram_type, datagram_index;
    unsigned int cmd_follows, matched;
    const uint8_t *cur_data;
    ec_datagram_t *datagram;

    if (unlikely(size < EC_FRAME_HEADER_SIZE)) {
        master->stats.corrupted++;
        ec_master_output_stats(master);
        return;
    }

    cur_data = frame_data;

    // check length of entire frame
    frame_size = EC_READ_U16(cur_data) & 0x07FF;
    cur_data += EC_FRAME_HEADER_SIZE;

    if (unlikely(frame_size > size)) {
        master->stats.corrupted++;
        ec_master_output_stats(master);
        return;
    }

    cmd_follows = 1;
    while (cmd_follows) {
        // process datagram header
        datagram_type  = EC_READ_U8 (cur_data);
        datagram_index = EC_READ_U8 (cur_data + 1);
        data_size      = EC_READ_U16(cur_data + 6) & 0x07FF;
        cmd_follows    = EC_READ_U16(cur_data + 6) & 0x8000;
        cur_data += EC_DATAGRAM_HEADER_SIZE;

        if (unlikely(cur_data - frame_data
                     + data_size + EC_DATAGRAM_FOOTER_SIZE > size)) {
            master->stats.corrupted++;
            ec_master_output_stats(master);
            return;
        }

        // search for matching datagram in the queue
        list_for_each_entry(datagram, &master->datagram_queue, queue) {
            if (datagram->state == EC_DATAGRAM_SENT
                && datagram->type == datagram_type
                && datagram->index == datagram_index
                && datagram->data_size == data_size) {
        // no matching datagram was found
        if (!matched) {
            master->stats.unmatched++;
            ec_master_output_stats(master);
            cur_data += data_size + EC_DATAGRAM_FOOTER_SIZE;
        // copy received data into the datagram memory
        memcpy(datagram->data, cur_data, data_size);
        // set the datagram's working counter
        datagram->working_counter = EC_READ_U16(cur_data);
        cur_data += EC_DATAGRAM_FOOTER_SIZE;
        // dequeue the received datagram
        datagram->state = EC_DATAGRAM_RECEIVED;
        datagram->cycles_received = master->device->cycles_isr;
        datagram->jiffies_received = master->device->jiffies_isr;
        list_del_init(&datagram->queue);
    }
}

/*****************************************************************************/

   Output statistics in cyclic mode.
   This function outputs statistical data on demand, but not more often than
   necessary. The output happens at most once a second.
void ec_master_output_stats(ec_master_t *master /**< EtherCAT master */)
    if (unlikely(jiffies - master->stats.output_jiffies >= HZ)) {
        master->stats.output_jiffies = jiffies;
        if (master->stats.timeouts) {
Florian Pose's avatar
Florian Pose committed
            EC_WARN("%i datagram%s TIMED OUT!\n", master->stats.timeouts,
                    master->stats.timeouts == 1 ? "" : "s");
            master->stats.timeouts = 0;
        }
        if (master->stats.corrupted) {
Florian Pose's avatar
Florian Pose committed
            EC_WARN("%i frame%s CORRUPTED!\n", master->stats.corrupted,
                    master->stats.corrupted == 1 ? "" : "s");
        if (master->stats.skipped) {
Florian Pose's avatar
Florian Pose committed
            EC_WARN("%i datagram%s SKIPPED!\n", master->stats.skipped,
                    master->stats.skipped == 1 ? "" : "s");
            master->stats.skipped = 0;
        }
        if (master->stats.unmatched) {
Florian Pose's avatar
Florian Pose committed
            EC_WARN("%i datagram%s UNMATCHED!\n", master->stats.unmatched,
                    master->stats.unmatched == 1 ? "" : "s");
/*****************************************************************************/

Florian Pose's avatar
Florian Pose committed
/**
   Master kernel thread function.
static int ec_master_thread(void *data)
Florian Pose's avatar
Florian Pose committed
{
    ec_master_t *master = (ec_master_t *) data;
    cycles_t cycles_start, cycles_end;
    daemonize("EtherCAT2");
    allow_signal(SIGTERM);
    while (!signal_pending(current) && master->mode == EC_MASTER_MODE_IDLE) {
        cycles_start = get_cycles();
        // receive
        spin_lock_bh(&master->internal_lock);
        ecrt_master_receive(master);
        spin_unlock_bh(&master->internal_lock);
        // execute master state machine
        ec_fsm_exec(&master->fsm);
        // send
        spin_lock_bh(&master->internal_lock);
        ecrt_master_send(master);
        spin_unlock_bh(&master->internal_lock);
        
        cycles_end = get_cycles();
        master->idle_cycle_times[master->idle_cycle_time_pos]
            = (u32) (cycles_end - cycles_start) * 1000 / cpu_khz;
        master->idle_cycle_time_pos++;
        master->idle_cycle_time_pos %= HZ;
        set_current_state(TASK_INTERRUPTIBLE);
        schedule_timeout(1);
    }
    
    EC_INFO("Master thread exiting.\n");
    master->thread_id = 0;
    complete_and_exit(&master->thread_exit, 0);
Florian Pose's avatar
Florian Pose committed
}

/*****************************************************************************/

   Initializes a sync manager configuration page with EEPROM data.
   The referenced memory (\a data) must be at least EC_SYNC_SIZE bytes.
void ec_sync_config(const ec_sii_sync_t *sync, /**< sync manager */
                    const ec_slave_t *slave, /**< EtherCAT slave */
                    uint8_t *data /**> configuration memory */
    size_t sync_size;

    sync_size = ec_slave_calc_sync_size(slave, sync);

    if (slave->master->debug_level) {
        EC_DBG("Slave %3i, SM %i: Addr 0x%04X, Size %3i, Ctrl 0x%02X, En %i\n",
               slave->ring_position, sync->index, sync->physical_start_address,
               sync_size, sync->control_register, sync->enable);
    EC_WRITE_U16(data,     sync->physical_start_address);
    EC_WRITE_U16(data + 2, sync_size);
    EC_WRITE_U8 (data + 4, sync->control_register);
    EC_WRITE_U8 (data + 5, 0x00); // status byte (read only)
    EC_WRITE_U16(data + 6, sync->enable ? 0x0001 : 0x0000); // enable
}

/*****************************************************************************/

   Initializes an FMMU configuration page.
   The referenced memory (\a data) must be at least EC_FMMU_SIZE bytes.
void ec_fmmu_config(const ec_fmmu_t *fmmu, /**< FMMU */
                    const ec_slave_t *slave, /**< EtherCAT slave */
                    uint8_t *data /**> configuration memory */
    size_t sync_size;

    sync_size = ec_slave_calc_sync_size(slave, fmmu->sync);

Florian Pose's avatar
Florian Pose committed
    if (slave->master->debug_level) {
        EC_DBG("Slave %3i, FMMU %2i:"
               " LogAddr 0x%08X, Size %3i, PhysAddr 0x%04X, Dir %s\n",
               slave->ring_position, fmmu->index, fmmu->logical_start_address,
               sync_size, fmmu->sync->physical_start_address,
               ((fmmu->sync->control_register & 0x04) ? "out" : "in"));
    EC_WRITE_U32(data,      fmmu->logical_start_address);
    EC_WRITE_U16(data + 4,  sync_size); // size of fmmu
    EC_WRITE_U8 (data + 6,  0x00); // logical start bit
    EC_WRITE_U8 (data + 7,  0x07); // logical end bit
    EC_WRITE_U16(data + 8,  fmmu->sync->physical_start_address);
    EC_WRITE_U8 (data + 10, 0x00); // physical start bit
    EC_WRITE_U8 (data + 11, ((fmmu->sync->control_register & 0x04)
                             ? 0x02 : 0x01));
    EC_WRITE_U16(data + 12, 0x0001); // enable
    EC_WRITE_U16(data + 14, 0x0000); // reserved
Florian Pose's avatar
Florian Pose committed
/*****************************************************************************/

/**
   Formats master information for SysFS read access.
   \return number of bytes written
*/

ssize_t ec_master_info(ec_master_t *master, /**< EtherCAT master */
                       char *buffer /**< memory to store data */
                       )
{
    off_t off = 0;
Florian Pose's avatar
Florian Pose committed
    ec_eoe_t *eoe;
    uint32_t cur, sum, min, max, pos, i;
    unsigned int frames_lost;
    off += sprintf(buffer + off, "\nVersion: %s", ec_master_version_str);
    off += sprintf(buffer + off, "\nMode: ");
    switch (master->mode) {
        case EC_MASTER_MODE_ORPHANED:
            off += sprintf(buffer + off, "ORPHANED");
        case EC_MASTER_MODE_IDLE:
            off += sprintf(buffer + off, "IDLE");
        case EC_MASTER_MODE_OPERATION:
            off += sprintf(buffer + off, "OPERATION");
Florian Pose's avatar
Florian Pose committed
    off += sprintf(buffer + off, "\nSlaves: %i\n",
                   master->slave_count);
Florian Pose's avatar
Florian Pose committed
    off += sprintf(buffer + off, "\nDevice:\n");
    off += sprintf(buffer + off, "  Frames sent:     %u\n",
		   master->device->tx_count);
    off += sprintf(buffer + off, "  Frames received: %u\n",
		   master->device->rx_count);
    frames_lost = master->device->tx_count - master->device->rx_count;
    if (frames_lost) frames_lost--;
    off += sprintf(buffer + off, "  Frames lost:     %u\n", frames_lost);
    off += sprintf(buffer + off, "\nTiming (min/avg/max) [us]:\n");

    sum = 0;
    min = 0xFFFFFFFF;
    max = 0;
    pos = master->idle_cycle_time_pos;
    for (i = 0; i < HZ; i++) {
        cur = master->idle_cycle_times[(i + pos) % HZ];
        sum += cur;
        if (cur < min) min = cur;
        if (cur > max) max = cur;
    }
    off += sprintf(buffer + off, "  Idle cycle: %u / %u.%u / %u\n",
                   min, sum / HZ, (sum * 100 / HZ) % 100, max);

    sum = 0;
    min = 0xFFFFFFFF;
    max = 0;
    pos = master->eoe_cycle_time_pos;
    for (i = 0; i < HZ; i++) {
        cur = master->eoe_cycle_times[(i + pos) % HZ];
        sum += cur;
        if (cur < min) min = cur;
        if (cur > max) max = cur;
    }
    off += sprintf(buffer + off, "  EoE cycle: %u / %u.%u / %u\n",
                   min, sum / HZ, (sum * 100 / HZ) % 100, max);
Florian Pose's avatar
Florian Pose committed
    if (!list_empty(&master->eoe_handlers))
Florian Pose's avatar
Florian Pose committed
        off += sprintf(buffer + off, "\nEoE statistics (RX/TX) [bps]:\n");
Florian Pose's avatar
Florian Pose committed
    list_for_each_entry(eoe, &master->eoe_handlers, list) {
        off += sprintf(buffer + off, "  %s: %u / %u (%u KB/s)\n",
                       eoe->dev->name, eoe->rx_rate, eoe->tx_rate,
                       ((eoe->rx_rate + eoe->tx_rate) / 8 + 512) / 1024);
    off += sprintf(buffer + off, "\n");

    return off;
}

/*****************************************************************************/

   Formats attribute data for SysFS read access.
   \return number of bytes to read
ssize_t ec_show_master_attribute(struct kobject *kobj, /**< kobject */
                                 struct attribute *attr, /**< attribute */
                                 char *buffer /**< memory to store data */
                                 )
{
    ec_master_t *master = container_of(kobj, ec_master_t, kobj);