master/master.c
author Florian Pose <fp@igh-essen.com>
Mon, 06 Mar 2006 16:18:01 +0000
changeset 99 72e375b0b308
parent 98 f564d0929292
child 101 b0c19892145a
permissions -rw-r--r--
Reload-Script hinzugef?gt.
/******************************************************************************
 *
 *  m a s t e r . c
 *
 *  Methoden für einen EtherCAT-Master.
 *
 *  $Id$
 *
 *****************************************************************************/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>

#include "../include/EtherCAT_rt.h"
#include "../include/EtherCAT_si.h"
#include "globals.h"
#include "master.h"
#include "slave.h"
#include "types.h"
#include "device.h"
#include "command.h"

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

/**
   Konstruktor des EtherCAT-Masters.
*/

void ec_master_init(ec_master_t *master /**< EtherCAT-Master */)
{
    master->slaves = NULL;
    master->device = NULL;

    INIT_LIST_HEAD(&master->commands);
    INIT_LIST_HEAD(&master->domains);

    ec_master_reset(master);
}

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

/**
   Destruktor eines EtherCAT-Masters.

   Entfernt alle Kommandos aus der Liste, löscht den Zeiger
   auf das Slave-Array und gibt die Prozessdaten frei.
*/

void ec_master_clear(ec_master_t *master /**< EtherCAT-Master */)
{
    ec_master_reset(master);

    if (master->device) {
        ec_device_clear(master->device);
        kfree(master->device);
    }
}

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

/**
   Setzt den Master zurück in den Ausgangszustand.

   Bei einem "release" sollte immer diese Funktion aufgerufen werden,
   da sonst Slave-Liste, Domains, etc. weiter existieren.
*/

void ec_master_reset(ec_master_t *master
                     /**< Zeiger auf den zurückzusetzenden Master */
                     )
{
    unsigned int i;
    ec_command_t *command, *next_command;
    ec_domain_t *domain, *next_domain;

    // Alle Slaves entfernen
    if (master->slaves) {
        for (i = 0; i < master->slave_count; i++) {
            ec_slave_clear(master->slaves + i);
        }
        kfree(master->slaves);
        master->slaves = NULL;
    }
    master->slave_count = 0;

    // Kommando-Warteschlange leeren
    list_for_each_entry_safe(command, next_command, &master->commands, list) {
        command->state = EC_CMD_ERROR;
        list_del_init(&command->list);
    }

    // Domain-Liste leeren
    list_for_each_entry_safe(domain, next_domain, &master->domains, list) {
        list_del(&domain->list);
        ec_domain_clear(domain);
        kfree(domain);
    }

    master->command_index = 0;
    master->debug_level = 0;
    master->stats.timeouts = 0;
    master->stats.delayed = 0;
    master->stats.corrupted = 0;
    master->stats.unmatched = 0;
    master->stats.t_last = 0;
}

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

/**
   Öffnet das EtherCAT-Geraet des Masters.

   \return 0 wenn alles ok, < 0 wenn kein Gerät registriert wurde oder
           es nicht geoeffnet werden konnte.
*/

int ec_master_open(ec_master_t *master /**< Der EtherCAT-Master */)
{
    if (!master->device) {
        EC_ERR("No device registered!\n");
        return -1;
    }

    if (ec_device_open(master->device)) {
        EC_ERR("Could not open device!\n");
        return -1;
    }

    return 0;
}

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

/**
   Schliesst das EtherCAT-Geraet, auf dem der Master arbeitet.
*/

void ec_master_close(ec_master_t *master /**< EtherCAT-Master */)
{
    if (!master->device) {
        EC_WARN("Warning - Trying to close an unregistered device!\n");
        return;
    }

    if (ec_device_close(master->device))
        EC_WARN("Warning - Could not close device!\n");
}

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

/**
   Stellt ein Kommando in die Warteschlange.
*/

