/****************************************************************************** * * $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, "\nName: "); if (slave->sii_name) off += sprintf(buffer + off, "%s", slave->sii_name); off += sprintf(buffer + off, "\nVendor ID: 0x%08X\n", slave->sii_vendor_id); off += sprintf(buffer + off, "Product code: 0x%08X\n\n", slave->sii_product_code); 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, ")\nFlags: %s, %s\n", slave->online_state == EC_SLAVE_ONLINE ? "online" : "OFFLINE", slave->error_flag ? "ERROR" : "ok"); off += sprintf(buffer + off, "Ring position: %i\n", slave->ring_position); off += sprintf(buffer + off, "Current consumption: %i mA\n\n", slave->sii_current_on_ebus); off += sprintf(buffer + off, "Data link status:\n"); for (i = 0; i < 4; i++) { off += sprintf(buffer + off, " Port %i (", 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 (%i)", slave->sii_physical_layer[i]); } 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"); } if (slave->sii_mailbox_protocols) { off += sprintf(buffer + off, "\nMailboxes:\n"); off += sprintf(buffer + off, " RX mailbox: 0x%04X/%i," " TX mailbox: 0x%04X/%i\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"); } if (slave->sii_alias || slave->sii_group || slave->sii_image || slave->sii_order) off += sprintf(buffer + off, "\nSII data:\n"); if (slave->sii_alias) off += sprintf(buffer + off, " Configured station alias:" " 0x%04X (%i)\n", slave->sii_alias, slave->sii_alias); 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_sync_count) off += sprintf(buffer + off, "\nSync 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); } } } // type-cast to avoid warnings on some compilers if (!list_empty((struct list_head *) &slave->sii_pdos)) off += sprintf(buffer + off, "\nAvailable 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); } } // type-cast to avoid warnings on some compilers if (!list_empty((struct list_head *) &slave->sdo_confs)) off += sprintf(buffer + off, "\nSDO 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 */ /*****************************************************************************/