Skip to content
Snippets Groups Projects
slave.c 42.9 KiB
Newer Older
/******************************************************************************
Florian Pose's avatar
Florian Pose committed
 *
Florian Pose's avatar
Florian Pose committed
 *
 *  Copyright (C) 2006  Florian Pose, Ingenieurgemeinschaft IgH
 *
 *  This file is part of the IgH EtherCAT Master.
 *
 *  The IgH EtherCAT Master is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2 of the
 *  License, or (at your option) any later version.
 *
 *  The IgH EtherCAT Master is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with the IgH EtherCAT Master; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  The right to use EtherCAT Technology is granted and comes free of
 *  charge under condition of compatibility of product made by
 *  Licensee. People intending to distribute/sell products based on the
 *  code, have to sign an agreement to guarantee that products using
 *  software based on IgH EtherCAT master stay compatible with the actual
 *  EtherCAT specification (which are released themselves as an open
 *  standard) as the (only) precondition to have the right to use EtherCAT
 *  Technology, IP and trade marks.
 *
 *****************************************************************************/
/**
   \file
   EtherCAT slave methods.
*/

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

#include <linux/module.h>
#include "globals.h"
#include "slave.h"
#include "command.h"
#include "master.h"
/*****************************************************************************/
extern const ec_code_msg_t al_status_messages[];

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

int ec_slave_fetch_categories(ec_slave_t *);
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);

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

EC_SYSFS_READ_ATTR(ring_position);
EC_SYSFS_READ_ATTR(vendor_name);
EC_SYSFS_READ_ATTR(product_name);
EC_SYSFS_READ_ATTR(product_desc);
EC_SYSFS_READ_ATTR(type);
EC_SYSFS_READ_WRITE_ATTR(state);

static struct attribute *def_attrs[] = {
    &attr_ring_position,
    &attr_vendor_name,
    &attr_product_name,
    &attr_product_desc,
    &attr_type,
    &attr_state,
    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
};
/*****************************************************************************/

Florian Pose's avatar
Florian Pose committed
/**
   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 */
Florian Pose's avatar
Florian Pose committed
    unsigned int i;

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

    // 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");
        kobject_put(&slave->kobj);
        return -1;
    }

    slave->coupler_index = 0;
    slave->coupler_subindex = 0xFFFF;
    slave->base_type = 0;
    slave->base_revision = 0;
    slave->base_build = 0;
    slave->base_fmmu_count = 0;
    slave->base_sync_count = 0;
Florian Pose's avatar
Florian Pose committed
    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->type = NULL;
    slave->registered = 0;
    slave->fmmu_count = 0;
    slave->eeprom_group = NULL;
    slave->eeprom_image = NULL;
    slave->eeprom_order = NULL;
    slave->eeprom_name = NULL;
    slave->requested_state = EC_SLAVE_STATE_UNKNOWN;
    slave->current_state = EC_SLAVE_STATE_UNKNOWN;
    slave->state_error = 0;

    ec_command_init(&slave->mbox_command);

    INIT_LIST_HEAD(&slave->eeprom_strings);
    INIT_LIST_HEAD(&slave->eeprom_syncs);
    INIT_LIST_HEAD(&slave->eeprom_pdos);
    INIT_LIST_HEAD(&slave->sdo_dictionary);
    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;
