Skip to content
Snippets Groups Projects
master.c 82.6 KiB
Newer Older
/******************************************************************************
Florian Pose's avatar
Florian Pose committed
 *
Florian Pose's avatar
Florian Pose committed
 *
 *  Copyright (C) 2006-2008  Florian Pose, Ingenieurgemeinschaft IgH
 *
 *  This file is part of the IgH EtherCAT Master.
 *
 *  The IgH EtherCAT Master is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License version 2, as
 *  published by the Free Software Foundation.
 *  The IgH EtherCAT Master is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 *  Public License for more details.
 *  You should have received a copy of the GNU General Public License along
 *  with the IgH EtherCAT Master; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  ---
 *
 *  The license mentioned above concerns the source code only. Using the
 *  EtherCAT technology and brand is only permitted in compliance with the
 *  industrial property and similar rights of Beckhoff Automation GmbH.
 *****************************************************************************/
/**
   \file
   EtherCAT master 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 <linux/device.h>
#include <linux/version.h>
#include <linux/hrtimer.h>
#include "slave.h"
#include "datagram.h"
#ifdef EC_EOE
#include "ethernet.h"
#endif
/*****************************************************************************/
/** Set to 1 to enable external datagram injection debugging.
 */
#define DEBUG_INJECT 0

#ifdef EC_HAVE_CYCLES

/** Frame timeout in cycles.
 */
static cycles_t timeout_cycles;

/** Timeout for external datagram injection [cycles].
 */
static cycles_t ext_injection_timeout_cycles;

#else

/** Frame timeout in jiffies.
 */
static unsigned long timeout_jiffies;

/** Timeout for external datagram injection [jiffies].
 */
static unsigned long ext_injection_timeout_jiffies;
#endif

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

void ec_master_clear_slave_configs(ec_master_t *);
void ec_master_clear_domains(ec_master_t *);
static int ec_master_idle_thread(void *);
static int ec_master_operation_thread(void *);
#ifdef EC_EOE
static int ec_master_eoe_thread(void *);
#endif
void ec_master_find_dc_ref_clock(ec_master_t *);

/*****************************************************************************/
/** Static variables initializer.
*/
void ec_master_init_static(void)
{
#ifdef EC_HAVE_CYCLES
    timeout_cycles = (cycles_t) EC_IO_TIMEOUT /* us */ * (cpu_khz / 1000);
    ext_injection_timeout_cycles = (cycles_t) EC_SDO_INJECTION_TIMEOUT /* us */ * (cpu_khz / 1000);
#else
    // one jiffy may always elapse between time measurement
    timeout_jiffies = max(EC_IO_TIMEOUT * HZ / 1000000, 1);
    ext_injection_timeout_jiffies = max(EC_SDO_INJECTION_TIMEOUT * HZ / 1000000, 1);
#endif
}

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

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 */
        const uint8_t *main_mac, /**< MAC address of main device */
        const uint8_t *backup_mac, /**< MAC address of backup device */
        dev_t device_number, /**< Character device number. */
        struct class *class, /**< Device class. */
        unsigned int debug_level /**< Debug level (module parameter). */
    sema_init(&master->master_sem, 1);
    master->main_mac = main_mac;
    master->backup_mac = backup_mac;

    sema_init(&master->device_sem, 1);
    master->phase = EC_ORPHANED;
    master->injection_seq_fsm = 0;
    master->injection_seq_rt = 0;
    master->slaves = NULL;
Florian Pose's avatar
Florian Pose committed
    master->slave_count = 0;
    INIT_LIST_HEAD(&master->configs);
    INIT_LIST_HEAD(&master->domains);
    master->app_time = 0ULL;
    master->app_start_time = 0ULL;
    sema_init(&master->scan_sem, 1);
    init_waitqueue_head(&master->scan_queue);

    sema_init(&master->config_sem, 1);
    init_waitqueue_head(&master->config_queue);
    
    INIT_LIST_HEAD(&master->datagram_queue);
Florian Pose's avatar
Florian Pose committed
    master->datagram_index = 0;

    INIT_LIST_HEAD(&master->ext_datagram_queue);
    sema_init(&master->ext_queue_sem, 1);
    INIT_LIST_HEAD(&master->external_datagram_queue);
    
    // send interval in IDLE phase
    ec_master_set_send_interval(master, 1000000 / HZ);
    master->debug_level = debug_level;
