master/slave.c
author Florian Pose <fp@igh-essen.com>
Mon, 28 Jul 2008 08:16:01 +0000
branchstable-1.3
changeset 1756 dbe4a4219aa5
parent 1751 b6cfd85db58e
permissions -rw-r--r--
merge -c1371 trunk: Reduced watchdog executions in e1000.
/******************************************************************************
 *
 *  $Id$
 *
 *  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 slave methods.
*/

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

#include <linux/module.h>
#include <linux/delay.h>

#include "globals.h"
#include "slave.h"
#include "datagram.h"
#include "master.h"

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

extern const ec_code_msg_t al_status_messages[];

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

void ec_slave_clear(struct kobject *);
void ec_slave_sdos_clear(struct kobject *);
ssize_t ec_show_slave_attribute(struct kobject *, struct attribute *, char *);
ssize_t ec_store_slave_attribute(struct kobject *, struct attribute *,
                                 const char *, size_t);
char *ec_slave_sii_string(ec_slave_t *, unsigned int);

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

/** \cond */

EC_SYSFS_READ_ATTR(info);
EC_SYSFS_READ_WRITE_ATTR(state);
EC_SYSFS_READ_WRITE_ATTR(eeprom);
EC_SYSFS_READ_WRITE_ATTR(alias);

static struct attribute *def_attrs[] = {
    &attr_info,
    &attr_state,
    &attr_eeprom,
    &attr_alias,
    NULL,
};

static struct sysfs_ops sysfs_ops = {
    .show = ec_show_slave_attribute,
    .store = ec_store_slave_attribute
};

static struct kobj_type ktype_ec_slave = {
    .release = ec_slave_clear,
    .sysfs_ops = &sysfs_ops,
    .default_attrs = def_attrs
};

static struct kobj_type ktype_ec_slave_sdos = {
    .release = ec_slave_sdos_clear
};

/** \endcond */

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

/**
   Slave constructor.
   \return 0 in case of success, else < 0
*/

int ec_slave_init(ec_slave_t *slave, /**< EtherCAT slave */
                  ec_master_t *master, /**< EtherCAT master */
                  uint16_t ring_position, /**< ring position */
                  uint16_t station_address /**< station address to configure */
                  )
{
    unsigned int i;

    slave->ring_position = ring_position;
    slave->station_address = station_address;

    slave->master = master;

    slave->requested_state = EC_SLAVE_STATE_PREOP;
    slave->current_state = EC_SLAVE_STATE_UNKNOWN;
    slave->self_configured = 0;
    slave->error_flag = 0;
    slave->online_state = EC_SLAVE_ONLINE;
    slave->fmmu_count = 0;
    slave->pdos_registered = 0;

    slave->base_type = 0;
    slave->base_revision = 0;
    slave->base_build = 0;
    slave->base_fmmu_count = 0;

    slave->eeprom_data = NULL;
    slave->eeprom_size = 0;

    slave->sii_alias = 0;
    slave->sii_vendor_id = 0;
    slave->sii_product_code = 0;
    slave->sii_revision_number = 0;
    slave->sii_serial_number = 0;
    slave->sii_rx_mailbox_offset = 0;
    slave->sii_rx_mailbox_size = 0;
    slave->sii_tx_mailbox_offset = 0;
    slave->sii_tx_mailbox_size = 0;
    slave->sii_mailbox_protocols = 0;
    slave->sii_group = NULL;
    slave->sii_image = NULL;
    slave->sii_order = NULL;
    slave->sii_name = NULL;
    slave->sii_current_on_ebus = 0;

    slave->sii_strings = NULL;
    slave->sii_string_count = 0;
    slave->sii_syncs = NULL;
    slave->sii_sync_count = 0;
    INIT_LIST_HEAD(&slave->sii_pdos);
    INIT_LIST_HEAD(&slave->sdo_dictionary);
    INIT_LIST_HEAD(&slave->sdo_confs);

    slave->sdo_dictionary_fetched = 0;
    slave->pdo_mapping_fetched = 0;
    slave->jiffies_preop = 0;

    for (i = 0; i < 4; i++) {
        slave->dl_link[i] = 0;
        slave->dl_loop[i] = 0;
        slave->dl_signal[i] = 0;
        slave->sii_physical_layer[i] = 0xFF;
    }

    // init kobject and add it to the hierarchy
    memset(&slave->kobj, 0x00, sizeof(struct kobject));
    kobject_init(&slave->kobj);
    slave->kobj.ktype = &ktype_ec_slave;
    slave->kobj.parent = &master->kobj;
    if (kobject_set_name(&slave->kobj, "slave%03i", slave->ring_position)) {
        EC_ERR("Failed to set kobject name.\n");
        goto out_slave_put;
    }
    if (kobject_add(&slave->kobj)) {
        EC_ERR("Failed to add slave's kobject.\n");
        goto out_slave_put;
    }

    // init SDO kobject and add it to the hierarchy
    memset(&slave->sdo_kobj, 0x00, sizeof(struct kobject));
    kobject_init(&slave->sdo_kobj);
    slave->sdo_kobj.ktype = &ktype_ec_slave_sdos;
    slave->sdo_kobj.parent = &slave->kobj;
    if (kobject_set_name(&slave->sdo_kobj, "sdos")) {
        EC_ERR("Failed to set kobject name.\n");
        goto out_sdo_put;
    }
    if (kobject_add(&slave->sdo_kobj)) {
        EC_ERR("Failed to add SDOs kobject.\n");
        goto out_sdo_put;
    }

    return 0;

 out_sdo_put:
    kobject_put(&slave->sdo_kobj);
    kobject_del(&slave->kobj);
 out_slave_put:
    kobject_put(&slave->kobj);
    return -1;
}

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

/**
   Slave destructor.
   Clears and frees a slave object.
*/