/*****************************************************************************/
void ec_slave_clear(struct kobject *kobj /**< kobject of the slave */)
    ec_slave_t *slave;
    ec_eeprom_string_t *string, *next_str;
    ec_eeprom_sync_t *sync, *next_sync;
    ec_eeprom_pdo_t *pdo, *next_pdo;
    ec_eeprom_pdo_entry_t *entry, *next_ent;
    ec_sdo_t *sdo, *next_sdo;
    ec_sdo_entry_t *en, *next_en;
    slave = container_of(kobj, ec_slave_t, kobj);

    list_for_each_entry_safe(string, next_str, &slave->eeprom_strings, list) {
        list_del(&string->list);
        kfree(string);
    }
    list_for_each_entry_safe(sync, next_sync, &slave->eeprom_syncs, list) {
        list_del(&sync->list);
        kfree(sync);
    }

    list_for_each_entry_safe(pdo, next_pdo, &slave->eeprom_pdos, list) {
        list_del(&pdo->list);
        if (pdo->name) kfree(pdo->name);

        list_for_each_entry_safe(entry, next_ent, &pdo->entries, list) {
            list_del(&entry->list);
            if (entry->name) kfree(entry->name);
            kfree(entry);
        }

        kfree(pdo);
    }

    if (slave->eeprom_group) kfree(slave->eeprom_group);
    if (slave->eeprom_image) kfree(slave->eeprom_image);
    if (slave->eeprom_order) kfree(slave->eeprom_order);
    if (slave->eeprom_name) kfree(slave->eeprom_name);
    list_for_each_entry_safe(sdo, next_sdo, &slave->sdo_dictionary, list) {
        list_del(&sdo->list);
        if (sdo->name) kfree(sdo->name);
Florian Pose's avatar
Florian Pose committed
        list_for_each_entry_safe(en, next_en, &sdo->entries, list) {
            list_del(&en->list);
            kfree(en);
        }

    ec_command_clear(&slave->mbox_command);
}

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

