Added debugging.
/******************************************************************************
*
* $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 "datagram.h"
#include "master.h"
#include "slave_config.h"
#include "slave.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(sii);
EC_SYSFS_READ_WRITE_ATTR(alias);
static struct attribute *def_attrs[] = {
&attr_info,
&attr_state,
&attr_sii,
&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->config = NULL;
slave->requested_state = EC_SLAVE_STATE_PREOP;
slave->current_state = EC_SLAVE_STATE_UNKNOWN;
slave->error_flag = 0;
slave->force_config = 0;
slave->base_type = 0;
slave->base_revision = 0;
slave->base_build = 0;
slave->base_fmmu_count = 0;
slave->sii_data = NULL;
slave->sii_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.strings = NULL;
slave->sii.string_count = 0;
slave->sii.has_general = 0;
slave->sii.group = NULL;
slave->sii.image = NULL;
slave->sii.order = NULL;
slave->sii.name = NULL;
memset(&slave->sii.coe_details, 0x00, sizeof(ec_sii_coe_details_t));
memset(&slave->sii.general_flags, 0x00, sizeof(ec_sii_general_flags_t));
slave->sii.current_on_ebus = 0;
slave->sii.syncs = NULL;
slave->sii.sync_count = 0;
INIT_LIST_HEAD(&slave->sii.pdos);
INIT_LIST_HEAD(&slave->sdo_dictionary);
slave->sdo_dictionary_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;
if (slave->config)
ec_slave_config_detach(slave->config);
// 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;
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
ec_slave_clear_sync_managers(slave);
// 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);
}
if (slave->sii_data)
kfree(slave->sii_data);
kfree(slave);
}
/*****************************************************************************/
/**
* Sdo kobject clear method.
*/
void ec_slave_sdos_clear(struct kobject *kobj /**< kobject for Sdos */)
{
}
/*****************************************************************************/
/** Clear the sync manager array.
*/
void ec_slave_clear_sync_managers(ec_slave_t *slave /**< EtherCAT slave. */)
{
unsigned int i;
if (slave->sii.syncs) {
for (i = 0; i < slave->sii.sync_count; i++) {
ec_sync_clear(&slave->sii.syncs[i]);
}
kfree(slave->sii.syncs);
slave->sii.syncs = NULL;
}
}
/*****************************************************************************/
/**
* 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 %u: %s -> %s.\n",
slave->ring_position, old_state, cur_state);
}
slave->current_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;
uint8_t flags;
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);
// read CoE details
flags = EC_READ_U8(data + 5);
slave->sii.coe_details.enable_sdo = (flags >> 0) & 0x01;
slave->sii.coe_details.enable_sdo_info = (flags >> 1) & 0x01;
slave->sii.coe_details.enable_pdo_assign = (flags >> 2) & 0x01;
slave->sii.coe_details.enable_pdo_configuration = (flags >> 3) & 0x01;
slave->sii.coe_details.enable_upload_at_startup = (flags >> 4) & 0x01;
slave->sii.coe_details.enable_sdo_complete_access = (flags >> 5) & 0x01;
// read general flags
flags = EC_READ_U8(data + 0x000B);
slave->sii.general_flags.enable_safeop = (flags >> 0) & 0x01;
slave->sii.general_flags.enable_not_lrw = (flags >> 1) & 0x01;
slave->sii.current_on_ebus = EC_READ_S16(data + 0x0C);
slave->sii.has_general = 1;
return 0;
}
/*****************************************************************************/
/** Fetches data from a SYNC MANAGER category.
*
* Appends the sync managers described in the category to the existing ones.
*
* \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, count, total_count;
ec_sync_t *sync;
size_t memsize;
ec_sync_t *syncs;
uint8_t index;
// one sync manager struct is 4 words long
if (data_size % 8) {
EC_ERR("Invalid SII sync manager category size %u in slave %u.\n",
data_size, slave->ring_position);
return -1;
}
count = data_size / 8;
if (count) {
total_count = count + slave->sii.sync_count;
memsize = sizeof(ec_sync_t) * total_count;
if (!(syncs = kmalloc(memsize, GFP_KERNEL))) {
EC_ERR("Failed to allocate %u bytes for sync managers.\n",
memsize);
return -1;
}
for (i = 0; i < slave->sii.sync_count; i++)
ec_sync_init_copy(syncs + i, slave->sii.syncs + i);
// initialize new sync managers
for (i = 0; i < count; i++, data += 8) {
index = i + slave->sii.sync_count;
sync = &syncs[index];
ec_sync_init(sync, slave, index);
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);
}
if (slave->sii.syncs)
kfree(slave->sii.syncs);
slave->sii.syncs = syncs;
slave->sii.sync_count = total_count;
}
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_direction_t dir /**< Pdo direction. */
)
{
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->dir = dir;
pdo->index = EC_READ_U16(data);
entry_count = EC_READ_U8(data + 2);
pdo->sync_index = EC_READ_U8(data + 3);
if (ec_pdo_set_name(pdo,
ec_slave_sii_string(slave, EC_READ_U8(data + 5)))) {
ec_pdo_clear(pdo);
kfree(pdo);
return -1;
}
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;
}
ec_pdo_entry_init(entry);
entry->index = EC_READ_U16(data);
entry->subindex = EC_READ_U8(data + 2);
if (ec_pdo_entry_set_name(entry,
ec_slave_sii_string(slave, EC_READ_U8(data + 3)))) {
ec_pdo_entry_clear(entry);
kfree(entry);
return -1;
}
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;
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 (ec_pdo_list_add_pdo_copy(&sync->pdos, pdo))
return -1;
sync->assign_source = EC_ASSIGN_SII;
}
}
return 0;
}
/*****************************************************************************/
/**
Searches the string list for an index.
\return 0 in case of success, else < 0
*/
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 %u not found in slave %u.\n",
index, slave->ring_position);
return NULL;
}
return slave->sii.strings[index];
}
/*****************************************************************************/
/** Outputs all information about a certain slave.
*/
ssize_t ec_slave_info(const ec_slave_t *slave, /**< EtherCAT slave */
char *buffer /**< Output buffer */
)
{
const ec_sync_t *sync;
const ec_pdo_t *pdo;
const ec_pdo_entry_t *pdo_entry;
int first, i;
char *large_buffer, *buf;
unsigned int size;
if (!(large_buffer = (char *) kmalloc(PAGE_SIZE * 2, GFP_KERNEL))) {
return -ENOMEM;
}
buf = large_buffer;
buf += sprintf(buf, "Ring position: %u\n",
slave->ring_position);
buf += sprintf(buf, "State: ");
buf += ec_state_string(slave->current_state, buf);
buf += sprintf(buf, " (");
buf += ec_state_string(slave->requested_state, buf);
buf += sprintf(buf, ")\n");
buf += sprintf(buf, "Flags: %s\n\n", slave->error_flag ? "ERROR" : "ok");
buf += sprintf(buf, "Data link status:\n");
for (i = 0; i < 4; i++) {
buf += sprintf(buf, " Port %u: Phy %u (",
i, slave->sii.physical_layer[i]);
switch (slave->sii.physical_layer[i]) {
case 0x00:
buf += sprintf(buf, "EBUS");
break;
case 0x01:
buf += sprintf(buf, "100BASE-TX");
break;
case 0x02:
buf += sprintf(buf, "100BASE-FX");
break;
default:
buf += sprintf(buf, "unknown");
}
buf += sprintf(buf, "), 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");
}
buf += sprintf(buf, "\n");
if (slave->sii.alias)
buf += sprintf(buf, "Configured station alias:"
" 0x%04X (%u)\n\n", slave->sii.alias, slave->sii.alias);
buf += sprintf(buf, "Identity:\n");
buf += sprintf(buf, " Vendor ID: 0x%08X (%u)\n",
slave->sii.vendor_id, slave->sii.vendor_id);
buf += sprintf(buf, " Product code: 0x%08X (%u)\n",
slave->sii.product_code, slave->sii.product_code);
buf += sprintf(buf, " Revision number: 0x%08X (%u)\n",
slave->sii.revision_number, slave->sii.revision_number);
buf += sprintf(buf, " Serial number: 0x%08X (%u)\n\n",
slave->sii.serial_number, slave->sii.serial_number);
if (slave->sii.mailbox_protocols) {
buf += sprintf(buf, "Mailboxes:\n");
buf += sprintf(buf, " 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);
buf += sprintf(buf, " Supported protocols: ");
first = 1;
if (slave->sii.mailbox_protocols & EC_MBOX_AOE) {
buf += sprintf(buf, "AoE");
first = 0;
}
if (slave->sii.mailbox_protocols & EC_MBOX_EOE) {
if (!first) buf += sprintf(buf, ", ");
buf += sprintf(buf, "EoE");
first = 0;
}
if (slave->sii.mailbox_protocols & EC_MBOX_COE) {
if (!first) buf += sprintf(buf, ", ");
buf += sprintf(buf, "CoE");
first = 0;
}
if (slave->sii.mailbox_protocols & EC_MBOX_FOE) {
if (!first) buf += sprintf(buf, ", ");
buf += sprintf(buf, "FoE");
first = 0;
}
if (slave->sii.mailbox_protocols & EC_MBOX_SOE) {
if (!first) buf += sprintf(buf, ", ");
buf += sprintf(buf, "SoE");
first = 0;
}
if (slave->sii.mailbox_protocols & EC_MBOX_VOE) {
if (!first) buf += sprintf(buf, ", ");
buf += sprintf(buf, "VoE");
}
buf += sprintf(buf, "\n\n");
}
if (slave->sii.has_general) {
buf += sprintf(buf, "General:\n");
if (slave->sii.group)
buf += sprintf(buf, " Group: %s\n", slave->sii.group);
if (slave->sii.image)
buf += sprintf(buf, " Image: %s\n", slave->sii.image);
if (slave->sii.order)
buf += sprintf(buf, " Order number: %s\n",
slave->sii.order);
if (slave->sii.name)
buf += sprintf(buf, " Name: %s\n", slave->sii.name);
if (slave->sii.mailbox_protocols & EC_MBOX_COE) {
buf += sprintf(buf, " CoE details:\n");
buf += sprintf(buf, " Enable Sdo: %s\n",
slave->sii.coe_details.enable_sdo ? "yes" : "no");
buf += sprintf(buf, " Enable Sdo Info: %s\n",
slave->sii.coe_details.enable_sdo_info ? "yes" : "no");
buf += sprintf(buf, " Enable Pdo Assign: %s\n",
slave->sii.coe_details.enable_pdo_assign ? "yes" : "no");
buf += sprintf(buf, " Enable Pdo Configuration: %s\n",
slave->sii.coe_details.enable_pdo_configuration ?
"yes" : "no");
buf += sprintf(buf, " Enable Upload at startup: %s\n",
slave->sii.coe_details.enable_upload_at_startup ?
"yes" : "no");
buf += sprintf(buf, " Enable Sdo complete access: %s\n",
slave->sii.coe_details.enable_sdo_complete_access
? "yes" : "no");
}
buf += sprintf(buf, " Flags:\n");
buf += sprintf(buf, " Enable SafeOp: %s\n",
slave->sii.general_flags.enable_safeop ? "yes" : "no");
buf += sprintf(buf, " Enable notLRW: %s\n",
slave->sii.general_flags.enable_not_lrw ? "yes" : "no");
buf += sprintf(buf, " Current consumption: %i mA\n\n",
slave->sii.current_on_ebus);
}
if (slave->sii.sync_count) {
buf += sprintf(buf, "Sync managers / assigned Pdos:\n");
for (i = 0; i < slave->sii.sync_count; i++) {
sync = &slave->sii.syncs[i];
buf += sprintf(buf,
" SM%u: addr 0x%04X, size %u, control 0x%02X, %s\n",
sync->index, sync->physical_start_address,
sync->length, sync->control_register,
sync->enable ? "enable" : "disable");
if (list_empty(&sync->pdos.list)) {
buf += sprintf(buf, " No Pdos assigned.\n");
} else if (sync->assign_source != EC_ASSIGN_NONE) {
buf += sprintf(buf, " Pdo assignment from ");
switch (sync->assign_source) {
case EC_ASSIGN_SII:
buf += sprintf(buf, "SII");
break;
case EC_ASSIGN_COE:
buf += sprintf(buf, "CoE");
break;
case EC_ASSIGN_CUSTOM:
buf += sprintf(buf, "application");
break;
default:
buf += sprintf(buf, "?");
break;
}
buf += sprintf(buf, ".\n");
}
list_for_each_entry(pdo, &sync->pdos.list, list) {
buf += sprintf(buf, " %s 0x%04X \"%s\"\n",
pdo->dir == EC_DIR_OUTPUT ? "RxPdo" : "TxPdo",
pdo->index, pdo->name ? pdo->name : "???");
list_for_each_entry(pdo_entry, &pdo->entries, list) {
buf += sprintf(buf,
" 0x%04X:%02X, %u bit, \"%s\"\n",
pdo_entry->index, pdo_entry->subindex,
pdo_entry->bit_length,
pdo_entry->name ? pdo_entry->name : "???");
}
}
}
buf += sprintf(buf, "\n");
}
// type-cast to avoid warnings on some compilers
if (!list_empty((struct list_head *) &slave->sii.pdos)) {
buf += sprintf(buf, "Available Pdos from SII:\n");
list_for_each_entry(pdo, &slave->sii.pdos, list) {
buf += sprintf(buf, " %s 0x%04X \"%s\"",
pdo->dir == EC_DIR_OUTPUT ? "RxPdo" : "TxPdo",
pdo->index, pdo->name ? pdo->name : "???");
if (pdo->sync_index >= 0)
buf += sprintf(buf, ", default assignment: SM%u.\n",
pdo->sync_index);
else
buf += sprintf(buf, ", no default assignment.\n");
list_for_each_entry(pdo_entry, &pdo->entries, list) {
buf += sprintf(buf, " 0x%04X:%02X, %u bit, \"%s\"\n",
pdo_entry->index, pdo_entry->subindex,
pdo_entry->bit_length,
pdo_entry->name ? pdo_entry->name : "???");
}
}
buf += sprintf(buf, "\n");
}
size = buf - large_buffer;
if (size >= PAGE_SIZE) {
const char trunc[] = "\n---TRUNCATED---\n";
unsigned int len = strlen(trunc);
memcpy(large_buffer + PAGE_SIZE - len, trunc, len);
}
size = min(size, (unsigned int) PAGE_SIZE);
memcpy(buffer, large_buffer, size);
kfree(large_buffer);
return size;
}
/*****************************************************************************/
/**
* Schedules an SII write request.
* \return 0 case of success, otherwise error code.
*/
int ec_slave_schedule_sii_writing(
ec_sii_write_request_t *request /**< SII write request */
)
{
ec_master_t *master = request->slave->master;
request->state = EC_REQUEST_QUEUED;
// schedule SII write request.
down(&master->sii_sem);
list_add_tail(&request->list, &master->sii_requests);
up(&master->sii_sem);
// wait for processing through FSM
if (wait_event_interruptible(master->sii_queue,
request->state != EC_REQUEST_QUEUED)) {
// interrupted by signal
down(&master->sii_sem);
if (request->state == EC_REQUEST_QUEUED) {
list_del(&request->list);
up(&master->sii_sem);
return -EINTR;
}
// request already processing: interrupt not possible.
up(&master->sii_sem);
}
// wait until master FSM has finished processing
wait_event(master->sii_queue,
request->state != EC_REQUEST_BUSY);
return request->state == EC_REQUEST_SUCCESS ? 0 : -EIO;
}
/*****************************************************************************/
/**
* Calculates the SII 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_sii_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 SII contents to a slave.
* \return data size written in case of success, otherwise error code.
*/
ssize_t ec_slave_write_sii(ec_slave_t *slave, /**< EtherCAT slave */
const uint8_t *data, /**< new SII data */
size_t size /**< size of data in bytes */
)
{
ec_sii_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 SIIs only allowed in idle mode!\n");
return -EBUSY;
}
if (size % 2) {
EC_ERR("SII data size is odd (%u bytes)! SII data must be"
" word-aligned. Dropping.\n", size);
return -EINVAL;
}
// init SII 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("SII data too short (%u words)! Mimimum is"
" 40 fixed words + 1 delimiter. Dropping.\n",
request.word_size);
return -EINVAL;
}
// calculate checksum
crc = ec_slave_sii_crc(data, 14); // CRC over words 0 to 6
if (crc != data[14]) {
EC_WARN("SII CRC incorrect. Must be 0x%02x.\n", crc);
}
cat_header = (const uint16_t *) request.data
+ EC_FIRST_SII_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("SII 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("SII data corrupted! Dropping.\n");
return -EINVAL;
}
cat_header += cat_size + 2;
cat_type = EC_READ_U16(cat_header);
}
// SII data ok. schedule writing.
if ((ret = ec_slave_schedule_sii_writing(&request)))
return ret; // error code
return size; // success
}
/*****************************************************************************/
/**
* Writes the Secondary slave address (alias) to the slave's SII.
* \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_sii_write_request_t request;
char *remainder;
uint16_t alias;
int ret;
uint8_t sii_data[16], crc;
if (slave->master->mode != EC_MASTER_MODE_IDLE) { // FIXME
EC_ERR("Writing to SII 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->sii_data || slave->sii_size < 16) {
EC_ERR("Failed to read SII contents from slave %u.\n",
slave->ring_position);
return -EINVAL;
}
// copy first 7 words of recent SII contents
memcpy(sii_data, slave->sii_data, 14);
// write new alias address in word 4
EC_WRITE_U16(sii_data + 8, alias);
// calculate new checksum over words 0 to 6
crc = ec_slave_sii_crc(sii_data, 14);
EC_WRITE_U16(sii_data + 14, crc);
// init SII write request
INIT_LIST_HEAD(&request.list);
request.slave = slave;
request.data = sii_data;
request.word_offset = 0x0000;
request.word_size = 8;
if ((ret = ec_slave_schedule_sii_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_SAFEOP:
return sprintf(buffer, "SAFEOP\n");
case EC_SLAVE_STATE_OP:
return sprintf(buffer, "OP\n");
default:
return sprintf(buffer, "UNKNOWN\n");
}
}
else if (attr == &attr_sii) {
if (slave->sii_data) {
if (slave->sii_size > PAGE_SIZE) {
EC_ERR("SII contents of slave %u exceed 1 page (%u/%u).\n",
slave->ring_position, slave->sii_size,
(int) PAGE_SIZE);
}
else {
memcpy(buffer, slave->sii_data, slave->sii_size);
return slave->sii_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, "SAFEOP\n"))
ec_slave_request_state(slave, EC_SLAVE_STATE_SAFEOP);
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 %u.\n",
state, slave->ring_position);
return size;
}
else if (attr == &attr_sii) {
return ec_slave_write_sii(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.
*
* \todo This seems not to be correct in every case...
* \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;
}
if (slave->sii.sync_count != 1) {
sync_index = (unsigned int) dir;
if (slave->sii.mailbox_protocols) sync_index += 2;
if (sync_index >= slave->sii.sync_count)
return NULL;
} else { // sync_count == 1
// A single sync manager may be used for inputs OR outputs!
if (ec_sync_direction(&slave->sii.syncs[0]) != dir)
return NULL;
sync_index = 0;
}
return &slave->sii.syncs[sync_index];
}
/*****************************************************************************/
/**
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, or 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;
}
/*****************************************************************************/
/** Finds a mapped Pdo.
* \returns The desired Pdo object, or NULL.
*/
const ec_pdo_t *ec_slave_find_pdo(
const ec_slave_t *slave, /**< Slave. */
uint16_t index /**< Pdo index to find. */
)
{
unsigned int i;
const ec_sync_t *sync;
const ec_pdo_t *pdo;
for (i = 0; i < slave->sii.sync_count; i++) {
sync = &slave->sii.syncs[i];
if (!(pdo = ec_pdo_list_find_pdo_const(&sync->pdos, index)))
continue;
return pdo;
}
return NULL;
}
/*****************************************************************************/