void ec_master_queue_command(ec_master_t *master, /**< EtherCAT-Master */
                             ec_command_t *command /**< Kommando */
                             )
{
    ec_command_t *queued_command;

    // Ist das Kommando schon in der Warteschlange?
    list_for_each_entry(queued_command, &master->commands, list) {
        if (queued_command == command) {
            command->state = EC_CMD_QUEUED;
            if (unlikely(master->debug_level))
                EC_WARN("command already queued.\n");
            return;
        }
    }

    list_add_tail(&command->list, &master->commands);
    command->state = EC_CMD_QUEUED;
}

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

/**
   Sendet die Kommandos in der Warteschlange.

   \return 0 bei Erfolg, sonst < 0
*/

void ec_master_send_commands(ec_master_t *master /**< EtherCAT-Master */)
{
    ec_command_t *command;
    size_t command_size;
    uint8_t *frame_data, *cur_data;
    void *follows_word;
    cycles_t start = 0, end;

    if (unlikely(master->debug_level > 0)) {
        EC_DBG("ec_master_send\n");
        start = get_cycles();
    }

    // Zeiger auf Socket-Buffer holen
    frame_data = ec_device_tx_data(master->device);
    cur_data = frame_data + EC_FRAME_HEADER_SIZE;
    follows_word = NULL;

    // Aktuellen Frame mit Kommandos füllen
    list_for_each_entry(command, &master->commands, list) {
        if (command->state != EC_CMD_QUEUED) continue;

        // Passt das aktuelle Kommando noch in den aktuellen Rahmen?
        command_size = EC_COMMAND_HEADER_SIZE + command->data_size
            + EC_COMMAND_FOOTER_SIZE;
        if (cur_data - frame_data + command_size > EC_MAX_FRAME_SIZE) break;

        command->state = EC_CMD_SENT;
        command->index = master->command_index++;

        if (unlikely(master->debug_level > 0))
            EC_DBG("adding command 0x%02X\n", command->index);

        // Command-Following-Flag im letzten Kommando setzen
        if (follows_word)
            EC_WRITE_U16(follows_word, EC_READ_U16(follows_word) | 0x8000);

        // EtherCAT command header
        EC_WRITE_U8 (cur_data,     command->type);
        EC_WRITE_U8 (cur_data + 1, command->index);
        EC_WRITE_U32(cur_data + 2, command->address.logical);
        EC_WRITE_U16(cur_data + 6, command->data_size & 0x7FF);
        EC_WRITE_U16(cur_data + 8, 0x0000);
        follows_word = cur_data + 6;
        cur_data += EC_COMMAND_HEADER_SIZE;

        // EtherCAT command data
        memcpy(cur_data, command->data, command->data_size);
        cur_data += command->data_size;

        // EtherCAT command footer
        EC_WRITE_U16(cur_data, command->working_counter);
        cur_data += EC_COMMAND_FOOTER_SIZE;
    }

    if (cur_data - frame_data == EC_FRAME_HEADER_SIZE) {
        if (unlikely(master->debug_level > 0)) EC_DBG("nothing to send.\n");
        return;
    }

    // EtherCAT frame header
    EC_WRITE_U16(frame_data, ((cur_data - frame_data
                               - EC_FRAME_HEADER_SIZE) & 0x7FF) | 0x1000);

    // Rahmen auffüllen
    while (cur_data - frame_data < EC_MIN_FRAME_SIZE)
        EC_WRITE_U8(cur_data++, 0x00);

    if (unlikely(master->debug_level > 0))
        EC_DBG("Frame size: %i\n", cur_data - frame_data);

    // Send frame
    ec_device_send(master->device, cur_data - frame_data);

    if (unlikely(master->debug_level > 0)) {
        end = get_cycles();
        EC_DBG("ec_master_send finished in %ius.\n",
               (u32) (end - start) * 1000 / cpu_khz);
    }
}

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

/**
   Wertet einen empfangenen Rahmen aus.

   \return 0 bei Erfolg, sonst < 0
*/