/**
   Reads all necessary information from a slave.
   \return 0 in case of success, else < 0
int ec_slave_fetch(ec_slave_t *slave /**< EtherCAT slave */)
Florian Pose's avatar
Florian Pose committed
    unsigned int i;
    uint16_t dl_status;
    command = &slave->master->simple_command;

    if (ec_command_nprd(command, slave->station_address, 0x0000, 6)) return -1;
    if (unlikely(ec_master_simple_io(slave->master, command))) {
Florian Pose's avatar
Florian Pose committed
        EC_ERR("Reading base data from slave %i failed!\n",
               slave->ring_position);
    slave->base_type =       EC_READ_U8 (command->data);
    slave->base_revision =   EC_READ_U8 (command->data + 1);
    slave->base_build =      EC_READ_U16(command->data + 2);
    slave->base_fmmu_count = EC_READ_U8 (command->data + 4);
    slave->base_sync_count = EC_READ_U8 (command->data + 5);

    if (slave->base_fmmu_count > EC_MAX_FMMUS)
        slave->base_fmmu_count = EC_MAX_FMMUS;

    if (ec_command_nprd(command, slave->station_address, 0x0110, 2)) return -1;
    if (unlikely(ec_master_simple_io(slave->master, command))) {
Florian Pose's avatar
Florian Pose committed
        EC_ERR("Reading DL status from slave %i failed!\n",
               slave->ring_position);
        return -1;
    }

    dl_status = EC_READ_U16(command->data);
    for (i = 0; i < 4; i++) {
        slave->dl_link[i] = dl_status & (1 << (4 + i)) ? 1 : 0;
        slave->dl_loop[i] = dl_status & (1 << (8 + i * 2)) ? 1 : 0;
        slave->dl_signal[i] = dl_status & (1 << (9 + i * 2)) ? 1 : 0;
    if (ec_slave_sii_read16(slave, 0x0004, &slave->sii_alias))
Florian Pose's avatar
Florian Pose committed
        return -1;
    if (ec_slave_sii_read32(slave, 0x0008, &slave->sii_vendor_id))
    if (ec_slave_sii_read32(slave, 0x000A, &slave->sii_product_code))
    if (ec_slave_sii_read32(slave, 0x000C, &slave->sii_revision_number))
    if (ec_slave_sii_read32(slave, 0x000E, &slave->sii_serial_number))
    if (ec_slave_sii_read16(slave, 0x0018, &slave->sii_rx_mailbox_offset))
        return -1;
    if (ec_slave_sii_read16(slave, 0x0019, &slave->sii_rx_mailbox_size))
        return -1;
    if (ec_slave_sii_read16(slave, 0x001A, &slave->sii_tx_mailbox_offset))
        return -1;
    if (ec_slave_sii_read16(slave, 0x001B, &slave->sii_tx_mailbox_size))
        return -1;
    if (ec_slave_sii_read16(slave, 0x001C, &slave->sii_mailbox_protocols))
    if (unlikely(ec_slave_fetch_categories(slave))) {
        EC_ERR("Failed to fetch category data!\n");
    return 0;
}

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

/**
   Reads 16 bit from the slave information interface (SII).
   \return 0 in case of success, else < 0
int ec_slave_sii_read16(ec_slave_t *slave,
                        /**< address of the SII register to read */
    command = &slave->master->simple_command;
    if (ec_command_npwr(command, slave->station_address, 0x502, 6)) return -1;
    EC_WRITE_U8 (command->data,     0x00); // read-only access
    EC_WRITE_U8 (command->data + 1, 0x01); // request read operation
    EC_WRITE_U32(command->data + 2, offset);
    if (unlikely(ec_master_simple_io(slave->master, command))) {
        EC_ERR("SII-read failed on slave %i!\n", slave->ring_position);
        return -1;
    }

    start = get_cycles();
    timeout = (cycles_t) 100 * cpu_khz; // 100ms

    while (1)
    {
        udelay(10);

        if (ec_command_nprd(command, slave->station_address, 0x502, 10))
            return -1;
        if (unlikely(ec_master_simple_io(slave->master, command))) {
            EC_ERR("Getting SII-read status failed on slave %i!\n",
                   slave->ring_position);
            return -1;
        }

        end = get_cycles();

        if (likely((EC_READ_U8(command->data + 1) & 0x81) == 0)) {
            *target = EC_READ_U16(command->data + 6);
            return 0;
        }

        if (unlikely((end - start) >= timeout)) {
            EC_ERR("SII-read. Slave %i timed out!\n", slave->ring_position);
            return -1;
        }
    }
}

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

/**
   Reads 32 bit from the slave information interface (SII).
   \return 0 in case of success, else < 0
*/

int ec_slave_sii_read32(ec_slave_t *slave,
                        /**< address of the SII register to read */
    cycles_t start, end, timeout;
    command = &slave->master->simple_command;
    if (ec_command_npwr(command, slave->station_address, 0x502, 6)) return -1;
    EC_WRITE_U8 (command->data,     0x00); // read-only access
    EC_WRITE_U8 (command->data + 1, 0x01); // request read operation
    EC_WRITE_U32(command->data + 2, offset);
    if (unlikely(ec_master_simple_io(slave->master, command))) {
        EC_ERR("SII-read failed on slave %i!\n", slave->ring_position);
    timeout = (cycles_t) 100 * cpu_khz; // 100ms
Florian Pose's avatar
Florian Pose committed
    while (1)
Florian Pose's avatar
Florian Pose committed
        udelay(10);

        if (ec_command_nprd(command, slave->station_address, 0x502, 10))
            return -1;
        if (unlikely(ec_master_simple_io(slave->master, command))) {
            EC_ERR("Getting SII-read status failed on slave %i!\n",
                   slave->ring_position);
        if (likely((EC_READ_U8(command->data + 1) & 0x81) == 0)) {
            *target = EC_READ_U32(command->data + 6);
Florian Pose's avatar
Florian Pose committed
            return 0;
Florian Pose's avatar
Florian Pose committed
        if (unlikely((end - start) >= timeout)) {
            EC_ERR("SII-read. Slave %i timed out!\n", slave->ring_position);
Florian Pose's avatar
Florian Pose committed
            return -1;
        }
    }
}

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

Florian Pose's avatar
Florian Pose committed
/**
   Writes 16 bit of data to the slave information interface (SII).
   \return 0 in case of success, else < 0
int ec_slave_sii_write16(ec_slave_t *slave,
                         /**< address of the SII register to write */
Florian Pose's avatar
Florian Pose committed
    cycles_t start, end, timeout;

    command = &slave->master->simple_command;

Florian Pose's avatar
Florian Pose committed
    EC_INFO("SII-write (slave %i, offset 0x%04X, value 0x%04X)\n",
            slave->ring_position, offset, value);

    // initiate write operation
    if (ec_command_npwr(command, slave->station_address, 0x502, 8)) return -1;
    EC_WRITE_U8 (command->data,     0x01); // enable write access
    EC_WRITE_U8 (command->data + 1, 0x02); // request write operation
    EC_WRITE_U32(command->data + 2, offset);
    EC_WRITE_U16(command->data + 6, value);
    if (unlikely(ec_master_simple_io(slave->master, command))) {
Florian Pose's avatar
Florian Pose committed
        EC_ERR("SII-write failed on slave %i!\n", slave->ring_position);
        return -1;
    }

    start = get_cycles();
    timeout = (cycles_t) 100 * cpu_khz; // 100ms
Florian Pose's avatar
Florian Pose committed

    while (1)
    {
        udelay(10);

        if (ec_command_nprd(command, slave->station_address, 0x502, 2))
            return -1;
        if (unlikely(ec_master_simple_io(slave->master, command))) {
Florian Pose's avatar
Florian Pose committed
            EC_ERR("Getting SII-write status failed on slave %i!\n",
                   slave->ring_position);
            return -1;
        }

        end = get_cycles();

        if (likely((EC_READ_U8(command->data + 1) & 0x82) == 0)) {
            if (EC_READ_U8(command->data + 1) & 0x40) {
Florian Pose's avatar
Florian Pose committed
                EC_ERR("SII-write failed!\n");
                return -1;
            }
            else {
                EC_INFO("SII-write succeeded!\n");
                return 0;
            }
        }

        if (unlikely((end - start) >= timeout)) {
            EC_ERR("SII-write: Slave %i timed out!\n", slave->ring_position);
Florian Pose's avatar
Florian Pose committed
            return -1;
        }
    }
}

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

   Fetches data from slave's EEPROM.
   \return 0 in case of success, else < 0
   \todo memory allocation
int ec_slave_fetch_categories(ec_slave_t *slave /**< EtherCAT slave */)
    uint16_t word_offset, cat_type, word_count;
    uint32_t value;
    uint8_t *cat_data;
    unsigned int i;

    word_offset = 0x0040;

    if (!(cat_data = (uint8_t *) kmalloc(0x10000, GFP_KERNEL))) {
        EC_ERR("Failed to allocate 64k bytes for category data.\n");
        return -1;
    }

    while (1) {
        // read category type
        if (ec_slave_sii_read32(slave, word_offset, &value)) {
            EC_ERR("Unable to read category header.\n");
Florian Pose's avatar
Florian Pose committed
            goto out_free;
        if ((value & 0xFFFF) == 0xFFFF) break;

        cat_type = value & 0x7FFF;
        word_count = (value >> 16) & 0xFFFF;

        for (i = 0; i < word_count; i++) {
            if (ec_slave_sii_read32(slave, word_offset + 2 + i, &value)) {
                EC_ERR("Unable to read category data word %i.\n", i);
Florian Pose's avatar
Florian Pose committed
                goto out_free;
            }

            cat_data[i * 2]     = (value >> 0) & 0xFF;
            cat_data[i * 2 + 1] = (value >> 8) & 0xFF;

            // read second word "on the fly"
            if (i + 1 < word_count) {
                i++;
                cat_data[i * 2]     = (value >> 16) & 0xFF;
                cat_data[i * 2 + 1] = (value >> 24) & 0xFF;
            }
        }

        switch (cat_type)
        {
            case 0x000A:
Florian Pose's avatar
Florian Pose committed
                if (ec_slave_fetch_strings(slave, cat_data))
                    goto out_free;
                break;
            case 0x001E:
Florian Pose's avatar
Florian Pose committed
                if (ec_slave_fetch_general(slave, cat_data))
                    goto out_free;
                break;
            case 0x0028:
                break;
            case 0x0029:
                if (ec_slave_fetch_sync(slave, cat_data, word_count))
                    goto out_free;
                break;
            case 0x0032:
                if (ec_slave_fetch_pdo(slave, cat_data, word_count, EC_TX_PDO))
                    goto out_free;
                break;
            case 0x0033:
                if (ec_slave_fetch_pdo(slave, cat_data, word_count, EC_RX_PDO))
                    goto out_free;
                break;
            default:
                EC_WARN("Unknown category type 0x%04X in slave %i.\n",
                        cat_type, slave->ring_position);
        }

        word_offset += 2 + word_count;
    }

    kfree(cat_data);
    return 0;
Florian Pose's avatar
Florian Pose committed

 out_free:
    kfree(cat_data);
    return -1;
}

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

/**
   Fetches data from a STRING category.
   \return 0 in case of success, else < 0
int ec_slave_fetch_strings(ec_slave_t *slave, /**< EtherCAT slave */
                           const uint8_t *data /**< category data */
                           )
{
    unsigned int string_count, i;
    size_t size;
    off_t offset;
    ec_eeprom_string_t *string;

    string_count = data[0];
    offset = 1;
    for (i = 0; i < string_count; i++) {
        size = data[offset];
        // allocate memory for string structure and data at a single blow
        if (!(string = (ec_eeprom_string_t *)
              kmalloc(sizeof(ec_eeprom_string_t) + size + 1, GFP_KERNEL))) {
            EC_ERR("Failed to allocate string memory.\n");
            return -1;
        }
        string->size = size;
        // string memory appended to string structure
        string->data = (char *) string + sizeof(ec_eeprom_string_t);
        memcpy(string->data, data + offset + 1, size);
        string->data[size] = 0x00;
        list_add_tail(&string->list, &slave->eeprom_strings);
        offset += 1 + size;
    }

    return 0;
}

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

/**
   Fetches data from a GENERAL category.
   \return 0 in case of success, else < 0
int ec_slave_fetch_general(ec_slave_t *slave, /**< EtherCAT slave */
                           const uint8_t *data /**< category data */
Florian Pose's avatar
Florian Pose committed
                           )