void ec_slave_destroy(ec_slave_t *slave /**< EtherCAT slave */)
{
    ec_sdo_t *sdo, *next_sdo;

    // free all SDOs
    list_for_each_entry_safe(sdo, next_sdo, &slave->sdo_dictionary, list) {
        list_del(&sdo->list);
        ec_sdo_destroy(sdo);
    }

    // free SDO kobject
    kobject_del(&slave->sdo_kobj);
    kobject_put(&slave->sdo_kobj);

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

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

/**
   Clear and free slave.
   This method is called by the kobject,
   once there are no more references to it.
*/

void ec_slave_clear(struct kobject *kobj /**< kobject of the slave */)
{
    ec_slave_t *slave;
    ec_pdo_t *pdo, *next_pdo;
    ec_sdo_data_t *sdodata, *next_sdodata;
    unsigned int i;

    slave = container_of(kobj, ec_slave_t, kobj);

    // free all strings
    if (slave->sii_strings) {
        for (i = 0; i < slave->sii_string_count; i++)
            kfree(slave->sii_strings[i]);
        kfree(slave->sii_strings);
    }

    // free all sync managers
    if (slave->sii_syncs) {
        for (i = 0; i < slave->sii_sync_count; i++) {
            ec_sync_clear(&slave->sii_syncs[i]);
        }
        kfree(slave->sii_syncs);
    }

    // free all SII PDOs
    list_for_each_entry_safe(pdo, next_pdo, &slave->sii_pdos, list) {
        list_del(&pdo->list);
        ec_pdo_clear(pdo);
        kfree(pdo);
    }

    // free all SDO configurations
    list_for_each_entry_safe(sdodata, next_sdodata, &slave->sdo_confs, list) {
        list_del(&sdodata->list);
        kfree(sdodata->data);
        kfree(sdodata);
    }

    if (slave->eeprom_data) kfree(slave->eeprom_data);

    kfree(slave);
}

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

/**
 * SDO kobject clear method.
 */

void ec_slave_sdos_clear(struct kobject *kobj /**< kobject for SDOs */)
{
}

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

/**
   Reset slave from operation mode.
*/

void ec_slave_reset(ec_slave_t *slave /**< EtherCAT slave */)
{
    ec_sdo_data_t *sdodata, *next_sdodata;
    unsigned int i;

    slave->fmmu_count = 0;
    slave->pdos_registered = 0;

    // free all SDO configurations
    list_for_each_entry_safe(sdodata, next_sdodata, &slave->sdo_confs, list) {
        list_del(&sdodata->list);
        kfree(sdodata->data);
        kfree(sdodata);
    }

    // remove estimated sync manager sizes
    for (i = 0; i < slave->sii_sync_count; i++) {
        slave->sii_syncs[i].est_length = 0;
    }
}

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

/**
 * Sets the application state of a slave.
 */

void ec_slave_set_state(ec_slave_t *slave, /**< EtherCAT slave */
        ec_slave_state_t new_state /**< new application state */
        )
{
    if (new_state != slave->current_state) {
        if (slave->master->debug_level) {
            char old_state[EC_STATE_STRING_SIZE],
                cur_state[EC_STATE_STRING_SIZE];
            ec_state_string(slave->current_state, old_state);
            ec_state_string(new_state, cur_state);
            EC_DBG("Slave %i: %s -> %s.\n",
                   slave->ring_position, old_state, cur_state);
        }
        slave->current_state = new_state;
    }
}

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

/**
 * Sets the online state of a slave.
 */

void ec_slave_set_online_state(ec_slave_t *slave, /**< EtherCAT slave */
        ec_slave_online_state_t new_state /**< new online state */
        )
{
    if (new_state == EC_SLAVE_OFFLINE &&
            slave->online_state == EC_SLAVE_ONLINE) {
        if (slave->pdos_registered)
            slave->master->pdo_slaves_offline++;
        if (slave->master->debug_level)
            EC_DBG("Slave %i: offline.\n", slave->ring_position);
    }
    else if (new_state == EC_SLAVE_ONLINE &&
            slave->online_state == EC_SLAVE_OFFLINE) {
        slave->error_flag = 0; // clear error flag
        if (slave->pdos_registered)
            slave->master->pdo_slaves_offline--;
        if (slave->master->debug_level) {
            char cur_state[EC_STATE_STRING_SIZE];
            ec_state_string(slave->current_state, cur_state);
            EC_DBG("Slave %i: online (%s).\n",
                   slave->ring_position, cur_state);
        }
    }

    slave->online_state = new_state;
}

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

/**
 * Request a slave state and resets the error flag.
 */

void ec_slave_request_state(ec_slave_t *slave, /**< EtherCAT slave */
                            ec_slave_state_t state /**< new state */
                            )
{
    slave->requested_state = state;
    slave->error_flag = 0;
}

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

/**
   Fetches data from a STRING category.
   \todo range checking
   \return 0 in case of success, else < 0
*/

int ec_slave_fetch_sii_strings(
        ec_slave_t *slave, /**< EtherCAT slave */
        const uint8_t *data, /**< category data */
        size_t data_size /**< number of bytes */
        )
{
    int i;
    size_t size;
    off_t offset;

    slave->sii_string_count = data[0];

    if (!slave->sii_string_count)
        return 0;

    if (!(slave->sii_strings =
                kmalloc(sizeof(char *) * slave->sii_string_count,
                    GFP_KERNEL))) {
        EC_ERR("Failed to allocate string array memory.\n");
        goto out_zero;
    }

    offset = 1;
    for (i = 0; i < slave->sii_string_count; i++) {
        size = data[offset];
        // allocate memory for string structure and data at a single blow
        if (!(slave->sii_strings[i] =
                    kmalloc(sizeof(char) * size + 1, GFP_KERNEL))) {
            EC_ERR("Failed to allocate string memory.\n");
            goto out_free;
        }
        memcpy(slave->sii_strings[i], data + offset + 1, size);
        slave->sii_strings[i][size] = 0x00; // append binary zero
        offset += 1 + size;
    }

    return 0;

out_free:
    for (i--; i >= 0; i--) kfree(slave->sii_strings[i]);
    kfree(slave->sii_strings);
    slave->sii_strings = NULL;
out_zero:
    slave->sii_string_count = 0;
    return -1;
}

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

/**
   Fetches data from a GENERAL category.
   \return 0 in case of success, else < 0
*/

int ec_slave_fetch_sii_general(
        ec_slave_t *slave, /**< EtherCAT slave */
        const uint8_t *data, /**< category data */
        size_t data_size /**< size in bytes */
        )
{
    unsigned int i;

    if (data_size != 32) {
        EC_ERR("Wrong size of general category (%u/32) in slave %u.\n",
                data_size, slave->ring_position);
        return -1;
    }

    slave->sii_group = ec_slave_sii_string(slave, data[0]);
    slave->sii_image = ec_slave_sii_string(slave, data[1]);
    slave->sii_order = ec_slave_sii_string(slave, data[2]);
    slave->sii_name = ec_slave_sii_string(slave, data[3]);

    for (i = 0; i < 4; i++)
        slave->sii_physical_layer[i] =
            (data[4] & (0x03 << (i * 2))) >> (i * 2);

    slave->sii_current_on_ebus = EC_READ_S16(data + 0x0C);

    return 0;
}

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

/**
   Fetches data from a SYNC MANAGER category.
   \return 0 in case of success, else < 0
*/

int ec_slave_fetch_sii_syncs(
        ec_slave_t *slave, /**< EtherCAT slave */
        const uint8_t *data, /**< category data */
        size_t data_size /**< number of bytes */
        )
{
    unsigned int i;
    ec_sync_t *sync;
    size_t memsize;

    // one sync manager struct is 4 words long
    if (data_size % 8) {
        EC_ERR("Invalid SII sync manager size %u in slave %u.\n",
                data_size, slave->ring_position);
        return -1;
    }

    slave->sii_sync_count = data_size / 8;

    memsize = sizeof(ec_sync_t) * slave->sii_sync_count;
    if (!(slave->sii_syncs = kmalloc(memsize, GFP_KERNEL))) {
        EC_ERR("Failed to allocate %u bytes for sync managers.\n",
                memsize);
        slave->sii_sync_count = 0;
        return -1;
    }
    
    for (i = 0; i < slave->sii_sync_count; i++, data += 8) {
        sync = &slave->sii_syncs[i];

        ec_sync_init(sync, slave, i);
        sync->physical_start_address = EC_READ_U16(data);
        sync->length = EC_READ_U16(data + 2);
        sync->control_register = EC_READ_U8 (data + 4);
        sync->enable = EC_READ_U8 (data + 6);
    }

    return 0;
}

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

/**
   Fetches data from a [RT]XPDO category.
   \return 0 in case of success, else < 0
*/

int ec_slave_fetch_sii_pdos(
        ec_slave_t *slave, /**< EtherCAT slave */
        const uint8_t *data, /**< category data */
        size_t data_size, /**< number of bytes */
        ec_pdo_type_t pdo_type /**< PDO type */
        )
{
    ec_pdo_t *pdo;
    ec_pdo_entry_t *entry;
    unsigned int entry_count, i;

    while (data_size >= 8) {
        if (!(pdo = kmalloc(sizeof(ec_pdo_t), GFP_KERNEL))) {
            EC_ERR("Failed to allocate PDO memory.\n");
            return -1;
        }

        ec_pdo_init(pdo);
        pdo->type = pdo_type;
        pdo->index = EC_READ_U16(data);
        entry_count = EC_READ_U8(data + 2);
        pdo->sync_index = EC_READ_U8(data + 3);
        pdo->name = ec_slave_sii_string(slave, EC_READ_U8(data + 5));
        list_add_tail(&pdo->list, &slave->sii_pdos);

        data_size -= 8;
        data += 8;

        for (i = 0; i < entry_count; i++) {
            if (!(entry = kmalloc(sizeof(ec_pdo_entry_t), GFP_KERNEL))) {
                EC_ERR("Failed to allocate PDO entry memory.\n");
                return -1;
            }

            entry->index = EC_READ_U16(data);
            entry->subindex = EC_READ_U8(data + 2);
            entry->name = ec_slave_sii_string(slave, EC_READ_U8(data + 3));
            entry->bit_length = EC_READ_U8(data + 5);
            list_add_tail(&entry->list, &pdo->entries);

            data_size -= 8;
            data += 8;
        }

        // if sync manager index is positive, the PDO is mapped by default
        if (pdo->sync_index >= 0) {
            ec_sync_t *sync;
            ec_pdo_t *mapped_pdo;

            if (pdo->sync_index >= slave->sii_sync_count) {
                EC_ERR("Invalid SM index %i for PDO 0x%04X in slave %u.",
                        pdo->sync_index, pdo->index, slave->ring_position);
                return -1;
            }
            sync = &slave->sii_syncs[pdo->sync_index];

            if (!(mapped_pdo = kmalloc(sizeof(ec_pdo_t), GFP_KERNEL))) {
                EC_ERR("Failed to allocate PDO memory.\n");
                return -1;
            }

            if (ec_pdo_copy(mapped_pdo, pdo)) {
                EC_ERR("Failed to copy PDO.\n");
                kfree(mapped_pdo);
                return -1;
            }

            list_add_tail(&mapped_pdo->list, &sync->pdos);
            sync->mapping_source = EC_SYNC_MAPPING_SII;
        }
    }

    return 0;
}

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

/**
   Searches the string list for an index and allocates a new string.
   \return 0 in case of success, else < 0
   \todo documentation
*/

char *ec_slave_sii_string(
        ec_slave_t *slave, /**< EtherCAT slave */
        unsigned int index /**< string index */
        )
{
    if (!index--) 
        return NULL;

    if (index >= slave->sii_string_count) {
        if (slave->master->debug_level)
            EC_WARN("String %i not found in slave %i.\n",
                    index, slave->ring_position);
        return NULL;
    }

    return slave->sii_strings[index];
}

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

/**
 * Prepares an FMMU configuration.
 * Configuration data for the FMMU is saved in the slave structure and is
 * written to the slave in ecrt_master_activate().
 * The FMMU configuration is done in a way, that the complete data range
 * of the corresponding sync manager is covered. Seperate FMMUs are configured
 * for each domain.
 * If the FMMU configuration is already prepared, the function returns with
 * success.
 * \return 0 in case of success, else < 0
 */

int ec_slave_prepare_fmmu(
        ec_slave_t *slave, /**< EtherCAT slave */
        const ec_domain_t *domain, /**< domain */
        const ec_sync_t *sync  /**< sync manager */
        )
{
    unsigned int i;
    ec_fmmu_t *fmmu;

    // FMMU configuration already prepared?
    for (i = 0; i < slave->fmmu_count; i++) {
        fmmu = &slave->fmmus[i];
        if (fmmu->domain == domain && fmmu->sync == sync)
            return 0;
    }

    // reserve new FMMU...

    if (slave->fmmu_count >= slave->base_fmmu_count) {
        EC_ERR("Slave %i FMMU limit reached!\n", slave->ring_position);
        return -1;
    }

    fmmu = &slave->fmmus[slave->fmmu_count];

    ec_fmmu_init(fmmu, slave, slave->fmmu_count++);
    fmmu->domain = domain;
    fmmu->sync = sync;
    fmmu->logical_start_address = 0;

    slave->pdos_registered = 1;
    
    ec_slave_request_state(slave, EC_SLAVE_STATE_OP);

    return 0;
}

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

/**
   Outputs all information about a certain slave.
*/

size_t ec_slave_info(const ec_slave_t *slave, /**< EtherCAT slave */
                     char *buffer /**< Output buffer */
                     )
{
    off_t off = 0;
    ec_sync_t *sync;
    ec_pdo_t *pdo;
    ec_pdo_entry_t *pdo_entry;
    int first, i;
    ec_sdo_data_t *sdodata;
    char str[20];

    off += sprintf(buffer + off, "Ring position: %i\n",
                   slave->ring_position);
    off += sprintf(buffer + off, "State: ");
    off += ec_state_string(slave->current_state, buffer + off);
    off += sprintf(buffer + off, " (");
    off += ec_state_string(slave->requested_state, buffer + off);
    off += sprintf(buffer + off, ")\n");
    off += sprintf(buffer + off, "Flags: %s, %s\n\n",
            slave->online_state == EC_SLAVE_ONLINE ? "online" : "OFFLINE",
            slave->error_flag ? "ERROR" : "ok");

    off += sprintf(buffer + off, "Data link status:\n");
    for (i = 0; i < 4; i++) {
        off += sprintf(buffer + off, "  Port %u: Phy %u (",
                i, slave->sii_physical_layer[i]);
        switch (slave->sii_physical_layer[i]) {
            case 0x00:
                off += sprintf(buffer + off, "EBUS");
                break;
            case 0x01:
                off += sprintf(buffer + off, "100BASE-TX");
                break;
            case 0x02:
                off += sprintf(buffer + off, "100BASE-FX");
                break;
            default:
                off += sprintf(buffer + off, "unknown");
        }
        off += sprintf(buffer + off, "), Link %s, Loop %s, %s\n",
                       slave->dl_link[i] ? "up" : "down",
                       slave->dl_loop[i] ? "closed" : "open",
                       slave->dl_signal[i] ? "Signal detected" : "No signal");
    }
    off += sprintf(buffer + off, "\n");

    if (slave->sii_alias)
        off += sprintf(buffer + off, "Configured station alias:"
                       " 0x%04X (%i)\n\n", slave->sii_alias, slave->sii_alias);

    off += sprintf(buffer + off, "Identity:\n");
    off += sprintf(buffer + off, "  Vendor ID: 0x%08X (%u)\n",
                   slave->sii_vendor_id, slave->sii_vendor_id);
    off += sprintf(buffer + off, "  Product code: 0x%08X (%u)\n",
                   slave->sii_product_code, slave->sii_product_code);
    off += sprintf(buffer + off, "  Revision number: 0x%08X (%u)\n",
                   slave->sii_revision_number, slave->sii_revision_number);
    off += sprintf(buffer + off, "  Serial number: 0x%08X (%u)\n\n",
                   slave->sii_serial_number, slave->sii_serial_number);

    if (slave->sii_mailbox_protocols) {
        off += sprintf(buffer + off, "Mailboxes:\n");
        off += sprintf(buffer + off, "  RX: 0x%04X/%u, TX: 0x%04X/%u\n",
                slave->sii_rx_mailbox_offset, slave->sii_rx_mailbox_size,
                slave->sii_tx_mailbox_offset, slave->sii_tx_mailbox_size);
        off += sprintf(buffer + off, "  Supported protocols: ");

        first = 1;
        if (slave->sii_mailbox_protocols & EC_MBOX_AOE) {
            off += sprintf(buffer + off, "AoE");
            first = 0;
        }
        if (slave->sii_mailbox_protocols & EC_MBOX_EOE) {
            if (!first) off += sprintf(buffer + off, ", ");
            off += sprintf(buffer + off, "EoE");
            first = 0;
        }
        if (slave->sii_mailbox_protocols & EC_MBOX_COE) {
            if (!first) off += sprintf(buffer + off, ", ");
            off += sprintf(buffer + off, "CoE");
            first = 0;
        }
        if (slave->sii_mailbox_protocols & EC_MBOX_FOE) {
            if (!first) off += sprintf(buffer + off, ", ");
            off += sprintf(buffer + off, "FoE");
            first = 0;
        }
        if (slave->sii_mailbox_protocols & EC_MBOX_SOE) {
            if (!first) off += sprintf(buffer + off, ", ");
            off += sprintf(buffer + off, "SoE");
            first = 0;
        }
        if (slave->sii_mailbox_protocols & EC_MBOX_VOE) {
            if (!first) off += sprintf(buffer + off, ", ");
            off += sprintf(buffer + off, "VoE");
        }
        off += sprintf(buffer + off, "\n\n");
    }

    off += sprintf(buffer + off, "Current consumption: %i mA\n\n",
            slave->sii_current_on_ebus);

    if (slave->sii_group || slave->sii_image || slave->sii_order
            || slave->sii_name) {
        off += sprintf(buffer + off, "General:\n");

        if (slave->sii_group)
            off += sprintf(buffer + off, "  Group: %s\n", slave->sii_group);
        if (slave->sii_image)
            off += sprintf(buffer + off, "  Image: %s\n", slave->sii_image);
        if (slave->sii_order)
            off += sprintf(buffer + off, "  Order number: %s\n",
                    slave->sii_order);
        if (slave->sii_name)
            off += sprintf(buffer + off, "  Name: %s\n", slave->sii_name);
        off += sprintf(buffer + off, "\n");
    }

    if (slave->sii_sync_count) {
        off += sprintf(buffer + off, "Sync managers / PDO mapping:\n");

        for (i = 0; i < slave->sii_sync_count; i++) {
            sync = &slave->sii_syncs[i];
            off += sprintf(buffer + off,
                    "  SM%u: addr 0x%04X, size %i, control 0x%02X, %s\n",
                    sync->index, sync->physical_start_address,
                    ec_sync_size(sync), sync->control_register,
                    sync->enable ? "enable" : "disable");

            if (list_empty(&sync->pdos)) {
                off += sprintf(buffer + off, "    No PDOs mapped.\n");
            } else if (sync->mapping_source != EC_SYNC_MAPPING_NONE) {
                off += sprintf(buffer + off,
                        "    PDO mapping information from %s.\n",
                        sync->mapping_source == EC_SYNC_MAPPING_SII
                        ? "SII" : "CoE");
            }

            list_for_each_entry(pdo, &sync->pdos, list) {
                off += sprintf(buffer + off, "    %s 0x%04X \"%s\"\n",
                        pdo->type == EC_RX_PDO ? "RxPdo" : "TxPdo",
                        pdo->index, pdo->name ? pdo->name : "???");

                list_for_each_entry(pdo_entry, &pdo->entries, list) {
                    off += sprintf(buffer + off,
                            "      0x%04X:%X \"%s\", %i bit\n",
                            pdo_entry->index, pdo_entry->subindex,
                            pdo_entry->name ? pdo_entry->name : "???",
                            pdo_entry->bit_length);
                }
            }
        }
        off += sprintf(buffer + off, "\n");
    }

    // type-cast to avoid warnings on some compilers
    if (!list_empty((struct list_head *) &slave->sii_pdos)) {
        off += sprintf(buffer + off, "Available PDOs from SII:\n");

        list_for_each_entry(pdo, &slave->sii_pdos, list) {
            off += sprintf(buffer + off, "  %s 0x%04X \"%s\"",
                    pdo->type == EC_RX_PDO ? "RxPdo" : "TxPdo",
                    pdo->index, pdo->name ? pdo->name : "???");
            if (pdo->sync_index >= 0)
                off += sprintf(buffer + off, ", default mapping: SM%u.\n",
                        pdo->sync_index);
            else
                off += sprintf(buffer + off, ", no default mapping.\n");

            list_for_each_entry(pdo_entry, &pdo->entries, list) {
                off += sprintf(buffer + off, "    0x%04X:%X \"%s\", %i bit\n",
                        pdo_entry->index, pdo_entry->subindex,
                        pdo_entry->name ? pdo_entry->name : "???",
                        pdo_entry->bit_length);
            }
        }
        off += sprintf(buffer + off, "\n");
    }

    // type-cast to avoid warnings on some compilers
    if (!list_empty((struct list_head *) &slave->sdo_confs)) {
        off += sprintf(buffer + off, "SDO configurations:\n");

        list_for_each_entry(sdodata, &slave->sdo_confs, list) {
            switch (sdodata->size) {
                case 1: sprintf(str, "%i", EC_READ_U8(sdodata->data)); break;
                case 2: sprintf(str, "%i", EC_READ_U16(sdodata->data)); break;
                case 4: sprintf(str, "%i", EC_READ_U32(sdodata->data)); break;
                default: sprintf(str, "(invalid size)"); break;
            }
            off += sprintf(buffer + off, "  0x%04X:%-3i -> %s\n",
                    sdodata->index, sdodata->subindex, str);
        }
        off += sprintf(buffer + off, "\n");
    }

    return off;
}

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

/**
 * Schedules an EEPROM write request.
 * \return 0 case of success, otherwise error code.
 */

int ec_slave_schedule_eeprom_writing(
        ec_eeprom_write_request_t *request /**< EEPROM write request */
        )
{
    ec_master_t *master = request->slave->master;

    request->state = EC_REQUEST_QUEUED;

    // schedule EEPROM write request.
    down(&master->eeprom_sem);
    list_add_tail(&request->list, &master->eeprom_requests);
    up(&master->eeprom_sem);

    // wait for processing through FSM
    if (wait_event_interruptible(master->eeprom_queue,
                request->state != EC_REQUEST_QUEUED)) {
        // interrupted by signal
        down(&master->eeprom_sem);
        if (request->state == EC_REQUEST_QUEUED) {
            list_del(&request->list);
            up(&master->eeprom_sem);
            return -EINTR;
        }
        // request already processing: interrupt not possible.
        up(&master->eeprom_sem);
    }

    // wait until master FSM has finished processing
    wait_event(master->eeprom_queue,
            request->state != EC_REQUEST_IN_PROGRESS);

    return request->state == EC_REQUEST_COMPLETE ? 0 : -EIO;
}

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

/**
 * Calculates the EEPROM checksum field.
 *
 * The checksum is generated with the polynom x^8+x^2+x+1 (0x07) and an
 * initial value of 0xff (see IEC 61158-6-12 ch. 5.4).
 *
 * The below code was originally generated with PYCRC
 * http://www.tty1.net/pycrc
 *
 * ./pycrc.py --width=8 --poly=0x07 --reflect-in=0 --xor-in=0xff
 *   --reflect-out=0 --xor-out=0 --generate c --algorithm=bit-by-bit
 *
 * \return CRC8
 */

uint8_t ec_slave_eeprom_crc(
        const uint8_t *data, /**< pointer to data */
        size_t length /**< number of bytes in \a data */
        )
{
    unsigned int i;
    uint8_t bit, byte, crc = 0x48;

    while (length--) {
        byte = *data++;
        for (i = 0; i < 8; i++) {
            bit = crc & 0x80;
            crc = (crc << 1) | ((byte >> (7 - i)) & 0x01);
            if (bit) crc ^= 0x07;
        }
    }

    for (i = 0; i < 8; i++) {
        bit = crc & 0x80;
        crc <<= 1;
        if (bit) crc ^= 0x07;
    }

    return crc;
}

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

/**
 * Writes complete EEPROM contents to a slave.
 * \return data size written in case of success, otherwise error code.
 */

ssize_t ec_slave_write_eeprom(ec_slave_t *slave, /**< EtherCAT slave */
        const uint8_t *data, /**< new EEPROM data */
        size_t size /**< size of data in bytes */
        )
{
    ec_eeprom_write_request_t request;
    const uint16_t *cat_header;
    uint16_t cat_type, cat_size;
    int ret;
    uint8_t crc;

    if (slave->master->mode != EC_MASTER_MODE_IDLE) { // FIXME
        EC_ERR("Writing EEPROMs only allowed in idle mode!\n");
        return -EBUSY;
    }

    if (size % 2) {
        EC_ERR("EEPROM data size is odd! Dropping.\n");
        return -EINVAL;
    }

    // init EEPROM write request
    INIT_LIST_HEAD(&request.list);
    request.slave = slave;
    request.data = data;
    request.word_offset = 0;
    request.word_size = size / 2;

    if (request.word_size < 0x0041) {
        EC_ERR("EEPROM data too short! Dropping.\n");
        return -EINVAL;
    }

    // calculate checksum
    crc = ec_slave_eeprom_crc(data, 14); // CRC over words 0 to 6
    if (crc != data[14]) {
        EC_WARN("EEPROM CRC incorrect. Must be 0x%02x.\n", crc);
    }

    cat_header = (const uint16_t *) request.data
		+ EC_FIRST_EEPROM_CATEGORY_OFFSET;
    cat_type = EC_READ_U16(cat_header);
    while (cat_type != 0xFFFF) { // cycle through categories
        if (cat_header + 1 >
				(const uint16_t *) request.data + request.word_size) {
            EC_ERR("EEPROM data corrupted! Dropping.\n");
            return -EINVAL;
        }
        cat_size = EC_READ_U16(cat_header + 1);
        if (cat_header + cat_size + 2 >
				(const uint16_t *) request.data + request.word_size) {
            EC_ERR("EEPROM data corrupted! Dropping.\n");
            return -EINVAL;
        }
        cat_header += cat_size + 2;
        cat_type = EC_READ_U16(cat_header);
    }

    // EEPROM data ok. schedule writing.
    if ((ret = ec_slave_schedule_eeprom_writing(&request)))
        return ret; // error code

    return size; // success
}

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

/**
 * Writes the Secondary slave address (alias) to the slave's EEPROM.
 * \return data size written in case of success, otherwise error code.
 */

ssize_t ec_slave_write_alias(ec_slave_t *slave, /**< EtherCAT slave */
        const uint8_t *data, /**< alias string */
        size_t size /**< size of data in bytes */
        )
{
    ec_eeprom_write_request_t request;
    char *remainder;
    uint16_t alias;
    int ret;
    uint8_t eeprom_data[16], crc;

    if (slave->master->mode != EC_MASTER_MODE_IDLE) { // FIXME
        EC_ERR("Writing to EEPROM is only allowed in idle mode!\n");
        return -EBUSY;
    }

    alias = simple_strtoul(data, &remainder, 0);
    if (remainder == (char *) data || (*remainder && *remainder != '\n')) {
        EC_ERR("Invalid alias value! Dropping.\n");
        return -EINVAL;
    }

    if (!slave->eeprom_data || slave->eeprom_size < 16) {
        EC_ERR("Failed to read EEPROM contents from slave %u.\n",
                slave->ring_position);
        return -EINVAL;
    }

    // copy first 7 words of recent EEPROM contents
    memcpy(eeprom_data, slave->eeprom_data, 14);
    
    // write new alias address in word 4
    EC_WRITE_U16(eeprom_data + 8, alias);

    // calculate new checksum over words 0 to 6
    crc = ec_slave_eeprom_crc(eeprom_data, 14);
    EC_WRITE_U16(eeprom_data + 14, crc);

    // init EEPROM write request
    INIT_LIST_HEAD(&request.list);
    request.slave = slave;
    request.data = eeprom_data;
    request.word_offset = 0x0000;
    request.word_size = 8;

    if ((ret = ec_slave_schedule_eeprom_writing(&request)))
        return ret; // error code

    slave->sii_alias = alias; // FIXME: do this in state machine

    return size; // success
}


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

/**
   Formats attribute data for SysFS read access.
   \return number of bytes to read
*/

ssize_t ec_show_slave_attribute(struct kobject *kobj, /**< slave's kobject */
                                struct attribute *attr, /**< attribute */
                                char *buffer /**< memory to store data */
                                )
{
    ec_slave_t *slave = container_of(kobj, ec_slave_t, kobj);

    if (attr == &attr_info) {
        return ec_slave_info(slave, buffer);
    }
    else if (attr == &attr_state) {
        switch (slave->current_state) {
            case EC_SLAVE_STATE_INIT:
                return sprintf(buffer, "INIT\n");
            case EC_SLAVE_STATE_PREOP:
                return sprintf(buffer, "PREOP\n");
            case EC_SLAVE_STATE_SAVEOP:
                return sprintf(buffer, "SAVEOP\n");
            case EC_SLAVE_STATE_OP:
                return sprintf(buffer, "OP\n");
            default:
                return sprintf(buffer, "UNKNOWN\n");
        }
    }
    else if (attr == &attr_eeprom) {
        if (slave->eeprom_data) {
            if (slave->eeprom_size > PAGE_SIZE) {
                EC_ERR("EEPROM contents of slave %i exceed 1 page (%i/%i).\n",
                       slave->ring_position, slave->eeprom_size,
                       (int) PAGE_SIZE);
            }
            else {
                memcpy(buffer, slave->eeprom_data, slave->eeprom_size);
                return slave->eeprom_size;
            }
        }
    }
    else if (attr == &attr_alias) {
        return sprintf(buffer, "%u\n", slave->sii_alias);
    }

    return 0;
}

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

/**
   Formats attribute data for SysFS write access.
   \return number of bytes processed, or negative error code
*/

ssize_t ec_store_slave_attribute(struct kobject *kobj, /**< slave's kobject */
                                 struct attribute *attr, /**< attribute */
                                 const char *buffer, /**< memory with data */
                                 size_t size /**< size of data to store */
                                 )
{
    ec_slave_t *slave = container_of(kobj, ec_slave_t, kobj);

    if (attr == &attr_state) {
        char state[EC_STATE_STRING_SIZE];
        if (!strcmp(buffer, "INIT\n"))
            ec_slave_request_state(slave, EC_SLAVE_STATE_INIT);
        else if (!strcmp(buffer, "PREOP\n"))
            ec_slave_request_state(slave, EC_SLAVE_STATE_PREOP);
        else if (!strcmp(buffer, "SAVEOP\n"))
            ec_slave_request_state(slave, EC_SLAVE_STATE_SAVEOP);
        else if (!strcmp(buffer, "OP\n"))
            ec_slave_request_state(slave, EC_SLAVE_STATE_OP);
        else {
            EC_ERR("Invalid slave state \"%s\"!\n", buffer);
            return -EINVAL;
        }

        ec_state_string(slave->requested_state, state);
        EC_INFO("Accepted new state %s for slave %i.\n",
                state, slave->ring_position);
        return size;
    }
    else if (attr == &attr_eeprom) {
        return ec_slave_write_eeprom(slave, buffer, size);
    }
    else if (attr == &attr_alias) {
        return ec_slave_write_alias(slave, buffer, size);
    }

    return -EIO;
}

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

/**
 * Get the sync manager for either Rx- or Tx-PDOs.
 * \return pointer to sync manager, or NULL.
 */

ec_sync_t *ec_slave_get_pdo_sync(
        ec_slave_t *slave, /**< EtherCAT slave */
        ec_direction_t dir /**< input or output */
        )
{
    unsigned int sync_index;

    if (dir != EC_DIR_INPUT && dir != EC_DIR_OUTPUT) {
        EC_ERR("Invalid direction!\n");
        return NULL;
    }

    sync_index = (unsigned int) dir;
    if (slave->sii_mailbox_protocols) sync_index += 2;

    if (sync_index >= slave->sii_sync_count)
        return NULL;

    return &slave->sii_syncs[sync_index];
}

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

/**
   \return 0 in case of success, else < 0
*/

int ec_slave_conf_sdo(ec_slave_t *slave, /**< EtherCAT slave */
                      uint16_t sdo_index, /**< SDO index */
                      uint8_t sdo_subindex, /**< SDO subindex */
                      const uint8_t *data, /**< SDO data */
                      size_t size /**< SDO size in bytes */
                      )
{
    ec_sdo_data_t *sdodata;

    if (!(slave->sii_mailbox_protocols & EC_MBOX_COE)) {
        EC_ERR("Slave %i does not support CoE!\n", slave->ring_position);
        return -1;
    }

    if (!(sdodata = (ec_sdo_data_t *)
          kmalloc(sizeof(ec_sdo_data_t), GFP_KERNEL))) {
        EC_ERR("Failed to allocate memory for SDO configuration object!\n");
        return -1;
    }

    if (!(sdodata->data = (uint8_t *) kmalloc(size, GFP_KERNEL))) {
        EC_ERR("Failed to allocate memory for SDO configuration data!\n");
        kfree(sdodata);
        return -1;
    }

    sdodata->index = sdo_index;
    sdodata->subindex = sdo_subindex;
    memcpy(sdodata->data, data, size);
    sdodata->size = size;

    list_add_tail(&sdodata->list, &slave->sdo_confs);
    return 0;
}

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

/**
   \return 0 in case of success, else < 0
*/

int ec_slave_validate(const ec_slave_t *slave, /**< EtherCAT slave */
                      uint32_t vendor_id, /**< vendor ID */
                      uint32_t product_code /**< product code */
                      )
{
    if (vendor_id != slave->sii_vendor_id ||
        product_code != slave->sii_product_code) {
        EC_ERR("Invalid slave type at position %i:\n", slave->ring_position);
        EC_ERR("  Requested: 0x%08X 0x%08X\n", vendor_id, product_code);
        EC_ERR("      Found: 0x%08X 0x%08X\n",
                slave->sii_vendor_id, slave->sii_product_code);
        return -1;
    }
    return 0;
}

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

/**
   Counts the total number of SDOs and entries in the dictionary.
*/

void ec_slave_sdo_dict_info(const ec_slave_t *slave, /**< EtherCAT slave */
                            unsigned int *sdo_count, /**< number of SDOs */
                            unsigned int *entry_count /**< total number of
                                                         entries */
                            )
{
    unsigned int sdos = 0, entries = 0;
    ec_sdo_t *sdo;
    ec_sdo_entry_t *entry;

    list_for_each_entry(sdo, &slave->sdo_dictionary, list) {
        sdos++;
        list_for_each_entry(entry, &sdo->entries, list) {
            entries++;
        }
    }

    *sdo_count = sdos;
    *entry_count = entries;
}

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

/**
 * Get an SDO from the dictionary.
 * \returns The desired SDO, of NULL.
 */

ec_sdo_t *ec_slave_get_sdo(
        ec_slave_t *slave /**< EtherCAT slave */,
        uint16_t index /**< SDO index */
        )
{
    ec_sdo_t *sdo;

    list_for_each_entry(sdo, &slave->sdo_dictionary, list) {
        if (sdo->index != index) continue;
        return sdo;
    }

    return NULL;
}

/******************************************************************************
 *  Realtime interface
 *****************************************************************************/

/**
   \return 0 in case of success, else < 0
   \ingroup RealtimeInterface
*/

int ecrt_slave_conf_sdo8(ec_slave_t *slave, /**< EtherCAT slave */
                         uint16_t sdo_index, /**< SDO index */
                         uint8_t sdo_subindex, /**< SDO subindex */
                         uint8_t value /**< new SDO value */
                         )
{
    uint8_t data[1];
    EC_WRITE_U8(data, value);
    return ec_slave_conf_sdo(slave, sdo_index, sdo_subindex, data, 1);
}

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

/**
   \return 0 in case of success, else < 0
   \ingroup RealtimeInterface
*/

int ecrt_slave_conf_sdo16(ec_slave_t *slave, /**< EtherCAT slave */
                          uint16_t sdo_index, /**< SDO index */
                          uint8_t sdo_subindex, /**< SDO subindex */
                          uint16_t value /**< new SDO value */
                          )
{
    uint8_t data[2];
    EC_WRITE_U16(data, value);
    return ec_slave_conf_sdo(slave, sdo_index, sdo_subindex, data, 2);
}

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

/**
   \return 0 in case of success, else < 0
   \ingroup RealtimeInterface
*/

int ecrt_slave_conf_sdo32(ec_slave_t *slave, /**< EtherCAT slave */
                          uint16_t sdo_index, /**< SDO index */
                          uint8_t sdo_subindex, /**< SDO subindex */
                          uint32_t value /**< new SDO value */
                          )
{
    uint8_t data[4];
    EC_WRITE_U32(data, value);
    return ec_slave_conf_sdo(slave, sdo_index, sdo_subindex, data, 4);
}

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

/**
 * Clear slave's PDO mapping.
 */

void ecrt_slave_pdo_mapping_clear(
        ec_slave_t *slave, /**< EtherCAT slave */
        ec_direction_t dir /**< output/input */
        )
{
    ec_sync_t *sync;

    if (!(slave->sii_mailbox_protocols & EC_MBOX_COE)) {
        EC_ERR("Slave %i does not support CoE!\n", slave->ring_position);
        return;
    }

    if (!(sync = ec_slave_get_pdo_sync(slave, dir)))
        return;

    ec_sync_clear_pdos(sync);
    sync->alt_mapping = 1;
}

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

/**
 * Add a PDO to the list of known mapped PDOs.
 */

int ecrt_slave_pdo_mapping_add(
        ec_slave_t *slave, /**< EtherCAT slave */
        ec_direction_t dir, /**< input/output */
        uint16_t pdo_index /**< Index of mapped PDO */)
{
    ec_pdo_t *pdo;
    ec_sync_t *sync;
    unsigned int not_found = 1;

    if (!(slave->sii_mailbox_protocols & EC_MBOX_COE)) {
        EC_ERR("Slave %u does not support CoE!\n", slave->ring_position);
        return -1;
    }

    // does the slave provide the PDO?
    list_for_each_entry(pdo, &slave->sii_pdos, list) {
        if (pdo->index == pdo_index) {
            not_found = 0;
            break;
        }
    }

    if (not_found) {
        EC_ERR("Slave %u does not provide PDO 0x%04X!\n",
                slave->ring_position, pdo_index);
        return -1;
    }

    // check direction
    if ((pdo->type == EC_TX_PDO && dir == EC_DIR_OUTPUT) ||
            (pdo->type == EC_RX_PDO && dir == EC_DIR_INPUT)) {
        EC_ERR("Invalid direction for PDO 0x%04X.\n", pdo_index);
        return -1;
    }


    if (!(sync = ec_slave_get_pdo_sync(slave, dir))) {
        EC_ERR("Failed to obtain sync manager for PDO mapping of slave %u!\n",
                slave->ring_position);
        return -1;
    }

    if (ec_sync_add_pdo(sync, pdo))
        return -1;

    sync->alt_mapping = 1;
    return 0;
}

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

/**
 * Convenience function for ecrt_slave_pdo_mapping_clear() and
 * ecrt_slave_pdo_mapping_add().
 */

int ecrt_slave_pdo_mapping(ec_slave_t *slave, /**< EtherCAT slave */
        ec_direction_t dir, /**< input/output */
        unsigned int num_args, /**< Number of following arguments */
        ... /**< PDO indices to map */
        )
{
    va_list ap;

    ecrt_slave_pdo_mapping_clear(slave, dir);

    va_start(ap, num_args);

    for (; num_args; num_args--) {
        if (ecrt_slave_pdo_mapping_add(
                    slave, dir, (uint16_t) va_arg(ap, int))) {
            return -1;
        }
    }

    va_end(ap);
    return 0;
}


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

/** \cond */

EXPORT_SYMBOL(ecrt_slave_conf_sdo8);
EXPORT_SYMBOL(ecrt_slave_conf_sdo16);
EXPORT_SYMBOL(ecrt_slave_conf_sdo32);
EXPORT_SYMBOL(ecrt_slave_pdo_mapping_clear);
EXPORT_SYMBOL(ecrt_slave_pdo_mapping_add);
EXPORT_SYMBOL(ecrt_slave_pdo_mapping);

/** \endcond */

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