void ec_master_receive(ec_master_t *master, /**< EtherCAT-Master */
                      const uint8_t *frame_data, /**< Empfangene Daten */
                      size_t size /**< Anzahl empfangene Datenbytes */
                      )
{
    size_t frame_size, data_size;
    uint8_t command_type, command_index;
    unsigned int cmd_follows, matched;
    const uint8_t *cur_data;
    ec_command_t *command;

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

    cur_data = frame_data;

    // Länge des gesamten Frames prüfen
    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) {
        // Kommando-Header auswerten
        command_type  = EC_READ_U8 (cur_data);
        command_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_COMMAND_HEADER_SIZE;

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

        // Suche passendes Kommando in der Liste
        matched = 0;
        list_for_each_entry(command, &master->commands, list) {
            if (command->state == EC_CMD_SENT
                && command->type == command_type
                && command->index == command_index
                && command->data_size == data_size) {
                matched = 1;
                break;
            }
        }

        // Kein passendes Kommando in der Liste gefunden
        if (!matched) {
            master->stats.unmatched++;
            ec_master_output_stats(master);
            cur_data += data_size + EC_COMMAND_FOOTER_SIZE;
            continue;
        }

        // Empfangene Daten in Kommando-Datenspeicher kopieren
        memcpy(command->data, cur_data, data_size);
        cur_data += data_size;

        // Working-Counter setzen
        command->working_counter = EC_READ_U16(cur_data);
        cur_data += EC_COMMAND_FOOTER_SIZE;

        // Kommando aus der Liste entfernen
        command->state = EC_CMD_RECEIVED;
        list_del_init(&command->list);
    }
}

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

/**
   Sendet ein einzelnes Kommando und wartet auf den Empfang.

   Wenn der Slave nicht antwortet, wird das Kommando
   nochmals gesendet.

   \return 0 bei Erfolg, sonst < 0
*/

int ec_master_simple_io(ec_master_t *master, /**< EtherCAT-Master */
                        ec_command_t *command /**< Kommando */
                        )
{
    unsigned int response_tries_left;

    response_tries_left = 10;
    do
    {
        ec_master_queue_command(master, command);
        EtherCAT_rt_master_xio(master);

        if (command->state == EC_CMD_RECEIVED) {
            break;
        }
        else if (command->state == EC_CMD_TIMEOUT) {
            EC_ERR("Simple IO TIMED OUT!\n");
            return -1;
        }
        else if (command->state == EC_CMD_ERROR) {
            EC_ERR("Simple IO command error!\n");
            return -1;
        }
    }
    while (unlikely(!command->working_counter && --response_tries_left));

    if (unlikely(!response_tries_left)) {
        EC_ERR("No response in simple IO!\n");
        return -1;
    }

    return 0;
}

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

/**
   Durchsucht den EtherCAT-Bus nach Slaves.

   Erstellt ein Array mit allen Slave-Informationen die für den
   weiteren Betrieb notwendig sind.

   \return 0 bei Erfolg, sonst < 0
*/