Florian Pose's avatar
Florian Pose committed
    if (ec_slave_locate_string(slave, data[0], &slave->eeprom_group))
        return -1;
    if (ec_slave_locate_string(slave, data[1], &slave->eeprom_image))
        return -1;
    if (ec_slave_locate_string(slave, data[2], &slave->eeprom_order))
Florian Pose's avatar
Florian Pose committed
        return -1;
    if (ec_slave_locate_string(slave, data[3], &slave->eeprom_name))
Florian Pose's avatar
Florian Pose committed
        return -1;

    for (i = 0; i < 4; i++)
        slave->sii_physical_layer[i] =
            (data[4] & (0x03 << (i * 2))) >> (i * 2);
Florian Pose's avatar
Florian Pose committed
    return 0;
}

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

/**
   Fetches data from a SYNC MANAGER category.
   \return 0 in case of success, else < 0
int ec_slave_fetch_sync(ec_slave_t *slave, /**< EtherCAT slave */
                        const uint8_t *data, /**< category data */
                        size_t word_count /**< number of words */
    unsigned int sync_count, i;
    ec_eeprom_sync_t *sync;
    sync_count = word_count / 4; // sync manager struct is 4 words long
    for (i = 0; i < sync_count; i++, data += 8) {
        if (!(sync = (ec_eeprom_sync_t *)
              kmalloc(sizeof(ec_eeprom_sync_t), GFP_KERNEL))) {
            EC_ERR("Failed to allocate Sync-Manager memory.\n");
            return -1;
        }
        sync->index = i;
        sync->physical_start_address = *((uint16_t *) (data + 0));
        sync->length                 = *((uint16_t *) (data + 2));
        sync->control_register       = data[4];
        sync->enable                 = data[6];

        list_add_tail(&sync->list, &slave->eeprom_syncs);
    }

    return 0;
}

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