Florian Pose's avatar
Florian Pose committed
    master->stats.timeouts = 0;
    master->stats.corrupted = 0;
    master->stats.unmatched = 0;
    master->stats.output_jiffies = 0;

    master->thread = NULL;

#ifdef EC_EOE
    master->eoe_thread = NULL;
Florian Pose's avatar
Florian Pose committed
    INIT_LIST_HEAD(&master->eoe_handlers);
    sema_init(&master->io_sem, 1);
    master->send_cb = NULL;
    master->receive_cb = NULL;
    master->app_send_cb = NULL;
    master->app_receive_cb = NULL;
Florian Pose's avatar
Florian Pose committed
    INIT_LIST_HEAD(&master->sii_requests);
    init_waitqueue_head(&master->sii_queue);
    INIT_LIST_HEAD(&master->reg_requests);
    init_waitqueue_head(&master->reg_queue);
Florian Pose's avatar
Florian Pose committed

    ret = ec_device_init(&master->main_device, master);
    if (ret < 0)
    ret = ec_device_init(&master->backup_device, master);
    if (ret < 0)
    // init state machine datagram
    ec_datagram_init(&master->fsm_datagram);
    snprintf(master->fsm_datagram.name, EC_DATAGRAM_NAME_SIZE, "master-fsm");
    ret = ec_datagram_prealloc(&master->fsm_datagram, EC_MAX_DATA_SIZE);
    if (ret < 0) {
        ec_datagram_clear(&master->fsm_datagram);
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_ERR(master, "Failed to allocate FSM datagram.\n");
    // create state machine object
    ec_fsm_master_init(&master->fsm, master, &master->fsm_datagram);
    // init reference sync datagram
    ec_datagram_init(&master->ref_sync_datagram);
    snprintf(master->ref_sync_datagram.name, EC_DATAGRAM_NAME_SIZE, "refsync");
    ret = ec_datagram_apwr(&master->ref_sync_datagram, 0, 0x0910, 8);
    if (ret < 0) {
        ec_datagram_clear(&master->ref_sync_datagram);
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_ERR(master, "Failed to allocate reference"
                " synchronisation datagram.\n");
    // init sync datagram
    ec_datagram_init(&master->sync_datagram);
    snprintf(master->sync_datagram.name, EC_DATAGRAM_NAME_SIZE, "sync");
Florian Pose's avatar
Florian Pose committed
    ret = ec_datagram_prealloc(&master->sync_datagram, 4);
    if (ret < 0) {
        ec_datagram_clear(&master->sync_datagram);
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_ERR(master, "Failed to allocate"
                " synchronisation datagram.\n");
        goto out_clear_ref_sync;

    // init sync monitor datagram
    ec_datagram_init(&master->sync_mon_datagram);
    snprintf(master->sync_mon_datagram.name, EC_DATAGRAM_NAME_SIZE, "syncmon");
    ret = ec_datagram_brd(&master->sync_mon_datagram, 0x092c, 4);
    if (ret < 0) {
        ec_datagram_clear(&master->sync_mon_datagram);
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_ERR(master, "Failed to allocate sync"
                " monitoring datagram.\n");
        goto out_clear_sync;
    }

    ec_master_find_dc_ref_clock(master);
    // init character device
    ret = ec_cdev_init(&master->cdev, master, device_number);
    if (ret)
        goto out_clear_sync_mon;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
    master->class_device = device_create(class, NULL,
            MKDEV(MAJOR(device_number), master->index), NULL,
            "EtherCAT%u", master->index);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
    master->class_device = device_create(class, NULL,
            MKDEV(MAJOR(device_number), master->index),
            "EtherCAT%u", master->index);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 15)
    master->class_device = class_device_create(class, NULL,
            MKDEV(MAJOR(device_number), master->index), NULL,
            "EtherCAT%u", master->index);
    master->class_device = class_device_create(class,
            MKDEV(MAJOR(device_number), master->index), NULL,
            "EtherCAT%u", master->index);
    if (IS_ERR(master->class_device)) {
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_ERR(master, "Failed to create class device!\n");
        ret = PTR_ERR(master->class_device);
out_clear_cdev:
    ec_cdev_clear(&master->cdev);
out_clear_sync_mon:
    ec_datagram_clear(&master->sync_mon_datagram);
out_clear_sync:
    ec_datagram_clear(&master->sync_datagram);
out_clear_ref_sync:
    ec_datagram_clear(&master->ref_sync_datagram);
out_clear_fsm:
    ec_fsm_master_clear(&master->fsm);
    ec_datagram_clear(&master->fsm_datagram);
    ec_device_clear(&master->backup_device);
out_clear_main:
    ec_device_clear(&master->main_device);
out_return:
/*****************************************************************************/
Florian Pose's avatar
Florian Pose committed
*/
void ec_master_clear(
        ec_master_t *master /**< EtherCAT master */
        )
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
    device_unregister(master->class_device);
#else
    class_device_unregister(master->class_device);
    ec_cdev_clear(&master->cdev);
#ifdef EC_EOE
    ec_master_clear_eoe_handlers(master);
    ec_master_clear_slave_configs(master);
    ec_master_clear_slaves(master);
    ec_datagram_clear(&master->sync_mon_datagram);
    ec_datagram_clear(&master->sync_datagram);
    ec_datagram_clear(&master->ref_sync_datagram);
    ec_fsm_master_clear(&master->fsm);
    ec_datagram_clear(&master->fsm_datagram);
    ec_device_clear(&master->backup_device);
    ec_device_clear(&master->main_device);
}

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

#ifdef EC_EOE
/** Clear and free all EoE handlers.
 */
void ec_master_clear_eoe_handlers(
        ec_master_t *master /**< EtherCAT master */
        )
{
    ec_eoe_t *eoe, *next;
    list_for_each_entry_safe(eoe, next, &master->eoe_handlers, list) {
        list_del(&eoe->list);
        ec_eoe_clear(eoe);
        kfree(eoe);
    }
/*****************************************************************************/
/** Clear all slave configurations.
void ec_master_clear_slave_configs(ec_master_t *master)
{
    ec_slave_config_t *sc, *next;

    list_for_each_entry_safe(sc, next, &master->configs, list) {
        list_del(&sc->list);
        ec_slave_config_clear(sc);
        kfree(sc);
    }
}

/*****************************************************************************/
void ec_master_clear_slaves(ec_master_t *master)
    ec_slave_t *slave;
    master->dc_ref_clock = NULL;

    // external requests are obsolete, so we wake pending waiters and remove
    // them from the list
Florian Pose's avatar
Florian Pose committed

    while (!list_empty(&master->sii_requests)) {
        ec_sii_write_request_t *request =
            list_entry(master->sii_requests.next,
                    ec_sii_write_request_t, list);
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_WARN(master, "Discarding SII request, slave %u about"
                " to be deleted.\n", request->slave->ring_position);
        request->state = EC_INT_REQUEST_FAILURE;
        wake_up(&master->sii_queue);
    }
Florian Pose's avatar
Florian Pose committed
    while (!list_empty(&master->reg_requests)) {
        ec_reg_request_t *request =
            list_entry(master->reg_requests.next, ec_reg_request_t, list);
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_WARN(master, "Discarding register request, slave %u"
                " about to be deleted.\n", request->slave->ring_position);
        request->state = EC_INT_REQUEST_FAILURE;
        wake_up(&master->reg_queue);
    }
    for (slave = master->slaves;
            slave < master->slaves + master->slave_count;
            slave++) {
        ec_slave_clear(slave);
    if (master->slaves) {
        kfree(master->slaves);
        master->slaves = NULL;
    }

    master->slave_count = 0;
Florian Pose's avatar
Florian Pose committed
}
Florian Pose's avatar
Florian Pose committed
/*****************************************************************************/
Florian Pose's avatar
Florian Pose committed

void ec_master_clear_domains(ec_master_t *master)
    list_for_each_entry_safe(domain, next, &master->domains, list) {
        ec_domain_clear(domain);
        kfree(domain);
    }
}

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

/** Clear the configuration applied by the application.
 */
void ec_master_clear_config(
        ec_master_t *master /**< EtherCAT master. */
        )
{
    down(&master->master_sem);
    ec_master_clear_domains(master);
    ec_master_clear_slave_configs(master);
    up(&master->master_sem);
}

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

/** Internal sending callback.
void ec_master_internal_send_cb(
    ec_master_t *master = (ec_master_t *) cb_data;
    down(&master->io_sem);
    ecrt_master_send_ext(master);
    up(&master->io_sem);
}

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

/** Internal receiving callback.
void ec_master_internal_receive_cb(
    ec_master_t *master = (ec_master_t *) cb_data;
    down(&master->io_sem);
    ecrt_master_receive(master);
    up(&master->io_sem);
}

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

/** Starts the master thread.
 *
 * \retval  0 Success.
 * \retval <0 Error code.
 */
int ec_master_thread_start(
        ec_master_t *master, /**< EtherCAT master */
        int (*thread_func)(void *), /**< thread function to start */
        const char *name /**< Thread name. */
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_INFO(master, "Starting %s thread.\n", name);
    master->thread = kthread_run(thread_func, master, name);
    if (IS_ERR(master->thread)) {
        int err = (int) PTR_ERR(master->thread);
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_ERR(master, "Failed to start master thread (error %i)!\n",
                err);
        master->thread = NULL;
    
    return 0;
}

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

/** Stops the master thread.
void ec_master_thread_stop(
        ec_master_t *master /**< EtherCAT master */
        )
    unsigned long sleep_jiffies;
    
    if (!master->thread) {
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_WARN(master, "%s(): Already finished!\n", __func__);
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_DBG(master, 1, "Stopping master thread.\n");
    kthread_stop(master->thread);
    master->thread = NULL;
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_INFO(master, "Master thread exited.\n");
    if (master->fsm_datagram.state != EC_DATAGRAM_SENT)
        return;
    sleep_jiffies = max(HZ / 100, 1); // 10 ms, at least 1 jiffy
    schedule_timeout(sleep_jiffies);
}

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

/** Transition function from ORPHANED to IDLE phase.
int ec_master_enter_idle_phase(
        ec_master_t *master /**< EtherCAT master */
        )
Florian Pose's avatar
Florian Pose committed
{
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_DBG(master, 1, "ORPHANED -> IDLE.\n");
    master->send_cb = ec_master_internal_send_cb;
    master->receive_cb = ec_master_internal_receive_cb;
    master->phase = EC_IDLE;
    ret = ec_master_thread_start(master, ec_master_idle_thread,
            "EtherCAT-IDLE");
    if (ret)
        master->phase = EC_ORPHANED;
}

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

/** Transition function from IDLE to ORPHANED phase.
void ec_master_leave_idle_phase(ec_master_t *master /**< EtherCAT master */)
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_DBG(master, 1, "IDLE -> ORPHANED.\n");
    master->phase = EC_ORPHANED;