int ec_master_bus_scan(ec_master_t *master /**< EtherCAT-Master */)
{
    ec_command_t command;
    ec_slave_t *slave;
    ec_slave_ident_t *ident;
    unsigned int i;
    unsigned char data[2];

    if (master->slaves || master->slave_count) {
        EC_ERR("Slave scan already done!\n");
        return -1;
    }

    // Determine number of slaves on bus
    ec_command_init_brd(&command, 0x0000, 4);
    if (unlikely(ec_master_simple_io(master, &command))) return -1;
    master->slave_count = command.working_counter;
    EC_INFO("Found %i slaves on bus.\n", master->slave_count);

    if (!master->slave_count) return 0;

    if (!(master->slaves = (ec_slave_t *) kmalloc(master->slave_count
                                                  * sizeof(ec_slave_t),
                                                  GFP_KERNEL))) {
        EC_ERR("Could not allocate memory for slaves!\n");
        return -1;
    }

    // Init slaves
    for (i = 0; i < master->slave_count; i++) {
        slave = master->slaves + i;
        ec_slave_init(slave, master);
        slave->ring_position = i;
        slave->station_address = i + 1;
    }

    // For every slave in the list
    for (i = 0; i < master->slave_count; i++)
    {
        slave = master->slaves + i;

        // Write station address
        EC_WRITE_U16(data, slave->station_address);
        ec_command_init_apwr(&command, slave->ring_position,
                             0x0010, sizeof(uint16_t), data);
        if (unlikely(ec_master_simple_io(master, &command))) {
            EC_ERR("Writing station address failed on slave %i!\n", i);
            return -1;
        }

        // Fetch all slave information
        if (ec_slave_fetch(slave)) return -1;

        // Search for identification in "database"
        ident = slave_idents;
        while (ident) {
            if (unlikely(ident->vendor_id == slave->sii_vendor_id
                         && ident->product_code == slave->sii_product_code)) {
                slave->type = ident->type;
                break;
            }
            ident++;
        }

        if (!slave->type)
            EC_WARN("Unknown slave device (vendor 0x%08X, code 0x%08X) at"
                    " position %i.\n", slave->sii_vendor_id,
                    slave->sii_product_code, i);
    }

    return 0;
}

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

/**
   Statistik-Ausgaben während des zyklischen Betriebs.

   Diese Funktion sorgt dafür, dass Statistiken während des zyklischen
   Betriebs bei Bedarf, aber nicht zu oft ausgegeben werden.

   Die Ausgabe erfolgt gesammelt höchstens einmal pro Sekunde.
*/

void ec_master_output_stats(ec_master_t *master /**< EtherCAT-Master */)
{
    cycles_t t_now = get_cycles();

    if (unlikely((u32) (t_now - master->stats.t_last) / cpu_khz > 1000)) {
        if (master->stats.timeouts) {
            EC_WARN("%i commands TIMED OUT!\n", master->stats.timeouts);
            master->stats.timeouts = 0;
        }
        if (master->stats.delayed) {
            EC_WARN("%i frame(s) DELAYED!\n", master->stats.delayed);
            master->stats.delayed = 0;
        }
        if (master->stats.corrupted) {
            EC_WARN("%i frame(s) CORRUPTED!\n", master->stats.corrupted);
            master->stats.corrupted = 0;
        }
        if (master->stats.unmatched) {
            EC_WARN("%i command(s) UNMATCHED!\n", master->stats.unmatched);
            master->stats.unmatched = 0;
        }
        master->stats.t_last = t_now;
    }
}

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

/**
   Wandelt eine ASCII-kodierte Bus-Adresse in einen Slave-Zeiger.

   Gültige Adress-Strings sind Folgende:

   - \a "X" = der X. Slave im Bus,
   - \a "X:Y" = der Y. Slave hinter dem X. Buskoppler,
   - \a "#X" = der Slave mit der SSID X,
   - \a "#X:Y" = der Y. Slave hinter dem Buskoppler mit der SSID X.

   \return Zeiger auf Slave bei Erfolg, sonst NULL
*/