/**
   Fetches data from a [RT]XPDO category.
   \return 0 in case of success, else < 0
int ec_slave_fetch_pdo(ec_slave_t *slave, /**< EtherCAT slave */
                       const uint8_t *data, /**< category data */
                       size_t word_count, /**< number of words */
                       ec_pdo_type_t pdo_type /**< PDO type */
    ec_eeprom_pdo_t *pdo;
    ec_eeprom_pdo_entry_t *entry;
    unsigned int entry_count, i;

    while (word_count >= 4) {
        if (!(pdo = (ec_eeprom_pdo_t *)
              kmalloc(sizeof(ec_eeprom_pdo_t), GFP_KERNEL))) {
            EC_ERR("Failed to allocate PDO memory.\n");
            return -1;
        }
        INIT_LIST_HEAD(&pdo->entries);
        pdo->type = pdo_type;
        pdo->index = *((uint16_t *) data);
        entry_count = data[2];
        pdo->sync_manager = data[3];
        pdo->name = NULL;
        ec_slave_locate_string(slave, data[5], &pdo->name);
        list_add_tail(&pdo->list, &slave->eeprom_pdos);

        word_count -= 4;
        data += 8;

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

            entry->index = *((uint16_t *) data);
            entry->subindex = data[2];
            entry->name = NULL;
            ec_slave_locate_string(slave, data[3], &entry->name);
            entry->bit_length = data[5];

            list_add_tail(&entry->list, &pdo->entries);

            word_count -= 4;
            data += 8;
        }
    }

    return 0;
}

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

