fp@39: /****************************************************************************** fp@0: * fp@39: * $Id$ fp@0: * fp@197: * Copyright (C) 2006 Florian Pose, Ingenieurgemeinschaft IgH fp@197: * fp@197: * This file is part of the IgH EtherCAT Master. fp@197: * fp@197: * The IgH EtherCAT Master is free software; you can redistribute it fp@197: * and/or modify it under the terms of the GNU General Public License fp@246: * as published by the Free Software Foundation; either version 2 of the fp@246: * License, or (at your option) any later version. fp@197: * fp@197: * The IgH EtherCAT Master is distributed in the hope that it will be fp@197: * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of fp@197: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fp@197: * GNU General Public License for more details. fp@197: * fp@197: * You should have received a copy of the GNU General Public License fp@197: * along with the IgH EtherCAT Master; if not, write to the Free Software fp@197: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA fp@197: * fp@246: * The right to use EtherCAT Technology is granted and comes free of fp@246: * charge under condition of compatibility of product made by fp@246: * Licensee. People intending to distribute/sell products based on the fp@246: * code, have to sign an agreement to guarantee that products using fp@246: * software based on IgH EtherCAT master stay compatible with the actual fp@246: * EtherCAT specification (which are released themselves as an open fp@246: * standard) as the (only) precondition to have the right to use EtherCAT fp@246: * Technology, IP and trade marks. fp@246: * fp@39: *****************************************************************************/ fp@0: fp@199: /** fp@199: \file fp@199: EtherCAT slave methods. fp@199: */ fp@199: fp@199: /*****************************************************************************/ fp@199: fp@24: #include fp@73: #include fp@0: fp@54: #include "globals.h" fp@54: #include "slave.h" fp@293: #include "datagram.h" fp@98: #include "master.h" fp@0: fp@39: /*****************************************************************************/ fp@0: fp@251: extern const ec_code_msg_t al_status_messages[]; fp@251: fp@251: /*****************************************************************************/ fp@251: fp@404: void ec_slave_clear(struct kobject *); fp@482: void ec_slave_sdos_clear(struct kobject *); fp@182: ssize_t ec_show_slave_attribute(struct kobject *, struct attribute *, char *); fp@256: ssize_t ec_store_slave_attribute(struct kobject *, struct attribute *, fp@256: const char *, size_t); fp@182: fp@182: /*****************************************************************************/ fp@182: fp@199: /** \cond */ fp@199: fp@325: EC_SYSFS_READ_ATTR(info); fp@256: EC_SYSFS_READ_WRITE_ATTR(state); fp@269: EC_SYSFS_READ_WRITE_ATTR(eeprom); fp@182: fp@182: static struct attribute *def_attrs[] = { fp@325: &attr_info, fp@256: &attr_state, fp@266: &attr_eeprom, fp@182: NULL, fp@182: }; fp@182: fp@182: static struct sysfs_ops sysfs_ops = { fp@256: .show = ec_show_slave_attribute, fp@256: .store = ec_store_slave_attribute fp@182: }; fp@182: fp@182: static struct kobj_type ktype_ec_slave = { fp@182: .release = ec_slave_clear, fp@182: .sysfs_ops = &sysfs_ops, fp@182: .default_attrs = def_attrs fp@182: }; fp@118: fp@482: static struct kobj_type ktype_ec_slave_sdos = { fp@482: .release = ec_slave_sdos_clear fp@482: }; fp@419: fp@199: /** \endcond */ fp@199: fp@118: /*****************************************************************************/ fp@118: fp@0: /** fp@195: Slave constructor. fp@195: \return 0 in case of success, else < 0 fp@195: */ fp@195: fp@195: int ec_slave_init(ec_slave_t *slave, /**< EtherCAT slave */ fp@195: ec_master_t *master, /**< EtherCAT master */ fp@195: uint16_t ring_position, /**< ring position */ fp@195: uint16_t station_address /**< station address to configure */ fp@182: ) fp@73: { fp@142: unsigned int i; fp@142: fp@182: slave->ring_position = ring_position; fp@182: slave->station_address = station_address; fp@182: fp@73: slave->master = master; fp@325: fp@325: slave->requested_state = EC_SLAVE_STATE_UNKNOWN; fp@325: slave->current_state = EC_SLAVE_STATE_UNKNOWN; fp@523: slave->self_configured = 0; fp@325: slave->error_flag = 0; fp@325: slave->online = 1; fp@325: slave->fmmu_count = 0; fp@325: fp@188: slave->coupler_index = 0; fp@188: slave->coupler_subindex = 0xFFFF; fp@325: fp@73: slave->base_type = 0; fp@73: slave->base_revision = 0; fp@73: slave->base_build = 0; fp@73: slave->base_fmmu_count = 0; fp@73: slave->base_sync_count = 0; fp@325: fp@325: slave->eeprom_data = NULL; fp@325: slave->eeprom_size = 0; fp@325: slave->new_eeprom_data = NULL; fp@325: slave->new_eeprom_size = 0; fp@325: fp@114: slave->sii_alias = 0; fp@73: slave->sii_vendor_id = 0; fp@73: slave->sii_product_code = 0; fp@73: slave->sii_revision_number = 0; fp@73: slave->sii_serial_number = 0; fp@136: slave->sii_rx_mailbox_offset = 0; fp@136: slave->sii_rx_mailbox_size = 0; fp@136: slave->sii_tx_mailbox_offset = 0; fp@136: slave->sii_tx_mailbox_size = 0; fp@133: slave->sii_mailbox_protocols = 0; fp@325: slave->sii_group = NULL; fp@325: slave->sii_image = NULL; fp@325: slave->sii_order = NULL; fp@325: slave->sii_name = NULL; fp@499: slave->sii_current_on_ebus = 0; fp@325: fp@325: INIT_LIST_HEAD(&slave->sii_strings); fp@325: INIT_LIST_HEAD(&slave->sii_syncs); fp@325: INIT_LIST_HEAD(&slave->sii_pdos); fp@135: INIT_LIST_HEAD(&slave->sdo_dictionary); fp@329: INIT_LIST_HEAD(&slave->sdo_confs); fp@145: fp@419: slave->sdo_dictionary_fetched = 0; fp@419: slave->jiffies_preop = 0; fp@419: fp@183: for (i = 0; i < 4; i++) { fp@183: slave->dl_link[i] = 0; fp@183: slave->dl_loop[i] = 0; fp@183: slave->dl_signal[i] = 0; fp@190: slave->sii_physical_layer[i] = 0xFF; fp@145: } fp@182: fp@484: // init kobject and add it to the hierarchy fp@484: memset(&slave->kobj, 0x00, sizeof(struct kobject)); fp@484: kobject_init(&slave->kobj); fp@484: slave->kobj.ktype = &ktype_ec_slave; fp@484: slave->kobj.parent = &master->kobj; fp@484: if (kobject_set_name(&slave->kobj, "slave%03i", slave->ring_position)) { fp@484: EC_ERR("Failed to set kobject name.\n"); fp@484: goto out_slave_put; fp@484: } fp@484: if (kobject_add(&slave->kobj)) { fp@484: EC_ERR("Failed to add slave's kobject.\n"); fp@484: goto out_slave_put; fp@484: } fp@484: fp@484: // init SDO kobject and add it to the hierarchy fp@484: memset(&slave->sdo_kobj, 0x00, sizeof(struct kobject)); fp@484: kobject_init(&slave->sdo_kobj); fp@484: slave->sdo_kobj.ktype = &ktype_ec_slave_sdos; fp@484: slave->sdo_kobj.parent = &slave->kobj; fp@484: if (kobject_set_name(&slave->sdo_kobj, "sdos")) { fp@484: EC_ERR("Failed to set kobject name.\n"); fp@484: goto out_sdo_put; fp@484: } fp@484: if (kobject_add(&slave->sdo_kobj)) { fp@484: EC_ERR("Failed to add SDOs kobject.\n"); fp@484: goto out_sdo_put; fp@484: } fp@484: fp@182: return 0; fp@484: fp@484: out_sdo_put: fp@484: kobject_put(&slave->sdo_kobj); fp@484: kobject_del(&slave->kobj); fp@484: out_slave_put: fp@484: kobject_put(&slave->kobj); fp@484: return -1; fp@73: } fp@73: fp@73: /*****************************************************************************/ fp@73: fp@73: /** fp@195: Slave destructor. fp@448: Clears and frees a slave object. fp@448: */ fp@448: fp@448: void ec_slave_destroy(ec_slave_t *slave /**< EtherCAT slave */) fp@448: { fp@448: ec_sdo_t *sdo, *next_sdo; fp@448: fp@448: // free all SDOs fp@448: list_for_each_entry_safe(sdo, next_sdo, &slave->sdo_dictionary, list) { fp@448: list_del(&sdo->list); fp@448: ec_sdo_destroy(sdo); fp@448: } fp@448: fp@448: // free SDO kobject fp@484: kobject_del(&slave->sdo_kobj); fp@448: kobject_put(&slave->sdo_kobj); fp@448: fp@448: // destroy self fp@448: kobject_del(&slave->kobj); fp@448: kobject_put(&slave->kobj); fp@448: } fp@448: fp@448: /*****************************************************************************/ fp@448: fp@448: /** fp@448: Clear and free slave. fp@448: This method is called by the kobject, fp@448: once there are no more references to it. fp@195: */ fp@195: fp@195: void ec_slave_clear(struct kobject *kobj /**< kobject of the slave */) fp@182: { fp@182: ec_slave_t *slave; fp@325: ec_sii_string_t *string, *next_str; fp@325: ec_sii_sync_t *sync, *next_sync; fp@325: ec_sii_pdo_t *pdo, *next_pdo; fp@325: ec_sii_pdo_entry_t *entry, *next_ent; fp@329: ec_sdo_data_t *sdodata, *next_sdodata; fp@118: fp@182: slave = container_of(kobj, ec_slave_t, kobj); fp@182: fp@195: // free all string objects fp@325: list_for_each_entry_safe(string, next_str, &slave->sii_strings, list) { fp@118: list_del(&string->list); fp@118: kfree(string); fp@118: } fp@121: fp@195: // free all sync managers fp@325: list_for_each_entry_safe(sync, next_sync, &slave->sii_syncs, list) { fp@126: list_del(&sync->list); fp@126: kfree(sync); fp@126: } fp@126: fp@195: // free all PDOs fp@325: list_for_each_entry_safe(pdo, next_pdo, &slave->sii_pdos, list) { fp@126: list_del(&pdo->list); fp@126: if (pdo->name) kfree(pdo->name); fp@126: fp@195: // free all PDO entries fp@126: list_for_each_entry_safe(entry, next_ent, &pdo->entries, list) { fp@126: list_del(&entry->list); fp@126: if (entry->name) kfree(entry->name); fp@126: kfree(entry); fp@126: } fp@126: fp@126: kfree(pdo); fp@126: } fp@126: fp@325: if (slave->sii_group) kfree(slave->sii_group); fp@325: if (slave->sii_image) kfree(slave->sii_image); fp@325: if (slave->sii_order) kfree(slave->sii_order); fp@325: if (slave->sii_name) kfree(slave->sii_name); fp@135: fp@329: // free all SDO configurations fp@329: list_for_each_entry_safe(sdodata, next_sdodata, &slave->sdo_confs, list) { fp@329: list_del(&sdodata->list); fp@329: kfree(sdodata->data); fp@329: kfree(sdodata); fp@329: } fp@329: fp@266: if (slave->eeprom_data) kfree(slave->eeprom_data); fp@269: if (slave->new_eeprom_data) kfree(slave->new_eeprom_data); fp@418: fp@418: kfree(slave); fp@121: } fp@121: fp@121: /*****************************************************************************/ fp@121: fp@121: /** fp@482: */ fp@482: fp@482: void ec_slave_sdos_clear(struct kobject *kobj /**< kobject for SDOs */) fp@482: { fp@482: } fp@482: fp@482: /*****************************************************************************/ fp@482: fp@482: /** fp@446: Reset slave from operation mode. fp@446: */ fp@446: fp@446: void ec_slave_reset(ec_slave_t *slave /**< EtherCAT slave */) fp@446: { fp@465: ec_sdo_data_t *sdodata, *next_sdodata; fp@467: ec_sii_sync_t *sync; fp@465: fp@465: // remove FMMU configurations fp@446: slave->fmmu_count = 0; fp@465: fp@465: // free all SDO configurations fp@465: list_for_each_entry_safe(sdodata, next_sdodata, &slave->sdo_confs, list) { fp@465: list_del(&sdodata->list); fp@465: kfree(sdodata->data); fp@465: kfree(sdodata); fp@465: } fp@467: fp@467: // remove estimated sync manager sizes fp@467: list_for_each_entry(sync, &slave->sii_syncs, list) { fp@467: sync->est_length = 0; fp@467: } fp@446: } fp@446: fp@446: /*****************************************************************************/ fp@446: fp@446: /** fp@446: */ fp@446: fp@446: void ec_slave_request_state(ec_slave_t *slave, /**< ETherCAT slave */ fp@446: ec_slave_state_t state /**< new state */ fp@446: ) fp@446: { fp@446: slave->requested_state = state; fp@446: slave->error_flag = 0; fp@446: } fp@446: fp@446: /*****************************************************************************/ fp@446: fp@446: /** fp@195: Fetches data from a STRING category. fp@195: \return 0 in case of success, else < 0 fp@195: */ fp@195: fp@195: int ec_slave_fetch_strings(ec_slave_t *slave, /**< EtherCAT slave */ fp@195: const uint8_t *data /**< category data */ fp@118: ) fp@118: { fp@118: unsigned int string_count, i; fp@118: size_t size; fp@118: off_t offset; fp@325: ec_sii_string_t *string; fp@118: fp@118: string_count = data[0]; fp@118: offset = 1; fp@118: for (i = 0; i < string_count; i++) { fp@118: size = data[offset]; fp@195: // allocate memory for string structure and data at a single blow fp@325: if (!(string = (ec_sii_string_t *) fp@325: kmalloc(sizeof(ec_sii_string_t) + size + 1, GFP_ATOMIC))) { fp@118: EC_ERR("Failed to allocate string memory.\n"); fp@118: return -1; fp@118: } fp@121: string->size = size; fp@161: // string memory appended to string structure fp@325: string->data = (char *) string + sizeof(ec_sii_string_t); fp@118: memcpy(string->data, data + offset + 1, size); fp@118: string->data[size] = 0x00; fp@325: list_add_tail(&string->list, &slave->sii_strings); fp@118: offset += 1 + size; fp@118: } fp@118: fp@118: return 0; fp@118: } fp@118: fp@118: /*****************************************************************************/ fp@118: fp@118: /** fp@195: Fetches data from a GENERAL category. fp@195: \return 0 in case of success, else < 0 fp@195: */ fp@195: fp@325: void ec_slave_fetch_general(ec_slave_t *slave, /**< EtherCAT slave */ fp@325: const uint8_t *data /**< category data */ fp@325: ) fp@123: { fp@190: unsigned int i; fp@190: fp@325: ec_slave_locate_string(slave, data[0], &slave->sii_group); fp@325: ec_slave_locate_string(slave, data[1], &slave->sii_image); fp@325: ec_slave_locate_string(slave, data[2], &slave->sii_order); fp@325: ec_slave_locate_string(slave, data[3], &slave->sii_name); fp@123: fp@190: for (i = 0; i < 4; i++) fp@195: slave->sii_physical_layer[i] = fp@195: (data[4] & (0x03 << (i * 2))) >> (i * 2); fp@499: fp@499: slave->sii_current_on_ebus = EC_READ_S16(data + 0x0C); fp@118: } fp@118: fp@118: /*****************************************************************************/ fp@118: fp@118: /** fp@195: Fetches data from a SYNC MANAGER category. fp@195: \return 0 in case of success, else < 0 fp@195: */ fp@195: fp@195: int ec_slave_fetch_sync(ec_slave_t *slave, /**< EtherCAT slave */ fp@195: const uint8_t *data, /**< category data */ fp@195: size_t word_count /**< number of words */ fp@126: ) fp@126: { fp@126: unsigned int sync_count, i; fp@325: ec_sii_sync_t *sync; fp@126: fp@195: sync_count = word_count / 4; // sync manager struct is 4 words long fp@126: fp@126: for (i = 0; i < sync_count; i++, data += 8) { fp@325: if (!(sync = (ec_sii_sync_t *) fp@325: kmalloc(sizeof(ec_sii_sync_t), GFP_ATOMIC))) { fp@126: EC_ERR("Failed to allocate Sync-Manager memory.\n"); fp@126: return -1; fp@126: } fp@126: fp@126: sync->index = i; fp@325: sync->physical_start_address = EC_READ_U16(data); fp@325: sync->length = EC_READ_U16(data + 2); fp@325: sync->control_register = EC_READ_U8 (data + 4); fp@325: sync->enable = EC_READ_U8 (data + 6); fp@325: fp@416: sync->est_length = 0; fp@416: fp@325: list_add_tail(&sync->list, &slave->sii_syncs); fp@126: } fp@126: fp@126: return 0; fp@118: } fp@118: fp@118: /*****************************************************************************/ fp@118: fp@118: /** fp@195: Fetches data from a [RT]XPDO category. fp@195: \return 0 in case of success, else < 0 fp@195: */ fp@195: fp@195: int ec_slave_fetch_pdo(ec_slave_t *slave, /**< EtherCAT slave */ fp@195: const uint8_t *data, /**< category data */ fp@195: size_t word_count, /**< number of words */ fp@325: ec_sii_pdo_type_t pdo_type /**< PDO type */ fp@126: ) fp@126: { fp@325: ec_sii_pdo_t *pdo; fp@325: ec_sii_pdo_entry_t *entry; fp@126: unsigned int entry_count, i; fp@126: fp@126: while (word_count >= 4) { fp@325: if (!(pdo = (ec_sii_pdo_t *) fp@325: kmalloc(sizeof(ec_sii_pdo_t), GFP_ATOMIC))) { fp@126: EC_ERR("Failed to allocate PDO memory.\n"); fp@126: return -1; fp@126: } fp@126: fp@126: INIT_LIST_HEAD(&pdo->entries); fp@126: pdo->type = pdo_type; fp@126: fp@325: pdo->index = EC_READ_U16(data); fp@325: entry_count = EC_READ_U8(data + 2); fp@325: pdo->sync_index = EC_READ_U8(data + 3); fp@126: pdo->name = NULL; fp@325: ec_slave_locate_string(slave, EC_READ_U8(data + 5), &pdo->name); fp@325: fp@325: list_add_tail(&pdo->list, &slave->sii_pdos); fp@126: fp@126: word_count -= 4; fp@126: data += 8; fp@126: fp@126: for (i = 0; i < entry_count; i++) { fp@325: if (!(entry = (ec_sii_pdo_entry_t *) fp@325: kmalloc(sizeof(ec_sii_pdo_entry_t), GFP_ATOMIC))) { fp@126: EC_ERR("Failed to allocate PDO entry memory.\n"); fp@126: return -1; fp@126: } fp@126: fp@325: entry->index = EC_READ_U16(data); fp@325: entry->subindex = EC_READ_U8(data + 2); fp@126: entry->name = NULL; fp@325: ec_slave_locate_string(slave, EC_READ_U8(data + 3), &entry->name); fp@325: entry->bit_length = EC_READ_U8(data + 5); fp@126: fp@126: list_add_tail(&entry->list, &pdo->entries); fp@126: fp@126: word_count -= 4; fp@126: data += 8; fp@126: } fp@126: } fp@126: fp@126: return 0; fp@114: } fp@114: fp@114: /*****************************************************************************/ fp@114: fp@114: /** fp@195: Searches the string list for an index and allocates a new string. fp@195: \return 0 in case of success, else < 0 fp@197: \todo documentation fp@197: */ fp@197: fp@197: int ec_slave_locate_string(ec_slave_t *slave, /**< EtherCAT slave */ fp@197: unsigned int index, /**< string index */ fp@197: char **ptr /**< Address of the string pointer */ fp@197: ) fp@123: { fp@325: ec_sii_string_t *string; fp@126: char *err_string; fp@126: fp@126: // Erst alten Speicher freigeben fp@123: if (*ptr) { fp@123: kfree(*ptr); fp@123: *ptr = NULL; fp@123: } fp@123: fp@126: // Index 0 bedeutet "nicht belegt" fp@123: if (!index) return 0; fp@123: fp@126: // EEPROM-String mit Index finden und kopieren fp@325: list_for_each_entry(string, &slave->sii_strings, list) { fp@126: if (--index) continue; fp@126: fp@296: if (!(*ptr = (char *) kmalloc(string->size + 1, GFP_ATOMIC))) { fp@126: EC_ERR("Unable to allocate string memory.\n"); fp@126: return -1; fp@126: } fp@126: memcpy(*ptr, string->data, string->size + 1); fp@126: return 0; fp@126: } fp@126: fp@461: if (slave->master->debug_level) fp@461: EC_WARN("String %i not found in slave %i.\n", fp@461: index, slave->ring_position); fp@126: fp@126: err_string = "(string not found)"; fp@126: fp@296: if (!(*ptr = (char *) kmalloc(strlen(err_string) + 1, GFP_ATOMIC))) { fp@325: EC_WARN("Unable to allocate string memory.\n"); fp@126: return -1; fp@126: } fp@126: fp@126: memcpy(*ptr, err_string, strlen(err_string) + 1); fp@123: return 0; fp@123: } fp@123: fp@123: /*****************************************************************************/ fp@123: fp@123: /** fp@195: Prepares an FMMU configuration. fp@195: Configuration data for the FMMU is saved in the slave structure and is fp@195: written to the slave in ecrt_master_activate(). fp@195: The FMMU configuration is done in a way, that the complete data range fp@325: of the corresponding sync manager is covered. Seperate FMMUs are configured fp@195: for each domain. fp@195: If the FMMU configuration is already prepared, the function returns with fp@195: success. fp@195: \return 0 in case of success, else < 0 fp@195: */ fp@195: fp@195: int ec_slave_prepare_fmmu(ec_slave_t *slave, /**< EtherCAT slave */ fp@195: const ec_domain_t *domain, /**< domain */ fp@325: const ec_sii_sync_t *sync /**< sync manager */ fp@160: ) fp@73: { fp@73: unsigned int i; fp@412: ec_fmmu_t *fmmu; fp@73: fp@195: // FMMU configuration already prepared? fp@412: for (i = 0; i < slave->fmmu_count; i++) { fp@412: fmmu = &slave->fmmus[i]; fp@412: if (fmmu->domain == domain && fmmu->sync == sync) fp@73: return 0; fp@412: } fp@73: fp@195: // reserve new FMMU... fp@91: fp@73: if (slave->fmmu_count >= slave->base_fmmu_count) { fp@84: EC_ERR("Slave %i FMMU limit reached!\n", slave->ring_position); fp@73: return -1; fp@73: } fp@73: fp@412: fmmu = &slave->fmmus[slave->fmmu_count]; fp@412: fp@412: fmmu->index = slave->fmmu_count; fp@412: fmmu->domain = domain; fp@412: fmmu->sync = sync; fp@412: fmmu->logical_start_address = 0; fp@412: fp@73: slave->fmmu_count++; fp@73: fp@73: return 0; fp@73: } fp@73: fp@73: /*****************************************************************************/ fp@73: fp@73: /** fp@195: Outputs all information about a certain slave. fp@325: */ fp@325: fp@325: size_t ec_slave_info(const ec_slave_t *slave, /**< EtherCAT slave */ fp@325: char *buffer /**< Output buffer */ fp@325: ) fp@325: { fp@325: off_t off = 0; fp@325: ec_sii_sync_t *sync; fp@325: ec_sii_pdo_t *pdo; fp@325: ec_sii_pdo_entry_t *pdo_entry; fp@142: int first, i; fp@464: ec_sdo_data_t *sdodata; fp@464: char str[20]; fp@126: fp@325: off += sprintf(buffer + off, "\nName: "); fp@325: fp@325: if (slave->sii_name) fp@325: off += sprintf(buffer + off, "%s", slave->sii_name); fp@325: fp@339: off += sprintf(buffer + off, "\nVendor ID: 0x%08X\n", fp@325: slave->sii_vendor_id); fp@325: off += sprintf(buffer + off, "Product code: 0x%08X\n\n", fp@325: slave->sii_product_code); fp@325: fp@339: off += sprintf(buffer + off, "State: "); fp@339: off += ec_state_string(slave->current_state, buffer + off); fp@519: off += sprintf(buffer + off, " ("); fp@519: off += ec_state_string(slave->requested_state, buffer + off); fp@519: off += sprintf(buffer + off, ")\nFlags: %s, %s\n", fp@458: slave->online ? "online" : "OFFLINE", fp@458: slave->error_flag ? "ERROR" : "ok"); fp@458: off += sprintf(buffer + off, "Ring position: %i\n", fp@339: slave->ring_position); fp@357: off += sprintf(buffer + off, "Advanced position: %i:%i\n", fp@325: slave->coupler_index, slave->coupler_subindex); fp@499: off += sprintf(buffer + off, "Coupler: %s\n", fp@357: ec_slave_is_coupler(slave) ? "yes" : "no"); fp@499: off += sprintf(buffer + off, "Current consumption: %i mA\n\n", fp@499: slave->sii_current_on_ebus); fp@325: fp@325: off += sprintf(buffer + off, "Data link status:\n"); fp@183: for (i = 0; i < 4; i++) { fp@325: off += sprintf(buffer + off, " Port %i (", i); fp@190: switch (slave->sii_physical_layer[i]) { fp@190: case 0x00: fp@325: off += sprintf(buffer + off, "EBUS"); fp@190: break; fp@190: case 0x01: fp@325: off += sprintf(buffer + off, "100BASE-TX"); fp@190: break; fp@190: case 0x02: fp@325: off += sprintf(buffer + off, "100BASE-FX"); fp@190: break; fp@190: default: fp@325: off += sprintf(buffer + off, "unknown (%i)", fp@325: slave->sii_physical_layer[i]); fp@325: } fp@325: off += sprintf(buffer + off, ") Link %s, Loop %s, %s\n", fp@325: slave->dl_link[i] ? "up" : "down", fp@325: slave->dl_loop[i] ? "closed" : "open", fp@325: slave->dl_signal[i] ? "Signal detected" : "No signal"); fp@325: } fp@147: fp@147: if (slave->sii_mailbox_protocols) { fp@325: off += sprintf(buffer + off, "\nMailboxes:\n"); fp@325: off += sprintf(buffer + off, " RX mailbox: 0x%04X/%i," fp@325: " TX mailbox: 0x%04X/%i\n", fp@325: slave->sii_rx_mailbox_offset, fp@325: slave->sii_rx_mailbox_size, fp@325: slave->sii_tx_mailbox_offset, fp@325: slave->sii_tx_mailbox_size); fp@325: off += sprintf(buffer + off, " Supported protocols: "); fp@147: fp@147: first = 1; fp@147: if (slave->sii_mailbox_protocols & EC_MBOX_AOE) { fp@325: off += sprintf(buffer + off, "AoE"); fp@147: first = 0; fp@147: } fp@147: if (slave->sii_mailbox_protocols & EC_MBOX_EOE) { fp@325: if (!first) off += sprintf(buffer + off, ", "); fp@325: off += sprintf(buffer + off, "EoE"); fp@147: first = 0; fp@147: } fp@147: if (slave->sii_mailbox_protocols & EC_MBOX_COE) { fp@325: if (!first) off += sprintf(buffer + off, ", "); fp@325: off += sprintf(buffer + off, "CoE"); fp@147: first = 0; fp@147: } fp@147: if (slave->sii_mailbox_protocols & EC_MBOX_FOE) { fp@325: if (!first) off += sprintf(buffer + off, ", "); fp@325: off += sprintf(buffer + off, "FoE"); fp@147: first = 0; fp@147: } fp@147: if (slave->sii_mailbox_protocols & EC_MBOX_SOE) { fp@325: if (!first) off += sprintf(buffer + off, ", "); fp@325: off += sprintf(buffer + off, "SoE"); fp@147: first = 0; fp@147: } fp@147: if (slave->sii_mailbox_protocols & EC_MBOX_VOE) { fp@325: if (!first) off += sprintf(buffer + off, ", "); fp@325: off += sprintf(buffer + off, "VoE"); fp@325: } fp@325: off += sprintf(buffer + off, "\n"); fp@325: } fp@325: fp@325: if (slave->sii_alias || slave->sii_group fp@325: || slave->sii_image || slave->sii_order) fp@325: off += sprintf(buffer + off, "\nSII data:\n"); fp@266: fp@147: if (slave->sii_alias) fp@325: off += sprintf(buffer + off, " Configured station alias:" fp@325: " 0x%04X (%i)\n", slave->sii_alias, slave->sii_alias); fp@325: if (slave->sii_group) fp@325: off += sprintf(buffer + off, " Group: %s\n", slave->sii_group); fp@325: if (slave->sii_image) fp@325: off += sprintf(buffer + off, " Image: %s\n", slave->sii_image); fp@325: if (slave->sii_order) fp@339: off += sprintf(buffer + off, " Order number: %s\n", slave->sii_order); fp@325: fp@325: if (!list_empty(&slave->sii_syncs)) fp@325: off += sprintf(buffer + off, "\nSync-Managers:\n"); fp@325: fp@325: list_for_each_entry(sync, &slave->sii_syncs, list) { fp@325: off += sprintf(buffer + off, " %i: 0x%04X, length %i," fp@325: " control 0x%02X, %s\n", fp@325: sync->index, sync->physical_start_address, fp@325: sync->length, sync->control_register, fp@325: sync->enable ? "enable" : "disable"); fp@325: } fp@325: fp@325: if (!list_empty(&slave->sii_pdos)) fp@325: off += sprintf(buffer + off, "\nPDOs:\n"); fp@325: fp@325: list_for_each_entry(pdo, &slave->sii_pdos, list) { fp@325: off += sprintf(buffer + off, fp@339: " %s \"%s\" (0x%04X), Sync-Manager %i\n", fp@325: pdo->type == EC_RX_PDO ? "RXPDO" : "TXPDO", fp@325: pdo->name ? pdo->name : "???", fp@325: pdo->index, pdo->sync_index); fp@147: fp@147: list_for_each_entry(pdo_entry, &pdo->entries, list) { fp@339: off += sprintf(buffer + off, " \"%s\" 0x%04X:%X, %i bit\n", fp@325: pdo_entry->name ? pdo_entry->name : "???", fp@325: pdo_entry->index, pdo_entry->subindex, fp@325: pdo_entry->bit_length); fp@325: } fp@325: } fp@325: fp@464: if (!list_empty(&slave->sdo_confs)) fp@464: off += sprintf(buffer + off, "\nSDO configurations:\n"); fp@464: fp@464: list_for_each_entry(sdodata, &slave->sdo_confs, list) { fp@464: switch (sdodata->size) { fp@464: case 1: sprintf(str, "%i", EC_READ_U8(sdodata->data)); break; fp@464: case 2: sprintf(str, "%i", EC_READ_U16(sdodata->data)); break; fp@464: case 4: sprintf(str, "%i", EC_READ_U32(sdodata->data)); break; fp@464: default: sprintf(str, "(invalid size)"); break; fp@464: } fp@464: off += sprintf(buffer + off, " 0x%04X:%-3i -> %s\n", fp@464: sdodata->index, sdodata->subindex, str); fp@464: } fp@464: fp@325: off += sprintf(buffer + off, "\n"); fp@325: fp@325: return off; fp@325: } fp@325: fp@325: /*****************************************************************************/ fp@325: fp@133: /** fp@269: Schedules an EEPROM write operation. fp@269: \return 0 in case of success, else < 0 fp@269: */ fp@269: fp@269: ssize_t ec_slave_write_eeprom(ec_slave_t *slave, /**< EtherCAT slave */ fp@269: const uint8_t *data, /**< new EEPROM data */ fp@269: size_t size /**< size of data in bytes */ fp@269: ) fp@269: { fp@269: uint16_t word_size, cat_type, cat_size; fp@269: const uint16_t *data_words, *next_header; fp@269: uint16_t *new_data; fp@269: fp@306: if (slave->master->mode != EC_MASTER_MODE_IDLE) { fp@306: EC_ERR("Writing EEPROMs only allowed in idle mode!\n"); fp@599: return -EACCES; fp@269: } fp@269: fp@269: if (slave->new_eeprom_data) { fp@269: EC_ERR("Slave %i already has a pending EEPROM write operation!\n", fp@269: slave->ring_position); fp@599: return -EBUSY; fp@269: } fp@269: fp@269: // coarse check of the data fp@269: fp@269: if (size % 2) { fp@269: EC_ERR("EEPROM size is odd! Dropping.\n"); fp@599: return -EINVAL; fp@269: } fp@269: fp@269: data_words = (const uint16_t *) data; fp@269: word_size = size / 2; fp@269: fp@269: if (word_size < 0x0041) { fp@269: EC_ERR("EEPROM data too short! Dropping.\n"); fp@599: return -EINVAL; fp@269: } fp@269: fp@269: next_header = data_words + 0x0040; fp@269: cat_type = EC_READ_U16(next_header); fp@269: while (cat_type != 0xFFFF) { fp@269: cat_type = EC_READ_U16(next_header); fp@269: cat_size = EC_READ_U16(next_header + 1); fp@269: if ((next_header + cat_size + 2) - data_words >= word_size) { fp@269: EC_ERR("EEPROM data seems to be corrupted! Dropping.\n"); fp@599: return -EINVAL; fp@269: } fp@269: next_header += cat_size + 2; fp@269: cat_type = EC_READ_U16(next_header); fp@269: } fp@269: fp@269: // data ok! fp@269: fp@269: if (!(new_data = (uint16_t *) kmalloc(word_size * 2, GFP_KERNEL))) { fp@269: EC_ERR("Unable to allocate memory for new EEPROM data!\n"); fp@599: return -ENOMEM; fp@269: } fp@269: memcpy(new_data, data, size); fp@269: fp@269: slave->new_eeprom_size = word_size; fp@269: slave->new_eeprom_data = new_data; fp@269: fp@269: EC_INFO("EEPROM writing scheduled for slave %i, %i words.\n", fp@269: slave->ring_position, word_size); fp@599: return size; fp@269: } fp@269: fp@269: /*****************************************************************************/ fp@269: fp@269: /** fp@195: Formats attribute data for SysFS read access. fp@195: \return number of bytes to read fp@195: */ fp@195: fp@195: ssize_t ec_show_slave_attribute(struct kobject *kobj, /**< slave's kobject */ fp@195: struct attribute *attr, /**< attribute */ fp@195: char *buffer /**< memory to store data */ fp@182: ) fp@182: { fp@182: ec_slave_t *slave = container_of(kobj, ec_slave_t, kobj); fp@182: fp@325: if (attr == &attr_info) { fp@325: return ec_slave_info(slave, buffer); fp@185: } fp@256: else if (attr == &attr_state) { fp@256: switch (slave->current_state) { fp@256: case EC_SLAVE_STATE_INIT: fp@256: return sprintf(buffer, "INIT\n"); fp@256: case EC_SLAVE_STATE_PREOP: fp@256: return sprintf(buffer, "PREOP\n"); fp@256: case EC_SLAVE_STATE_SAVEOP: fp@256: return sprintf(buffer, "SAVEOP\n"); fp@256: case EC_SLAVE_STATE_OP: fp@256: return sprintf(buffer, "OP\n"); fp@256: default: fp@256: return sprintf(buffer, "UNKNOWN\n"); fp@256: } fp@256: } fp@266: else if (attr == &attr_eeprom) { fp@266: if (slave->eeprom_data) { fp@266: if (slave->eeprom_size > PAGE_SIZE) { fp@266: EC_ERR("EEPROM contents of slave %i exceed 1 page (%i/%i).\n", fp@266: slave->ring_position, slave->eeprom_size, fp@266: (int) PAGE_SIZE); fp@266: } fp@266: else { fp@266: memcpy(buffer, slave->eeprom_data, slave->eeprom_size); fp@266: return slave->eeprom_size; fp@266: } fp@266: } fp@266: } fp@182: fp@182: return 0; fp@182: } fp@182: fp@256: /*****************************************************************************/ fp@256: fp@256: /** fp@256: Formats attribute data for SysFS write access. fp@256: \return number of bytes processed, or negative error code fp@256: */ fp@256: fp@256: ssize_t ec_store_slave_attribute(struct kobject *kobj, /**< slave's kobject */ fp@256: struct attribute *attr, /**< attribute */ fp@256: const char *buffer, /**< memory with data */ fp@256: size_t size /**< size of data to store */ fp@256: ) fp@256: { fp@256: ec_slave_t *slave = container_of(kobj, ec_slave_t, kobj); fp@256: fp@256: if (attr == &attr_state) { fp@403: char state[EC_STATE_STRING_SIZE]; fp@292: if (!strcmp(buffer, "INIT\n")) fp@446: ec_slave_request_state(slave, EC_SLAVE_STATE_INIT); fp@292: else if (!strcmp(buffer, "PREOP\n")) fp@446: ec_slave_request_state(slave, EC_SLAVE_STATE_PREOP); fp@292: else if (!strcmp(buffer, "SAVEOP\n")) fp@446: ec_slave_request_state(slave, EC_SLAVE_STATE_SAVEOP); fp@292: else if (!strcmp(buffer, "OP\n")) fp@446: ec_slave_request_state(slave, EC_SLAVE_STATE_OP); fp@292: else { fp@292: EC_ERR("Invalid slave state \"%s\"!\n", buffer); fp@292: return -EINVAL; fp@292: } fp@292: fp@333: ec_state_string(slave->requested_state, state); fp@292: EC_INFO("Accepted new state %s for slave %i.\n", fp@333: state, slave->ring_position); fp@292: return size; fp@256: } fp@269: else if (attr == &attr_eeprom) { fp@599: return ec_slave_write_eeprom(slave, buffer, size); fp@269: } fp@256: fp@256: return -EINVAL; fp@256: } fp@256: fp@275: /*****************************************************************************/ fp@275: fp@275: /** fp@298: Calculates the size of a sync manager by evaluating PDO sizes. fp@298: \return sync manager size fp@298: */ fp@298: fp@325: uint16_t ec_slave_calc_sync_size(const ec_slave_t *slave, fp@325: /**< EtherCAT slave */ fp@325: const ec_sii_sync_t *sync fp@325: /**< sync manager */ fp@325: ) fp@325: { fp@325: ec_sii_pdo_t *pdo; fp@325: ec_sii_pdo_entry_t *pdo_entry; fp@416: unsigned int bit_size, byte_size; fp@298: fp@298: if (sync->length) return sync->length; fp@416: if (sync->est_length) return sync->est_length; fp@298: fp@320: bit_size = 0; fp@325: list_for_each_entry(pdo, &slave->sii_pdos, list) { fp@325: if (pdo->sync_index != sync->index) continue; fp@298: fp@298: list_for_each_entry(pdo_entry, &pdo->entries, list) { fp@320: bit_size += pdo_entry->bit_length; fp@320: } fp@320: } fp@320: fp@320: if (bit_size % 8) // round up to full bytes fp@416: byte_size = bit_size / 8 + 1; fp@320: else fp@416: byte_size = bit_size / 8; fp@416: fp@416: return byte_size; fp@298: } fp@298: fp@325: /*****************************************************************************/ fp@325: fp@325: /** fp@556: Initializes a sync manager configuration page with EEPROM data. fp@556: The referenced memory (\a data) must be at least EC_SYNC_SIZE bytes. fp@556: */ fp@556: fp@556: void ec_slave_sync_config(const ec_slave_t *slave, /**< EtherCAT slave */ fp@556: const ec_sii_sync_t *sync, /**< sync manager */ fp@556: uint8_t *data /**> configuration memory */ fp@556: ) fp@556: { fp@556: size_t sync_size; fp@556: fp@556: sync_size = ec_slave_calc_sync_size(slave, sync); fp@556: fp@556: if (slave->master->debug_level) { fp@556: EC_DBG("Slave %3i, SM %i: Addr 0x%04X, Size %3i, Ctrl 0x%02X, En %i\n", fp@556: slave->ring_position, sync->index, sync->physical_start_address, fp@556: sync_size, sync->control_register, sync->enable); fp@556: } fp@556: fp@556: EC_WRITE_U16(data, sync->physical_start_address); fp@556: EC_WRITE_U16(data + 2, sync_size); fp@556: EC_WRITE_U8 (data + 4, sync->control_register); fp@556: EC_WRITE_U8 (data + 5, 0x00); // status byte (read only) fp@556: EC_WRITE_U16(data + 6, sync->enable ? 0x0001 : 0x0000); // enable fp@556: } fp@556: fp@556: /*****************************************************************************/ fp@556: fp@556: /** fp@556: Initializes an FMMU configuration page. fp@556: The referenced memory (\a data) must be at least EC_FMMU_SIZE bytes. fp@556: */ fp@556: fp@556: void ec_slave_fmmu_config(const ec_slave_t *slave, /**< EtherCAT slave */ fp@556: const ec_fmmu_t *fmmu, /**< FMMU */ fp@556: uint8_t *data /**> configuration memory */ fp@556: ) fp@556: { fp@556: size_t sync_size; fp@556: fp@556: sync_size = ec_slave_calc_sync_size(slave, fmmu->sync); fp@556: fp@556: if (slave->master->debug_level) { fp@556: EC_DBG("Slave %3i, FMMU %2i:" fp@556: " LogAddr 0x%08X, Size %3i, PhysAddr 0x%04X, Dir %s\n", fp@556: slave->ring_position, fmmu->index, fmmu->logical_start_address, fp@556: sync_size, fmmu->sync->physical_start_address, fp@556: ((fmmu->sync->control_register & 0x04) ? "out" : "in")); fp@556: } fp@556: fp@556: EC_WRITE_U32(data, fmmu->logical_start_address); fp@556: EC_WRITE_U16(data + 4, sync_size); // size of fmmu fp@556: EC_WRITE_U8 (data + 6, 0x00); // logical start bit fp@556: EC_WRITE_U8 (data + 7, 0x07); // logical end bit fp@556: EC_WRITE_U16(data + 8, fmmu->sync->physical_start_address); fp@556: EC_WRITE_U8 (data + 10, 0x00); // physical start bit fp@556: EC_WRITE_U8 (data + 11, ((fmmu->sync->control_register & 0x04) fp@556: ? 0x02 : 0x01)); fp@556: EC_WRITE_U16(data + 12, 0x0001); // enable fp@556: EC_WRITE_U16(data + 14, 0x0000); // reserved fp@556: } fp@556: fp@556: /*****************************************************************************/ fp@556: fp@556: /** fp@325: \return non-zero if slave is a bus coupler fp@325: */ fp@325: fp@343: int ec_slave_is_coupler(const ec_slave_t *slave /**< EtherCAT slave */) fp@325: { fp@325: // TODO: Better bus coupler criterion fp@325: return slave->sii_vendor_id == 0x00000002 fp@325: && slave->sii_product_code == 0x044C2C52; fp@325: } fp@325: fp@329: /*****************************************************************************/ fp@329: fp@329: /** fp@394: \return non-zero if slave is a bus coupler fp@394: */ fp@394: fp@394: int ec_slave_has_subbus(const ec_slave_t *slave /**< EtherCAT slave */) fp@394: { fp@394: return slave->sii_vendor_id == 0x00000002 fp@400: && slave->sii_product_code == 0x04602c22; fp@394: } fp@394: fp@394: /*****************************************************************************/ fp@394: fp@394: /** fp@329: \return 0 in case of success, else < 0 fp@329: */ fp@329: fp@329: int ec_slave_conf_sdo(ec_slave_t *slave, /**< EtherCAT slave */ fp@329: uint16_t sdo_index, /**< SDO index */ fp@329: uint8_t sdo_subindex, /**< SDO subindex */ fp@329: const uint8_t *data, /**< SDO data */ fp@329: size_t size /**< SDO size in bytes */ fp@329: ) fp@329: { fp@329: ec_sdo_data_t *sdodata; fp@329: fp@329: if (!(slave->sii_mailbox_protocols & EC_MBOX_COE)) { fp@329: EC_ERR("Slave %i does not support CoE!\n", slave->ring_position); fp@329: return -1; fp@329: } fp@329: fp@329: if (!(sdodata = (ec_sdo_data_t *) fp@329: kmalloc(sizeof(ec_sdo_data_t), GFP_KERNEL))) { fp@329: EC_ERR("Failed to allocate memory for SDO configuration object!\n"); fp@329: return -1; fp@329: } fp@329: fp@329: if (!(sdodata->data = (uint8_t *) kmalloc(size, GFP_KERNEL))) { fp@329: EC_ERR("Failed to allocate memory for SDO configuration data!\n"); fp@329: kfree(sdodata); fp@329: return -1; fp@329: } fp@329: fp@329: sdodata->index = sdo_index; fp@329: sdodata->subindex = sdo_subindex; fp@329: memcpy(sdodata->data, data, size); fp@329: sdodata->size = size; fp@329: fp@329: list_add_tail(&sdodata->list, &slave->sdo_confs); fp@329: return 0; fp@329: } fp@329: fp@410: /*****************************************************************************/ fp@410: fp@410: /** fp@410: \return 0 in case of success, else < 0 fp@410: */ fp@410: fp@410: int ec_slave_validate(const ec_slave_t *slave, /**< EtherCAT slave */ fp@410: uint32_t vendor_id, /**< vendor ID */ fp@410: uint32_t product_code /**< product code */ fp@410: ) fp@410: { fp@410: if (vendor_id != slave->sii_vendor_id || fp@410: product_code != slave->sii_product_code) { fp@410: EC_ERR("Invalid slave type at position %i - Requested: 0x%08X 0x%08X," fp@410: " found: 0x%08X 0x%08X\".\n", slave->ring_position, vendor_id, fp@410: product_code, slave->sii_vendor_id, slave->sii_product_code); fp@410: return -1; fp@410: } fp@410: return 0; fp@410: } fp@410: fp@423: /*****************************************************************************/ fp@423: fp@423: /** fp@423: Counts the total number of SDOs and entries in the dictionary. fp@423: */ fp@423: fp@423: void ec_slave_sdo_dict_info(const ec_slave_t *slave, /**< EtherCAT slave */ fp@423: unsigned int *sdo_count, /**< number of SDOs */ fp@423: unsigned int *entry_count /**< total number of fp@423: entries */ fp@423: ) fp@423: { fp@423: unsigned int sdos = 0, entries = 0; fp@423: ec_sdo_t *sdo; fp@423: ec_sdo_entry_t *entry; fp@423: fp@423: list_for_each_entry(sdo, &slave->sdo_dictionary, list) { fp@423: sdos++; fp@423: list_for_each_entry(entry, &sdo->entries, list) { fp@423: entries++; fp@423: } fp@423: } fp@423: fp@423: *sdo_count = sdos; fp@423: *entry_count = entries; fp@423: } fp@423: fp@199: /****************************************************************************** fp@199: * Realtime interface fp@199: *****************************************************************************/ fp@199: fp@199: /** fp@199: \return 0 in case of success, else < 0 fp@199: \ingroup RealtimeInterface fp@199: */ fp@199: fp@329: int ecrt_slave_conf_sdo8(ec_slave_t *slave, /**< EtherCAT slave */ fp@329: uint16_t sdo_index, /**< SDO index */ fp@329: uint8_t sdo_subindex, /**< SDO subindex */ fp@329: uint8_t value /**< new SDO value */ fp@329: ) fp@329: { fp@329: uint8_t data[1]; fp@329: EC_WRITE_U8(data, value); fp@329: return ec_slave_conf_sdo(slave, sdo_index, sdo_subindex, data, 1); fp@329: } fp@329: fp@329: /*****************************************************************************/ fp@329: fp@329: /** fp@329: \return 0 in case of success, else < 0 fp@329: \ingroup RealtimeInterface fp@329: */ fp@329: fp@329: int ecrt_slave_conf_sdo16(ec_slave_t *slave, /**< EtherCAT slave */ fp@329: uint16_t sdo_index, /**< SDO index */ fp@329: uint8_t sdo_subindex, /**< SDO subindex */ fp@329: uint16_t value /**< new SDO value */ fp@329: ) fp@329: { fp@329: uint8_t data[2]; fp@329: EC_WRITE_U16(data, value); fp@329: return ec_slave_conf_sdo(slave, sdo_index, sdo_subindex, data, 2); fp@329: } fp@329: fp@329: /*****************************************************************************/ fp@329: fp@329: /** fp@329: \return 0 in case of success, else < 0 fp@329: \ingroup RealtimeInterface fp@329: */ fp@329: fp@329: int ecrt_slave_conf_sdo32(ec_slave_t *slave, /**< EtherCAT slave */ fp@329: uint16_t sdo_index, /**< SDO index */ fp@329: uint8_t sdo_subindex, /**< SDO subindex */ fp@329: uint32_t value /**< new SDO value */ fp@329: ) fp@329: { fp@329: uint8_t data[4]; fp@329: EC_WRITE_U32(data, value); fp@329: return ec_slave_conf_sdo(slave, sdo_index, sdo_subindex, data, 4); fp@329: } fp@329: fp@329: /*****************************************************************************/ fp@329: fp@526: /** \cond */ fp@164: fp@329: EXPORT_SYMBOL(ecrt_slave_conf_sdo8); fp@329: EXPORT_SYMBOL(ecrt_slave_conf_sdo16); fp@329: EXPORT_SYMBOL(ecrt_slave_conf_sdo32); fp@138: fp@526: /** \endcond */ fp@526: fp@526: /*****************************************************************************/