ec_slave_t *ec_master_slave_address(const ec_master_t *master,
                                    /**< EtherCAT-Master */
                                    const char *address
                                    /**< Address-String */
                                    )
{
    unsigned long first, second;
    char *remainder, *remainder2;
    unsigned int i;
    int coupler_idx, slave_idx;
    ec_slave_t *slave;

    if (!address || address[0] == 0) return NULL;

    if (address[0] == '#') {
        EC_ERR("Bus ID \"%s\" - #<SSID> not implemented yet!\n", address);
        return NULL;
    }

    first = simple_strtoul(address, &remainder, 0);
    if (remainder == address) {
        EC_ERR("Bus ID \"%s\" - First number empty!\n", address);
        return NULL;
    }

    if (!remainder[0]) { // absolute position
        if (first < master->slave_count) {
            return master->slaves + first;
        }

        EC_ERR("Bus ID \"%s\" - Absolute position illegal!\n", address);
    }

    else if (remainder[0] == ':') { // field position

        remainder++;
        second = simple_strtoul(remainder, &remainder2, 0);

        if (remainder2 == remainder) {
            EC_ERR("Bus ID \"%s\" - Sencond number empty!\n", address);
            return NULL;
        }

        if (remainder2[0]) {
            EC_ERR("Bus ID \"%s\" - Illegal trailer (2)!\n", address);
            return NULL;
        }

        coupler_idx = -1;
        slave_idx = 0;
        for (i = 0; i < master->slave_count; i++, slave_idx++) {
            slave = master->slaves + i;
            if (!slave->type) continue;

            if (slave->type->bus_coupler) {
                coupler_idx++;
                slave_idx = 0;
            }

            if (coupler_idx == first && slave_idx == second) return slave;
        }
    }

    else
        EC_ERR("Bus ID \"%s\" - Illegal trailer!\n", address);

    // FIXME ???

    return NULL;
}

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

/**
   Initialisiert eine Sync-Manager-Konfigurationsseite.

   Der mit \a data referenzierte Speicher muss mindestens EC_SYNC_SIZE Bytes
   groß sein.
*/

void ec_sync_config(const ec_sync_t *sync, /**< Sync-Manager */
                    uint8_t *data /**> Zeiger auf Konfigurationsspeicher */
                    )
{
    EC_WRITE_U16(data,     sync->physical_start_address);
    EC_WRITE_U16(data + 2, sync->size);
    EC_WRITE_U8 (data + 4, sync->control_byte);
    EC_WRITE_U8 (data + 5, 0x00); // status byte (read only)
    EC_WRITE_U16(data + 6, 0x0001); // enable
}

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

/**
   Initialisiert eine FMMU-Konfigurationsseite.

   Der mit \a data referenzierte Speicher muss mindestens EC_FMMU_SIZE Bytes
   groß sein.
*/

void ec_fmmu_config(const ec_fmmu_t *fmmu, /**< Sync-Manager */
                    uint8_t *data /**> Zeiger auf Konfigurationsspeicher */
                    )
{
    EC_WRITE_U32(data,      fmmu->logical_start_address);
    EC_WRITE_U16(data + 4,  fmmu->sync->size);
    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_byte & 0x04) ? 0x02 : 0x01);
    EC_WRITE_U16(data + 12, 0x0001); // Enable
    EC_WRITE_U16(data + 14, 0x0000); // res.
}

/******************************************************************************
 *
 * Echtzeitschnittstelle
 *
 *****************************************************************************/

/**
   Registriert eine neue Domäne.

   \return Zeiger auf die Domäne bei Erfolg, sonst NULL.
*/

ec_domain_t *EtherCAT_rt_master_register_domain(ec_master_t *master,
                                                /**< Domäne */
                                                ec_domain_mode_t mode,
                                                /**< Modus */
                                                unsigned int timeout_us
                                                /**< Timeout */
                                                )
{
    ec_domain_t *domain;

    if (!(domain = (ec_domain_t *) kmalloc(sizeof(ec_domain_t), GFP_KERNEL))) {
        EC_ERR("Error allocating domain memory!\n");
        return NULL;
    }

    ec_domain_init(domain, master, mode, timeout_us);

    list_add_tail(&domain->list, &master->domains);

    return domain;
}

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

/**
   Konfiguriert alle Slaves und setzt den Operational-Zustand.

   Führt die komplette Konfiguration und Aktivierunge aller registrierten
   Slaves durch. Setzt Sync-Manager und FMMUs, führt die entsprechenden
   Zustandsübergänge durch, bis der Slave betriebsbereit ist.

   \return 0 bei Erfolg, sonst < 0
*/