Florian Pose's avatar
Florian Pose committed
/**
   Searches the string list for an index and allocates a new string.
   \return 0 in case of success, else < 0
   \todo documentation
int ec_slave_locate_string(ec_slave_t *slave, /**< EtherCAT slave */
                           unsigned int index, /**< string index */
                           char **ptr /**< Address of the string pointer */
                           )
Florian Pose's avatar
Florian Pose committed
{
    ec_eeprom_string_t *string;
    char *err_string;
    // Erst alten Speicher freigeben
Florian Pose's avatar
Florian Pose committed
    if (*ptr) {
        kfree(*ptr);
        *ptr = NULL;
    }

    // Index 0 bedeutet "nicht belegt"
Florian Pose's avatar
Florian Pose committed
    if (!index) return 0;

    // EEPROM-String mit Index finden und kopieren
Florian Pose's avatar
Florian Pose committed
    list_for_each_entry(string, &slave->eeprom_strings, list) {
        if (--index) continue;

        if (!(*ptr = (char *) kmalloc(string->size + 1, GFP_KERNEL))) {
            EC_ERR("Unable to allocate string memory.\n");
            return -1;
Florian Pose's avatar
Florian Pose committed
        }
        memcpy(*ptr, string->data, string->size + 1);
        return 0;
    EC_WARN("String %i not found in slave %i.\n", index, slave->ring_position);

    err_string = "(string not found)";

    if (!(*ptr = (char *) kmalloc(strlen(err_string) + 1, GFP_KERNEL))) {
        EC_ERR("Unable to allocate string memory.\n");
        return -1;
    }

    memcpy(*ptr, err_string, strlen(err_string) + 1);
Florian Pose's avatar
Florian Pose committed
    return 0;
}

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

   Acknowledges an error after a state transition.
void ec_slave_state_ack(ec_slave_t *slave, /**< EtherCAT slave */
                        uint8_t state /**< previous state */
    cycles_t start, end, timeout;
    command = &slave->master->simple_command;
    if (ec_command_npwr(command, slave->station_address, 0x0120, 2)) return;
    EC_WRITE_U16(command->data, state | EC_ACK);
    if (unlikely(ec_master_simple_io(slave->master, command))) {
        EC_WARN("Acknowledge sending failed on slave %i!\n",
                slave->ring_position);
    timeout = (cycles_t) 10 * cpu_khz; // 10ms
Florian Pose's avatar
Florian Pose committed
    while (1)
        udelay(100); // wait a little bit...
        if (ec_command_nprd(command, slave->station_address, 0x0130, 2))
            return;
        if (unlikely(ec_master_simple_io(slave->master, command))) {
            slave->current_state = EC_SLAVE_STATE_UNKNOWN;
            EC_WARN("Acknowledge checking failed on slave %i!\n",
                    slave->ring_position);
        if (likely(EC_READ_U8(command->data) == state)) {
            slave->current_state = state;
            EC_INFO("Acknowleged state 0x%02X on slave %i.\n", state,
                    slave->ring_position);
Florian Pose's avatar
Florian Pose committed
        if (unlikely((end - start) >= timeout)) {
            slave->current_state = EC_SLAVE_STATE_UNKNOWN;
            EC_WARN("Failed to acknowledge state 0x%02X on slave %i"
                    " - Timeout!\n", state, slave->ring_position);
Florian Pose's avatar
Florian Pose committed
            return;
        }
    }
}

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

/**
   Reads the AL status code of a slave and displays it.
   If the AL status code is not supported, or if no error occurred (both
   resulting in code = 0), nothing is displayed.
void ec_slave_read_al_status_code(ec_slave_t *slave /**< EtherCAT slave */)
{
    ec_command_t *command;
    uint16_t code;
    const ec_code_msg_t *al_msg;

    command = &slave->master->simple_command;

    if (ec_command_nprd(command, slave->station_address, 0x0134, 2)) return;
    if (unlikely(ec_master_simple_io(slave->master, command))) {
        EC_WARN("Failed to read AL status code on slave %i!\n",
                slave->ring_position);
        return;
    }

    if (!(code = EC_READ_U16(command->data))) return;

    for (al_msg = al_status_messages; al_msg->code; al_msg++) {
        if (al_msg->code == code) {
            EC_ERR("AL status message 0x%04X: \"%s\".\n",
                   al_msg->code, al_msg->message);
            return;
        }
    }

    EC_ERR("Unknown AL status code 0x%04X.\n", code);
}

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

   Does a state transition.
   \return 0 in case of success, else < 0
int ec_slave_state_change(ec_slave_t *slave, /**< EtherCAT slave */
                          uint8_t state /**< new state */
    cycles_t start, end, timeout;
    command = &slave->master->simple_command;
    slave->requested_state = state;

    if (ec_command_npwr(command, slave->station_address, 0x0120, 2)) return -1;
    EC_WRITE_U16(command->data, state);
    if (unlikely(ec_master_simple_io(slave->master, command))) {
        EC_ERR("Failed to set state 0x%02X on slave %i!\n",
               state, slave->ring_position);
    timeout = (cycles_t) 10 * cpu_khz; // 10ms
Florian Pose's avatar
Florian Pose committed
    while (1)
        udelay(100); // wait a little bit
        if (ec_command_nprd(command, slave->station_address, 0x0130, 2))
            return -1;
        if (unlikely(ec_master_simple_io(slave->master, command))) {
            slave->current_state = EC_SLAVE_STATE_UNKNOWN;
            EC_ERR("Failed to check state 0x%02X on slave %i!\n",
                   state, slave->ring_position);
        if (unlikely(EC_READ_U8(command->data) & 0x10)) { // state change error
            EC_ERR("Failed to set state 0x%02X - Slave %i refused state change"
                   " (code 0x%02X)!\n", state, slave->ring_position,
                   EC_READ_U8(command->data));
            slave->current_state = EC_READ_U8(command->data);
            state = slave->current_state & 0x0F;
            ec_slave_read_al_status_code(slave);
            ec_slave_state_ack(slave, state);
        if (likely(EC_READ_U8(command->data) == (state & 0x0F))) {
            slave->current_state = state;
            return 0; // state change successful
Florian Pose's avatar
Florian Pose committed
        if (unlikely((end - start) >= timeout)) {
            slave->current_state = EC_SLAVE_STATE_UNKNOWN;
            EC_ERR("Failed to check state 0x%02X of slave %i - Timeout!\n",
Florian Pose's avatar
Florian Pose committed
                   state, slave->ring_position);
            return -1;
        }
    }
}

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

/**
   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 arce 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 */
    // FMMU configuration already prepared?
    for (i = 0; i < slave->fmmu_count; i++)
        if (slave->fmmus[i].domain == domain && slave->fmmus[i].sync == sync)
            return 0;