#ifdef EC_EOE
    ec_master_eoe_stop(master);
    ec_master_thread_stop(master);
    ec_master_clear_slaves(master);
}

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

/** Transition function from IDLE to OPERATION phase.
int ec_master_enter_operation_phase(ec_master_t *master /**< EtherCAT master */)
Florian Pose's avatar
Florian Pose committed
    ec_slave_t *slave;
#ifdef EC_EOE
    ec_eoe_t *eoe;
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_DBG(master, 1, "IDLE -> OPERATION.\n");
    down(&master->config_sem);
    if (master->config_busy) {
        up(&master->config_sem);
        // wait for slave configuration to complete
        ret = wait_event_interruptible(master->config_queue,
                    !master->config_busy);
        if (ret) {
Florian Pose's avatar
Florian Pose committed
            EC_MASTER_INFO(master, "Finishing slave configuration"
                    " interrupted by signal.\n");
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_DBG(master, 1, "Waiting for pending slave"
                " configuration returned.\n");
    down(&master->scan_sem);
    master->allow_scan = 0; // 'lock' the slave list
    if (!master->scan_busy) {
        up(&master->scan_sem);
    } else {
        up(&master->scan_sem);
        // wait for slave scan to complete
        ret = wait_event_interruptible(master->scan_queue, !master->scan_busy);
        if (ret) {
Florian Pose's avatar
Florian Pose committed
            EC_MASTER_INFO(master, "Waiting for slave scan"
                    " interrupted by signal.\n");
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_DBG(master, 1, "Waiting for pending"
                " slave scan returned.\n");
    // set states for all slaves
    for (slave = master->slaves;
            slave < master->slaves + master->slave_count;
            slave++) {
        ec_slave_request_state(slave, EC_SLAVE_STATE_PREOP);
Florian Pose's avatar
Florian Pose committed
    }
#ifdef EC_EOE
    // ... but set EoE slaves to OP
    list_for_each_entry(eoe, &master->eoe_handlers, list) {
        if (ec_eoe_is_open(eoe))
            ec_slave_request_state(eoe->slave, EC_SLAVE_STATE_OP);
    }
    master->phase = EC_OPERATION;
    master->app_send_cb = NULL;
    master->app_receive_cb = NULL;
    
out_allow:
    master->allow_scan = 1;
Florian Pose's avatar
Florian Pose committed
}

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

/** Transition function from OPERATION to IDLE phase.
void ec_master_leave_operation_phase(
        ec_master_t *master /**< EtherCAT master */
        )
Florian Pose's avatar
Florian Pose committed
{
    if (master->active) {
        ecrt_master_deactivate(master); // also clears config
    } else {
        ec_master_clear_config(master);
    }
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_DBG(master, 1, "OPERATION -> IDLE.\n");
    master->phase = EC_IDLE;
/*****************************************************************************/

/** Injects external datagrams that fit into the datagram queue.
void ec_master_inject_external_datagrams(
    ec_datagram_t *datagram, *n;
    size_t queue_size = 0;
    list_for_each_entry(datagram, &master->datagram_queue, queue) {
        queue_size += datagram->data_size;
    }

    list_for_each_entry_safe(datagram, n, &master->external_datagram_queue,
            queue) {
        queue_size += datagram->data_size;
        if (queue_size <= master->max_queue_size) {
            list_del_init(&datagram->queue);
#if DEBUG_INJECT
Florian Pose's avatar
Florian Pose committed
            EC_MASTER_DBG(master, 0, "Injecting external datagram %08x"
                    " size=%u, queue_size=%u\n", (unsigned int) datagram,
                    datagram->data_size, queue_size);
#ifdef EC_HAVE_CYCLES
            datagram->jiffies_sent = 0;
            ec_master_queue_datagram(master, datagram);
        }
        else {
            if (datagram->data_size > master->max_queue_size) {
                list_del_init(&datagram->queue);
                datagram->state = EC_DATAGRAM_ERROR;
Florian Pose's avatar
Florian Pose committed
                EC_MASTER_ERR(master, "External datagram %p is too large,"
                        " size=%zu, max_queue_size=%zu\n",
                        datagram, datagram->data_size,
                        master->max_queue_size);
            } else {
#ifdef EC_HAVE_CYCLES
                        > ext_injection_timeout_cycles)
                if (jiffies - datagram->jiffies_sent
                        > ext_injection_timeout_jiffies)
                {
                    unsigned int time_us;

                    list_del_init(&datagram->queue);
                    datagram->state = EC_DATAGRAM_ERROR;
#ifdef EC_HAVE_CYCLES
                    time_us = (unsigned int)
                        ((cycles_now - datagram->cycles_sent) * 1000LL)
                        / cpu_khz;
                    time_us = (unsigned int)
                        ((jiffies - datagram->jiffies_sent) * 1000000 / HZ);
Florian Pose's avatar
Florian Pose committed
                    EC_MASTER_ERR(master, "Timeout %u us: Injecting"
                            " external datagram %p size=%zu,"
                            " max_queue_size=%zu\n", time_us, datagram,
                            datagram->data_size, master->max_queue_size);
                }
#if DEBUG_INJECT
Florian Pose's avatar
Florian Pose committed
                else {
                    EC_MASTER_DBG(master, 0, "Deferred injecting"
                            " of external datagram %p"
                            " size=%u, queue_size=%u\n",
                            datagram, datagram->data_size, queue_size);
/*****************************************************************************/

/** Sets the expected interval between calls to ecrt_master_send
 * and calculates the maximum amount of data to queue.
void ec_master_set_send_interval(
        ec_master_t *master, /**< EtherCAT master */
        unsigned int send_interval /**< Send interval */
        )
{
    master->send_interval = send_interval;
    master->max_queue_size =
        (send_interval * 1000) / EC_BYTE_TRANSMISSION_TIME_NS;
    master->max_queue_size -= master->max_queue_size / 10;
/*****************************************************************************/

/** Places an external datagram in the sdo datagram queue.
void ec_master_queue_external_datagram(
        ec_master_t *master, /**< EtherCAT master */
        ec_datagram_t *datagram /**< datagram */
        )
{
Florian Pose's avatar
Florian Pose committed
    ec_datagram_t *queued_datagram;
Florian Pose's avatar
Florian Pose committed
    // check, if the datagram is already queued
    list_for_each_entry(queued_datagram, &master->external_datagram_queue,
Florian Pose's avatar
Florian Pose committed
        if (queued_datagram == datagram) {
            datagram->state = EC_DATAGRAM_QUEUED;
            return;
        }
    }
#if DEBUG_INJECT
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_DBG(master, 0, "Requesting external datagram %p size=%u\n",
            datagram, datagram->data_size);
Florian Pose's avatar
Florian Pose committed
    list_add_tail(&datagram->queue, &master->external_datagram_queue);
    datagram->state = EC_DATAGRAM_QUEUED;
#ifdef EC_HAVE_CYCLES
Florian Pose's avatar
Florian Pose committed
    datagram->cycles_sent = get_cycles();
Florian Pose's avatar
Florian Pose committed
    datagram->jiffies_sent = jiffies;
Florian Pose's avatar
Florian Pose committed
    master->fsm.idle = 0;
    up(&master->io_sem);
/*****************************************************************************/

/** Places a datagram in the datagram queue.
 */
void ec_master_queue_datagram(
        ec_master_t *master, /**< EtherCAT master */
        ec_datagram_t *datagram /**< datagram */
        )
    switch (datagram->state) {
        case EC_DATAGRAM_QUEUED:
            datagram->skip_count++;
            EC_MASTER_DBG(master, 1, "Skipping already queued datagram %p.\n",
                    datagram);
            break;
            EC_MASTER_DBG(master, 1, "Skipping already sent datagram %p.\n",
                    datagram);
            break;

        default:
            break;

    /* It is possible, that a datagram in the queue is re-initialized with the
     * ec_datagram_<type>() methods and then shall be queued with this method.
     * In that case, the state is already reset to EC_DATAGRAM_INIT. Check if
     * the datagram is queued to avoid duplicate queuing (which results in an
     * infinite loop!). Set the state to EC_DATAGRAM_QUEUED again, probably
     * causing an unmatched datagram. */
    list_for_each_entry(queued_datagram, &master->datagram_queue, queue) {
        if (queued_datagram == datagram) {
            datagram->skip_count++;
            EC_MASTER_DBG(master, 1, "Skipping re-initialized datagram %p.\n",
                    datagram);
            datagram->state = EC_DATAGRAM_QUEUED;
            return;
        }
    }

    list_add_tail(&datagram->queue, &master->datagram_queue);
    datagram->state = EC_DATAGRAM_QUEUED;
}

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

/** Places a datagram in the non-application datagram queue.
 */
void ec_master_queue_datagram_ext(
        ec_master_t *master, /**< EtherCAT master */
        ec_datagram_t *datagram /**< datagram */
        )
{
    down(&master->ext_queue_sem);
    list_add_tail(&datagram->queue, &master->ext_datagram_queue);
    up(&master->ext_queue_sem);
}

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

/** Sends the datagrams in the queue.
 *
 */
void ec_master_send_datagrams(ec_master_t *master /**< EtherCAT master */)
    ec_datagram_t *datagram, *next;
Florian Pose's avatar
Florian Pose committed
    size_t datagram_size;
    uint8_t *frame_data, *cur_data;
    void *follows_word;
#ifdef EC_HAVE_CYCLES
    cycles_t cycles_start, cycles_sent, cycles_end;
    unsigned long jiffies_sent;
    unsigned int frame_count, more_datagrams_waiting;
    struct list_head sent_datagrams;
#ifdef EC_HAVE_CYCLES
    cycles_start = get_cycles();
    frame_count = 0;
    INIT_LIST_HEAD(&sent_datagrams);
Florian Pose's avatar
Florian Pose committed
    EC_MASTER_DBG(master, 2, "ec_master_send_datagrams\n");
Florian Pose's avatar
Florian Pose committed
    do {
        // fetch pointer to transmit socket buffer
        frame_data = ec_device_tx_data(&master->main_device);
Florian Pose's avatar
Florian Pose committed
        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++;
Florian Pose's avatar
Florian Pose committed
            EC_MASTER_DBG(master, 2, "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);
            memcpy(cur_data + 2, datagram->address, EC_ADDR_LEN);
            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)) {
Florian Pose's avatar
Florian Pose committed
            EC_MASTER_DBG(master, 2, "nothing to send.\n");
Florian Pose's avatar
Florian Pose committed
            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);
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_DBG(master, 2, "frame size: %zu\n", cur_data - frame_data);
        ec_device_send(&master->main_device, cur_data - frame_data);
#ifdef EC_HAVE_CYCLES
        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;
#ifdef EC_HAVE_CYCLES
            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);
#ifdef EC_HAVE_CYCLES
    if (unlikely(master->debug_level > 1)) {
        cycles_end = get_cycles();
Florian Pose's avatar
Florian Pose committed
        EC_MASTER_DBG(master, 0, "ec_master_send_datagrams"
                " sent %u frames in %uus.\n", frame_count,
               (unsigned int) (cycles_end - cycles_start) * 1000 / cpu_khz);
}

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

/** 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 */