int EtherCAT_rt_master_activate(ec_master_t *master /**< EtherCAT-Master */)
{
    unsigned int i, j;
    ec_slave_t *slave;
    ec_command_t command;
    const ec_sync_t *sync;
    const ec_slave_type_t *type;
    const ec_fmmu_t *fmmu;
    uint8_t data[256];
    uint32_t domain_offset;
    ec_domain_t *domain;

    // Domains erstellen
    domain_offset = 0;
    list_for_each_entry(domain, &master->domains, list) {
        if (ec_domain_alloc(domain, domain_offset)) {
            EC_ERR("Failed to allocate domain %X!\n", (u32) domain);
            return -1;
        }
        domain_offset += domain->data_size;
    }

    // Slaves aktivieren
    for (i = 0; i < master->slave_count; i++)
    {
        slave = master->slaves + i;

        // Change state to INIT
        if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_INIT)))
            return -1;

        // Check if slave was registered...
        if (!slave->type) {
            EC_WARN("Slave %i has unknown type!\n", i);
            continue;
        }

        type = slave->type;

        // Check and reset CRC fault counters
        ec_slave_check_crc(slave);

        // Resetting FMMUs
        if (slave->base_fmmu_count) {
            memset(data, 0x00, EC_FMMU_SIZE * slave->base_fmmu_count);
            ec_command_init_npwr(&command, slave->station_address, 0x0600,
                                 EC_FMMU_SIZE * slave->base_fmmu_count, data);
            if (unlikely(ec_master_simple_io(master, &command))) {
                EC_ERR("Resetting FMMUs failed on slave %i!\n",
                       slave->ring_position);
                return -1;
            }
        }

        // Resetting Sync Manager channels
        if (slave->base_sync_count) {
            memset(data, 0x00, EC_SYNC_SIZE * slave->base_sync_count);
            ec_command_init_npwr(&command, slave->station_address, 0x0800,
                                 EC_SYNC_SIZE * slave->base_sync_count, data);
            if (unlikely(ec_master_simple_io(master, &command))) {
                EC_ERR("Resetting sync managers failed on slave %i!\n",
                       slave->ring_position);
                return -1;
            }
        }

        // Set Sync Managers
        for (j = 0; type->sync_managers[j] && j < EC_MAX_SYNC; j++)
        {
            sync = type->sync_managers[j];

            ec_sync_config(sync, data);
            ec_command_init_npwr(&command, slave->station_address,
                                 0x0800 + j * EC_SYNC_SIZE, EC_SYNC_SIZE,
                                 data);
            if (unlikely(ec_master_simple_io(master, &command))) {
                EC_ERR("Setting sync manager %i failed on slave %i!\n",
                       j, slave->ring_position);
                return -1;
            }
        }

        // Change state to PREOP
        if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_PREOP)))
            return -1;

        // Slaves that are not registered are only brought into PREOP
        // state -> nice blinking and mailbox comm. possible
        if (!slave->registered && !slave->type->bus_coupler) {
            EC_WARN("Slave %i was not registered!\n", slave->ring_position);
            continue;
        }

        // Set FMMUs
        for (j = 0; j < slave->fmmu_count; j++)
        {
            fmmu = &slave->fmmus[j];

            ec_fmmu_config(fmmu, data);
            ec_command_init_npwr(&command, slave->station_address,
                                 0x0600 + j * EC_FMMU_SIZE, EC_FMMU_SIZE,
                                 data);
            if (unlikely(ec_master_simple_io(master, &command))) {
                EC_ERR("Setting FMMU %i failed on slave %i!\n",
                       j, slave->ring_position);
                return -1;
            }
        }

        // Change state to SAVEOP
        if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_SAVEOP)))
            return -1;

        // Change state to OP
        if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_OP)))
            return -1;
    }

    return 0;
}

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

/**
   Setzt alle Slaves zurück in den Init-Zustand.

   \return 0 bei Erfolg, sonst < 0
*/

int EtherCAT_rt_master_deactivate(ec_master_t *master /**< EtherCAT-Master */)
{
    ec_slave_t *slave;
    unsigned int i;

    for (i = 0; i < master->slave_count; i++)
    {
        slave = master->slaves + i;

        // CRC-Zählerstände ausgeben
        ec_slave_check_crc(slave);

        if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_INIT) != 0))
            return -1;
    }

    return 0;
}

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

/**
   Sendet und empfängt Kommandos.

   \return 0 bei Erfolg, sonst < 0
*/

void EtherCAT_rt_master_xio(ec_master_t *master)
{
    ec_command_t *command, *next;
    unsigned int commands_sent;
    cycles_t t_start, t_end, t_timeout;

    ec_master_output_stats(master);

    if (unlikely(!master->device->link_state)) {
        // Link DOWN, keines der Kommandos kann gesendet werden.
        list_for_each_entry_safe(command, next, &master->commands, list) {
            command->state = EC_CMD_ERROR;
            list_del_init(&command->list);
        }

        // Device-Zustand abfragen
        ec_device_call_isr(master->device);
        return;
    }

    // Rahmen senden
    ec_master_send_commands(master);

    t_start = get_cycles(); // Sendezeit nehmen
    t_timeout = 100 * cpu_khz / 1000; // 100us

    do {
        ec_device_call_isr(master->device);

        t_end = get_cycles(); // Aktuelle Zeit nehmen
        if (t_end - t_start >= t_timeout) break; // Timeout

        commands_sent = 0;
        list_for_each_entry_safe(command, next, &master->commands, list) {
            if (command->state == EC_CMD_RECEIVED)
                list_del_init(&command->list);
            else if (command->state == EC_CMD_SENT)
                commands_sent++;
        }
    } while (commands_sent);

    // Zeit abgelaufen. Alle verbleibenden Kommandos entfernen.
    list_for_each_entry_safe(command, next, &master->commands, list) {
        switch (command->state) {
            case EC_CMD_SENT:
            case EC_CMD_QUEUED:
                command->state = EC_CMD_TIMEOUT;
                master->stats.timeouts++;
                ec_master_output_stats(master);
                break;
            case EC_CMD_RECEIVED:
                master->stats.delayed++;
                ec_master_output_stats(master);
                break;
            default:
                break;
        }
        list_del_init(&command->list);
    }
}

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

/**
   Setzt die Debug-Ebene des Masters.

   Folgende Debug-Level sind definiert:

   - 1: Nur Positionsmarken in bestimmten Funktionen
   - 2: Komplette Frame-Inhalte
*/

void EtherCAT_rt_master_debug(ec_master_t *master,
                              /**< EtherCAT-Master */
                              int level
                              /**< Debug-Level */
                              )
{
    if (level != master->debug_level) {
        master->debug_level = level;
        EC_INFO("Master debug level set to %i.\n", level);
    }
}

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

/**
   Gibt alle Informationen zum Master aus.
*/

void EtherCAT_rt_master_print(const ec_master_t *master
                              /**< EtherCAT-Master */
                              )
{
    unsigned int i;

    EC_INFO("*** Begin master information ***\n");
    for (i = 0; i < master->slave_count; i++)
        ec_slave_print(&master->slaves[i]);
    EC_INFO("*** End master information ***\n");
}

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

EXPORT_SYMBOL(EtherCAT_rt_master_register_domain);
EXPORT_SYMBOL(EtherCAT_rt_master_activate);
EXPORT_SYMBOL(EtherCAT_rt_master_deactivate);
EXPORT_SYMBOL(EtherCAT_rt_master_xio);
EXPORT_SYMBOL(EtherCAT_rt_master_debug);
EXPORT_SYMBOL(EtherCAT_rt_master_print);

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

/* Emacs-Konfiguration
;;; Local Variables: ***
;;; c-basic-offset:4 ***
;;; End: ***
*/