/****************************************************************************** * * $Id$ * * Copyright (C) 2006-2012 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 version 2, as * published by the Free Software Foundation. * * 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 license mentioned above concerns the source code only. Using the * EtherCAT technology and brand is only permitted in compliance with the * industrial property and similar rights of Beckhoff Automation GmbH. * *****************************************************************************/ /** \file EtherCAT master character device. */ /*****************************************************************************/ #include #include #include "master.h" #include "slave_config.h" #include "voe_handler.h" #include "ethernet.h" #include "ioctl.h" /** Set to 1 to enable ioctl() latency tracing. * * Requires CPU timestamp counter! */ #define DEBUG_LATENCY 0 /** Optional compiler attributes fo ioctl() functions. */ #if 0 #define ATTRIBUTES __attribute__ ((__noinline__)) #else #define ATTRIBUTES #endif /*****************************************************************************/ /** Copies a string to an ioctl structure. */ static void ec_ioctl_strcpy( char *target, /**< Target. */ const char *source /**< Source. */ ) { if (source) { strncpy(target, source, EC_IOCTL_STRING_SIZE); target[EC_IOCTL_STRING_SIZE - 1] = 0; } else { target[0] = 0; } } /*****************************************************************************/ /** Get module information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_module( void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_module_t data; data.ioctl_version_magic = EC_IOCTL_VERSION_MAGIC; data.master_count = ec_master_count(); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get master information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_master( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_master_t io; unsigned int dev_idx, j; if (down_interruptible(&master->master_sem)) { return -EINTR; } io.slave_count = master->slave_count; io.config_count = ec_master_config_count(master); io.domain_count = ec_master_domain_count(master); #ifdef EC_EOE io.eoe_handler_count = ec_master_eoe_handler_count(master); #endif io.phase = (uint8_t) master->phase; io.active = (uint8_t) master->active; io.scan_busy = master->scan_busy; up(&master->master_sem); if (down_interruptible(&master->device_sem)) { return -EINTR; } for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master); dev_idx++) { ec_device_t *device = &master->devices[dev_idx]; if (device->dev) { memcpy(io.devices[dev_idx].address, device->dev->dev_addr, ETH_ALEN); } else { memcpy(io.devices[dev_idx].address, master->macs[dev_idx], ETH_ALEN); } io.devices[dev_idx].attached = device->dev ? 1 : 0; io.devices[dev_idx].link_state = device->link_state ? 1 : 0; io.devices[dev_idx].tx_count = device->tx_count; io.devices[dev_idx].rx_count = device->rx_count; io.devices[dev_idx].tx_bytes = device->tx_bytes; io.devices[dev_idx].rx_bytes = device->rx_bytes; io.devices[dev_idx].tx_errors = device->tx_errors; for (j = 0; j < EC_RATE_COUNT; j++) { io.devices[dev_idx].tx_frame_rates[j] = device->tx_frame_rates[j]; io.devices[dev_idx].rx_frame_rates[j] = device->rx_frame_rates[j]; io.devices[dev_idx].tx_byte_rates[j] = device->tx_byte_rates[j]; io.devices[dev_idx].rx_byte_rates[j] = device->rx_byte_rates[j]; } } io.num_devices = ec_master_num_devices(master); io.tx_count = master->device_stats.tx_count; io.rx_count = master->device_stats.rx_count; io.tx_bytes = master->device_stats.tx_bytes; io.rx_bytes = master->device_stats.rx_bytes; for (j = 0; j < EC_RATE_COUNT; j++) { io.tx_frame_rates[j] = master->device_stats.tx_frame_rates[j]; io.rx_frame_rates[j] = master->device_stats.rx_frame_rates[j]; io.tx_byte_rates[j] = master->device_stats.tx_byte_rates[j]; io.rx_byte_rates[j] = master->device_stats.rx_byte_rates[j]; io.loss_rates[j] = master->device_stats.loss_rates[j]; } up(&master->device_sem); io.app_time = master->app_time; io.dc_ref_time = master->dc_ref_time; io.ref_clock = master->dc_ref_clock ? master->dc_ref_clock->ring_position : 0xffff; if (copy_to_user((void __user *) arg, &io, sizeof(io))) { return -EFAULT; } return 0; } /*****************************************************************************/ /** Get slave information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_slave_t data; const ec_slave_t *slave; int i; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(slave = ec_master_find_slave_const( master, 0, data.position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.position); return -EINVAL; } data.device_index = slave->device_index; data.vendor_id = slave->sii.vendor_id; data.product_code = slave->sii.product_code; data.revision_number = slave->sii.revision_number; data.serial_number = slave->sii.serial_number; data.alias = slave->effective_alias; data.boot_rx_mailbox_offset = slave->sii.boot_rx_mailbox_offset; data.boot_rx_mailbox_size = slave->sii.boot_rx_mailbox_size; data.boot_tx_mailbox_offset = slave->sii.boot_tx_mailbox_offset; data.boot_tx_mailbox_size = slave->sii.boot_tx_mailbox_size; data.std_rx_mailbox_offset = slave->sii.std_rx_mailbox_offset; data.std_rx_mailbox_size = slave->sii.std_rx_mailbox_size; data.std_tx_mailbox_offset = slave->sii.std_tx_mailbox_offset; data.std_tx_mailbox_size = slave->sii.std_tx_mailbox_size; data.mailbox_protocols = slave->sii.mailbox_protocols; data.has_general_category = slave->sii.has_general; data.coe_details = slave->sii.coe_details; data.general_flags = slave->sii.general_flags; data.current_on_ebus = slave->sii.current_on_ebus; for (i = 0; i < EC_MAX_PORTS; i++) { data.ports[i].desc = slave->ports[i].desc; data.ports[i].link.link_up = slave->ports[i].link.link_up; data.ports[i].link.loop_closed = slave->ports[i].link.loop_closed; data.ports[i].link.signal_detected = slave->ports[i].link.signal_detected; data.ports[i].receive_time = slave->ports[i].receive_time; if (slave->ports[i].next_slave) { data.ports[i].next_slave = slave->ports[i].next_slave->ring_position; } else { data.ports[i].next_slave = 0xffff; } data.ports[i].delay_to_next_dc = slave->ports[i].delay_to_next_dc; } data.fmmu_bit = slave->base_fmmu_bit_operation; data.dc_supported = slave->base_dc_supported; data.dc_range = slave->base_dc_range; data.has_dc_system_time = slave->has_dc_system_time; data.transmission_delay = slave->transmission_delay; data.al_state = slave->current_state; data.error_flag = slave->error_flag; data.sync_count = slave->sii.sync_count; data.sdo_count = ec_slave_sdo_count(slave); data.sii_nwords = slave->sii_nwords; ec_ioctl_strcpy(data.group, slave->sii.group); ec_ioctl_strcpy(data.image, slave->sii.image); ec_ioctl_strcpy(data.order, slave->sii.order); ec_ioctl_strcpy(data.name, slave->sii.name); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get slave sync manager information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sync( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_slave_sync_t data; const ec_slave_t *slave; const ec_sync_t *sync; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(slave = ec_master_find_slave_const( master, 0, data.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.slave_position); return -EINVAL; } if (data.sync_index >= slave->sii.sync_count) { up(&master->master_sem); EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", data.sync_index); return -EINVAL; } sync = &slave->sii.syncs[data.sync_index]; data.physical_start_address = sync->physical_start_address; data.default_size = sync->default_length; data.control_register = sync->control_register; data.enable = sync->enable; data.pdo_count = ec_pdo_list_count(&sync->pdos); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get slave sync manager PDO information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sync_pdo( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_slave_sync_pdo_t data; const ec_slave_t *slave; const ec_sync_t *sync; const ec_pdo_t *pdo; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(slave = ec_master_find_slave_const( master, 0, data.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.slave_position); return -EINVAL; } if (data.sync_index >= slave->sii.sync_count) { up(&master->master_sem); EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", data.sync_index); return -EINVAL; } sync = &slave->sii.syncs[data.sync_index]; if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( &sync->pdos, data.pdo_pos))) { up(&master->master_sem); EC_SLAVE_ERR(slave, "Sync manager %u does not contain a PDO with " "position %u!\n", data.sync_index, data.pdo_pos); return -EINVAL; } data.index = pdo->index; data.entry_count = ec_pdo_entry_count(pdo); ec_ioctl_strcpy(data.name, pdo->name); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get slave sync manager PDO entry information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sync_pdo_entry( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_slave_sync_pdo_entry_t data; const ec_slave_t *slave; const ec_sync_t *sync; const ec_pdo_t *pdo; const ec_pdo_entry_t *entry; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(slave = ec_master_find_slave_const( master, 0, data.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.slave_position); return -EINVAL; } if (data.sync_index >= slave->sii.sync_count) { up(&master->master_sem); EC_SLAVE_ERR(slave, "Sync manager %u does not exist!\n", data.sync_index); return -EINVAL; } sync = &slave->sii.syncs[data.sync_index]; if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( &sync->pdos, data.pdo_pos))) { up(&master->master_sem); EC_SLAVE_ERR(slave, "Sync manager %u does not contain a PDO with " "position %u!\n", data.sync_index, data.pdo_pos); return -EINVAL; } if (!(entry = ec_pdo_find_entry_by_pos_const( pdo, data.entry_pos))) { up(&master->master_sem); EC_SLAVE_ERR(slave, "PDO 0x%04X does not contain an entry with " "position %u!\n", data.pdo_pos, data.entry_pos); return -EINVAL; } data.index = entry->index; data.subindex = entry->subindex; data.bit_length = entry->bit_length; ec_ioctl_strcpy(data.name, entry->name); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get domain information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_domain( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_domain_t data; const ec_domain_t *domain; unsigned int dev_idx; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(domain = ec_master_find_domain_const(master, data.index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Domain %u does not exist!\n", data.index); return -EINVAL; } data.data_size = domain->data_size; data.logical_base_address = domain->logical_base_address; for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(domain->master); dev_idx++) { data.working_counter[dev_idx] = domain->working_counter[dev_idx]; } data.expected_working_counter = domain->expected_working_counter; data.fmmu_count = ec_domain_fmmu_count(domain); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get domain FMMU information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_domain_fmmu( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_domain_fmmu_t data; const ec_domain_t *domain; const ec_fmmu_config_t *fmmu; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Domain %u does not exist!\n", data.domain_index); return -EINVAL; } if (!(fmmu = ec_domain_find_fmmu(domain, data.fmmu_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Domain %u has less than %u" " fmmu configurations.\n", data.domain_index, data.fmmu_index + 1); return -EINVAL; } data.slave_config_alias = fmmu->sc->alias; data.slave_config_position = fmmu->sc->position; data.sync_index = fmmu->sync_index; data.dir = fmmu->dir; data.logical_address = fmmu->logical_start_address; data.data_size = fmmu->data_size; up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get domain data. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_domain_data( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< Userspace address to store the results. */ ) { ec_ioctl_domain_data_t data; const ec_domain_t *domain; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Domain %u does not exist!\n", data.domain_index); return -EINVAL; } if (domain->data_size != data.data_size) { up(&master->master_sem); EC_MASTER_ERR(master, "Data size mismatch %u/%zu!\n", data.data_size, domain->data_size); return -EFAULT; } if (copy_to_user((void __user *) data.target, domain->data, domain->data_size)) { up(&master->master_sem); return -EFAULT; } up(&master->master_sem); return 0; } /*****************************************************************************/ /** Set master debug level. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_master_debug( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { return ec_master_debug_level(master, (unsigned long) arg); } /*****************************************************************************/ /** Issue a bus scan. * * \return Always zero (success). */ static ATTRIBUTES int ec_ioctl_master_rescan( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { master->fsm.rescan_required = 1; return 0; } /*****************************************************************************/ /** Set slave state. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_state( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_state_t data; ec_slave_t *slave; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(slave = ec_master_find_slave( master, 0, data.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.slave_position); return -EINVAL; } ec_slave_request_state(slave, data.al_state); up(&master->master_sem); return 0; } /*****************************************************************************/ /** Get slave SDO information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sdo( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_sdo_t data; const ec_slave_t *slave; const ec_sdo_t *sdo; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(slave = ec_master_find_slave_const( master, 0, data.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.slave_position); return -EINVAL; } if (!(sdo = ec_slave_get_sdo_by_pos_const( slave, data.sdo_position))) { up(&master->master_sem); EC_SLAVE_ERR(slave, "SDO %u does not exist!\n", data.sdo_position); return -EINVAL; } data.sdo_index = sdo->index; data.max_subindex = sdo->max_subindex; ec_ioctl_strcpy(data.name, sdo->name); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get slave SDO entry information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sdo_entry( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_sdo_entry_t data; const ec_slave_t *slave; const ec_sdo_t *sdo; const ec_sdo_entry_t *entry; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(slave = ec_master_find_slave_const( master, 0, data.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.slave_position); return -EINVAL; } if (data.sdo_spec <= 0) { if (!(sdo = ec_slave_get_sdo_by_pos_const( slave, -data.sdo_spec))) { up(&master->master_sem); EC_SLAVE_ERR(slave, "SDO %u does not exist!\n", -data.sdo_spec); return -EINVAL; } } else { if (!(sdo = ec_slave_get_sdo_const( slave, data.sdo_spec))) { up(&master->master_sem); EC_SLAVE_ERR(slave, "SDO 0x%04X does not exist!\n", data.sdo_spec); return -EINVAL; } } if (!(entry = ec_sdo_get_entry_const( sdo, data.sdo_entry_subindex))) { up(&master->master_sem); EC_SLAVE_ERR(slave, "SDO entry 0x%04X:%02X does not exist!\n", sdo->index, data.sdo_entry_subindex); return -EINVAL; } data.data_type = entry->data_type; data.bit_length = entry->bit_length; data.read_access[EC_SDO_ENTRY_ACCESS_PREOP] = entry->read_access[EC_SDO_ENTRY_ACCESS_PREOP]; data.read_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = entry->read_access[EC_SDO_ENTRY_ACCESS_SAFEOP]; data.read_access[EC_SDO_ENTRY_ACCESS_OP] = entry->read_access[EC_SDO_ENTRY_ACCESS_OP]; data.write_access[EC_SDO_ENTRY_ACCESS_PREOP] = entry->write_access[EC_SDO_ENTRY_ACCESS_PREOP]; data.write_access[EC_SDO_ENTRY_ACCESS_SAFEOP] = entry->write_access[EC_SDO_ENTRY_ACCESS_SAFEOP]; data.write_access[EC_SDO_ENTRY_ACCESS_OP] = entry->write_access[EC_SDO_ENTRY_ACCESS_OP]; ec_ioctl_strcpy(data.description, entry->description); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Upload SDO. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sdo_upload( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_sdo_upload_t data; uint8_t *target; int ret; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (!(target = kmalloc(data.target_size, GFP_KERNEL))) { EC_MASTER_ERR(master, "Failed to allocate %zu bytes" " for SDO upload.\n", data.target_size); return -ENOMEM; } ret = ecrt_master_sdo_upload(master, data.slave_position, data.sdo_index, data.sdo_entry_subindex, target, data.target_size, &data.data_size, &data.abort_code); if (!ret) { if (copy_to_user((void __user *) data.target, target, data.data_size)) { kfree(target); return -EFAULT; } } kfree(target); if (__copy_to_user((void __user *) arg, &data, sizeof(data))) { return -EFAULT; } return ret; } /*****************************************************************************/ /** Download SDO. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sdo_download( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_sdo_download_t data; uint8_t *sdo_data; int retval; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (!(sdo_data = kmalloc(data.data_size, GFP_KERNEL))) { EC_MASTER_ERR(master, "Failed to allocate %zu bytes" " for SDO download.\n", data.data_size); return -ENOMEM; } if (copy_from_user(sdo_data, (void __user *) data.data, data.data_size)) { kfree(sdo_data); return -EFAULT; } if (data.complete_access) { retval = ecrt_master_sdo_download_complete(master, data.slave_position, data.sdo_index, sdo_data, data.data_size, &data.abort_code); } else { retval = ecrt_master_sdo_download(master, data.slave_position, data.sdo_index, data.sdo_entry_subindex, sdo_data, data.data_size, &data.abort_code); } kfree(sdo_data); if (__copy_to_user((void __user *) arg, &data, sizeof(data))) { retval = -EFAULT; } return retval; } /*****************************************************************************/ /** Read a slave's SII. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sii_read( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_sii_t data; const ec_slave_t *slave; int retval; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(slave = ec_master_find_slave_const( master, 0, data.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.slave_position); return -EINVAL; } if (!data.nwords || data.offset + data.nwords > slave->sii_nwords) { up(&master->master_sem); EC_SLAVE_ERR(slave, "Invalid SII read offset/size %u/%u for slave SII" " size %zu!\n", data.offset, data.nwords, slave->sii_nwords); return -EINVAL; } if (copy_to_user((void __user *) data.words, slave->sii_words + data.offset, data.nwords * 2)) retval = -EFAULT; else retval = 0; up(&master->master_sem); return retval; } /*****************************************************************************/ /** Write a slave's SII. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_sii_write( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_sii_t data; ec_slave_t *slave; unsigned int byte_size; uint16_t *words; ec_sii_write_request_t request; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (!data.nwords) { return 0; } byte_size = sizeof(uint16_t) * data.nwords; if (!(words = kmalloc(byte_size, GFP_KERNEL))) { EC_MASTER_ERR(master, "Failed to allocate %u bytes" " for SII contents.\n", byte_size); return -ENOMEM; } if (copy_from_user(words, (void __user *) data.words, byte_size)) { kfree(words); return -EFAULT; } if (down_interruptible(&master->master_sem)) { kfree(words); return -EINTR; } if (!(slave = ec_master_find_slave( master, 0, data.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", data.slave_position); kfree(words); return -EINVAL; } // init SII write request INIT_LIST_HEAD(&request.list); request.slave = slave; request.words = words; request.offset = data.offset; request.nwords = data.nwords; request.state = EC_INT_REQUEST_QUEUED; // schedule SII write request. list_add_tail(&request.list, &master->sii_requests); up(&master->master_sem); // wait for processing through FSM if (wait_event_interruptible(master->request_queue, request.state != EC_INT_REQUEST_QUEUED)) { // interrupted by signal down(&master->master_sem); if (request.state == EC_INT_REQUEST_QUEUED) { // abort request list_del(&request.list); up(&master->master_sem); kfree(words); return -EINTR; } up(&master->master_sem); } // wait until master FSM has finished processing wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); kfree(words); return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; } /*****************************************************************************/ /** Read a slave's registers. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_reg_read( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_reg_t io; ec_slave_t *slave; ec_reg_request_t request; int ret; if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } if (!io.size) { return 0; } // init register request ret = ec_reg_request_init(&request, io.size); if (ret) { return ret; } ecrt_reg_request_read(&request, io.address, io.size); if (down_interruptible(&master->master_sem)) { ec_reg_request_clear(&request); return -EINTR; } if (!(slave = ec_master_find_slave( master, 0, io.slave_position))) { up(&master->master_sem); ec_reg_request_clear(&request); EC_MASTER_ERR(master, "Slave %u does not exist!\n", io.slave_position); return -EINVAL; } // schedule request. list_add_tail(&request.list, &slave->reg_requests); up(&master->master_sem); // wait for processing through FSM if (wait_event_interruptible(master->request_queue, request.state != EC_INT_REQUEST_QUEUED)) { // interrupted by signal down(&master->master_sem); if (request.state == EC_INT_REQUEST_QUEUED) { // abort request list_del(&request.list); up(&master->master_sem); ec_reg_request_clear(&request); return -EINTR; } up(&master->master_sem); } // wait until master FSM has finished processing wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); if (request.state == EC_INT_REQUEST_SUCCESS) { if (copy_to_user((void __user *) io.data, request.data, io.size)) { return -EFAULT; } } ec_reg_request_clear(&request); return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; } /*****************************************************************************/ /** Write a slave's registers. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_reg_write( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_reg_t io; ec_slave_t *slave; ec_reg_request_t request; int ret; if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } if (!io.size) { return 0; } // init register request ret = ec_reg_request_init(&request, io.size); if (ret) { return ret; } if (copy_from_user(request.data, (void __user *) io.data, io.size)) { ec_reg_request_clear(&request); return -EFAULT; } ecrt_reg_request_write(&request, io.address, io.size); if (down_interruptible(&master->master_sem)) { ec_reg_request_clear(&request); return -EINTR; } if (io.emergency) { request.ring_position = io.slave_position; // schedule request. list_add_tail(&request.list, &master->emerg_reg_requests); } else { if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { up(&master->master_sem); ec_reg_request_clear(&request); EC_MASTER_ERR(master, "Slave %u does not exist!\n", io.slave_position); return -EINVAL; } // schedule request. list_add_tail(&request.list, &slave->reg_requests); } up(&master->master_sem); // wait for processing through FSM if (wait_event_interruptible(master->request_queue, request.state != EC_INT_REQUEST_QUEUED)) { // interrupted by signal down(&master->master_sem); if (request.state == EC_INT_REQUEST_QUEUED) { // abort request list_del(&request.list); up(&master->master_sem); ec_reg_request_clear(&request); return -EINTR; } up(&master->master_sem); } // wait until master FSM has finished processing wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); ec_reg_request_clear(&request); return request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; } /*****************************************************************************/ /** Get slave configuration information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_config( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_config_t data; const ec_slave_config_t *sc; uint8_t i; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config_const( master, data.config_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave config %u does not exist!\n", data.config_index); return -EINVAL; } data.alias = sc->alias; data.position = sc->position; data.vendor_id = sc->vendor_id; data.product_code = sc->product_code; for (i = 0; i < EC_MAX_SYNC_MANAGERS; i++) { data.syncs[i].dir = sc->sync_configs[i].dir; data.syncs[i].watchdog_mode = sc->sync_configs[i].watchdog_mode; data.syncs[i].pdo_count = ec_pdo_list_count(&sc->sync_configs[i].pdos); } data.watchdog_divider = sc->watchdog_divider; data.watchdog_intervals = sc->watchdog_intervals; data.sdo_count = ec_slave_config_sdo_count(sc); data.idn_count = ec_slave_config_idn_count(sc); data.slave_position = sc->slave ? sc->slave->ring_position : -1; data.dc_assign_activate = sc->dc_assign_activate; for (i = 0; i < EC_SYNC_SIGNAL_COUNT; i++) { data.dc_sync[i] = sc->dc_sync[i]; } up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get slave configuration PDO information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_config_pdo( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_config_pdo_t data; const ec_slave_config_t *sc; const ec_pdo_t *pdo; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (data.sync_index >= EC_MAX_SYNC_MANAGERS) { EC_MASTER_ERR(master, "Invalid sync manager index %u!\n", data.sync_index); return -EINVAL; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config_const( master, data.config_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave config %u does not exist!\n", data.config_index); return -EINVAL; } if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( &sc->sync_configs[data.sync_index].pdos, data.pdo_pos))) { up(&master->master_sem); EC_MASTER_ERR(master, "Invalid PDO position!\n"); return -EINVAL; } data.index = pdo->index; data.entry_count = ec_pdo_entry_count(pdo); ec_ioctl_strcpy(data.name, pdo->name); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get slave configuration PDO entry information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_config_pdo_entry( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_config_pdo_entry_t data; const ec_slave_config_t *sc; const ec_pdo_t *pdo; const ec_pdo_entry_t *entry; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (data.sync_index >= EC_MAX_SYNC_MANAGERS) { EC_MASTER_ERR(master, "Invalid sync manager index %u!\n", data.sync_index); return -EINVAL; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config_const( master, data.config_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave config %u does not exist!\n", data.config_index); return -EINVAL; } if (!(pdo = ec_pdo_list_find_pdo_by_pos_const( &sc->sync_configs[data.sync_index].pdos, data.pdo_pos))) { up(&master->master_sem); EC_MASTER_ERR(master, "Invalid PDO position!\n"); return -EINVAL; } if (!(entry = ec_pdo_find_entry_by_pos_const( pdo, data.entry_pos))) { up(&master->master_sem); EC_MASTER_ERR(master, "Entry not found!\n"); return -EINVAL; } data.index = entry->index; data.subindex = entry->subindex; data.bit_length = entry->bit_length; ec_ioctl_strcpy(data.name, entry->name); up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get slave configuration SDO information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_config_sdo( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_config_sdo_t *ioctl; const ec_slave_config_t *sc; const ec_sdo_request_t *req; if (!(ioctl = kmalloc(sizeof(*ioctl), GFP_KERNEL))) { return -ENOMEM; } if (copy_from_user(ioctl, (void __user *) arg, sizeof(*ioctl))) { kfree(ioctl); return -EFAULT; } if (down_interruptible(&master->master_sem)) { kfree(ioctl); return -EINTR; } if (!(sc = ec_master_get_config_const( master, ioctl->config_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave config %u does not exist!\n", ioctl->config_index); kfree(ioctl); return -EINVAL; } if (!(req = ec_slave_config_get_sdo_by_pos_const( sc, ioctl->sdo_pos))) { up(&master->master_sem); EC_MASTER_ERR(master, "Invalid SDO position!\n"); kfree(ioctl); return -EINVAL; } ioctl->index = req->index; ioctl->subindex = req->subindex; ioctl->size = req->data_size; memcpy(ioctl->data, req->data, min((u32) ioctl->size, (u32) EC_MAX_SDO_DATA_SIZE)); ioctl->complete_access = req->complete_access; up(&master->master_sem); if (copy_to_user((void __user *) arg, ioctl, sizeof(*ioctl))) { kfree(ioctl); return -EFAULT; } kfree(ioctl); return 0; } /*****************************************************************************/ /** Get slave configuration IDN information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_config_idn( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_config_idn_t *ioctl; const ec_slave_config_t *sc; const ec_soe_request_t *req; if (!(ioctl = kmalloc(sizeof(*ioctl), GFP_KERNEL))) { return -ENOMEM; } if (copy_from_user(ioctl, (void __user *) arg, sizeof(*ioctl))) { kfree(ioctl); return -EFAULT; } if (down_interruptible(&master->master_sem)) { kfree(ioctl); return -EINTR; } if (!(sc = ec_master_get_config_const( master, ioctl->config_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave config %u does not exist!\n", ioctl->config_index); kfree(ioctl); return -EINVAL; } if (!(req = ec_slave_config_get_idn_by_pos_const( sc, ioctl->idn_pos))) { up(&master->master_sem); EC_MASTER_ERR(master, "Invalid IDN position!\n"); kfree(ioctl); return -EINVAL; } ioctl->drive_no = req->drive_no; ioctl->idn = req->idn; ioctl->state = req->state; ioctl->size = req->data_size; memcpy(ioctl->data, req->data, min((u32) ioctl->size, (u32) EC_MAX_IDN_DATA_SIZE)); up(&master->master_sem); if (copy_to_user((void __user *) arg, ioctl, sizeof(*ioctl))) { kfree(ioctl); return -EFAULT; } kfree(ioctl); return 0; } /*****************************************************************************/ #ifdef EC_EOE /** Get EoE handler information. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_eoe_handler( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_eoe_handler_t data; const ec_eoe_t *eoe; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; if (!(eoe = ec_master_get_eoe_handler_const(master, data.eoe_index))) { up(&master->master_sem); EC_MASTER_ERR(master, "EoE handler %u does not exist!\n", data.eoe_index); return -EINVAL; } if (eoe->slave) { data.slave_position = eoe->slave->ring_position; } else { data.slave_position = 0xffff; } snprintf(data.name, EC_DATAGRAM_NAME_SIZE, eoe->dev->name); data.open = eoe->opened; data.rx_bytes = eoe->stats.tx_bytes; data.rx_rate = eoe->tx_rate; data.tx_bytes = eoe->stats.rx_bytes; data.tx_rate = eoe->tx_rate; data.tx_queued_frames = eoe->tx_queued_frames; data.tx_queue_size = eoe->tx_queue_size; up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } #endif /*****************************************************************************/ /** Request the master from userspace. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_request( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_master_t *m; int ret = 0; m = ecrt_request_master_err(master->index); if (IS_ERR(m)) { ret = PTR_ERR(m); } else { ctx->requested = 1; } return ret; } /*****************************************************************************/ /** Create a domain. * * \return Domain index on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_create_domain( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_domain_t *domain; if (unlikely(!ctx->requested)) return -EPERM; domain = ecrt_master_create_domain_err(master); if (IS_ERR(domain)) return PTR_ERR(domain); return domain->index; } /*****************************************************************************/ /** Create a slave configuration. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_create_slave_config( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_config_t data; ec_slave_config_t *sc, *entry; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } sc = ecrt_master_slave_config_err(master, data.alias, data.position, data.vendor_id, data.product_code); if (IS_ERR(sc)) return PTR_ERR(sc); data.config_index = 0; if (down_interruptible(&master->master_sem)) return -EINTR; list_for_each_entry(entry, &master->configs, list) { if (entry == sc) break; data.config_index++; } up(&master->master_sem); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Select the DC reference clock. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_select_ref_clock( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { unsigned long config_index = (unsigned long) arg; ec_slave_config_t *sc = NULL; int ret = 0; if (unlikely(!ctx->requested)) { ret = -EPERM; goto out_return; } if (down_interruptible(&master->master_sem)) { ret = -EINTR; goto out_return; } if (config_index != 0xFFFFFFFF) { if (!(sc = ec_master_get_config(master, config_index))) { ret = -ENOENT; goto out_up; } } ecrt_master_select_reference_clock(master, sc); out_up: up(&master->master_sem); out_return: return ret; } /*****************************************************************************/ /** Activates the master. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_activate( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_master_activate_t io; ec_domain_t *domain; off_t offset; int ret; if (unlikely(!ctx->requested)) return -EPERM; io.process_data = NULL; /* Get the sum of the domains' process data sizes. */ ctx->process_data_size = 0; if (down_interruptible(&master->master_sem)) return -EINTR; list_for_each_entry(domain, &master->domains, list) { ctx->process_data_size += ecrt_domain_size(domain); } up(&master->master_sem); if (ctx->process_data_size) { ctx->process_data = vmalloc(ctx->process_data_size); if (!ctx->process_data) { ctx->process_data_size = 0; return -ENOMEM; } /* Set the memory as external process data memory for the * domains. */ offset = 0; list_for_each_entry(domain, &master->domains, list) { ecrt_domain_external_memory(domain, ctx->process_data + offset); offset += ecrt_domain_size(domain); } #ifdef EC_IOCTL_RTDM /* RTDM uses a different approach for memory-mapping, which has to be * initiated by the kernel. */ ret = ec_rtdm_mmap(ctx, &io.process_data); if (ret < 0) { EC_MASTER_ERR(master, "Failed to map process data" " memory to user space (code %i).\n", ret); return ret; } #endif } io.process_data_size = ctx->process_data_size; #ifndef EC_IOCTL_RTDM ecrt_master_callbacks(master, ec_master_internal_send_cb, ec_master_internal_receive_cb, master); #endif ret = ecrt_master_activate(master); if (ret < 0) return ret; if (copy_to_user((void __user *) arg, &io, sizeof(ec_ioctl_master_activate_t))) return -EFAULT; return 0; } /*****************************************************************************/ /** Deactivates the master. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_deactivate( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { if (unlikely(!ctx->requested)) return -EPERM; ecrt_master_deactivate(master); return 0; } /*****************************************************************************/ /** Set max. number of databytes in a cycle * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_set_send_interval( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { size_t send_interval; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&send_interval, (void __user *) arg, sizeof(send_interval))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) return -EINTR; ec_master_set_send_interval(master, send_interval); up(&master->master_sem); return 0; } /*****************************************************************************/ /** Send frames. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_send( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { if (unlikely(!ctx->requested)) { return -EPERM; } ecrt_master_send(master); return 0; } /*****************************************************************************/ /** Receive frames. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_receive( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { if (unlikely(!ctx->requested)) { return -EPERM; } ecrt_master_receive(master); return 0; } /*****************************************************************************/ /** Get the master state. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_master_state( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_master_state_t data; ecrt_master_state(master, &data); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get the link state. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_master_link_state( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_link_state_t ioctl; ec_master_link_state_t state; int ret; if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { return -EFAULT; } ret = ecrt_master_link_state(master, ioctl.dev_idx, &state); if (ret < 0) { return ret; } if (copy_to_user((void __user *) ioctl.state, &state, sizeof(state))) { return -EFAULT; } return 0; } /*****************************************************************************/ /** Set the master DC application time. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_app_time( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { uint64_t time; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&time, (void __user *) arg, sizeof(time))) { return -EFAULT; } ecrt_master_application_time(master, time); return 0; } /*****************************************************************************/ /** Sync the reference clock. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sync_ref( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { if (unlikely(!ctx->requested)) { return -EPERM; } ecrt_master_sync_reference_clock(master); return 0; } /*****************************************************************************/ /** Sync the reference clock. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sync_ref_to( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { uint64_t time; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&time, (void __user *) arg, sizeof(time))) { return -EFAULT; } ecrt_master_sync_reference_clock_to(master, time); return 0; } /*****************************************************************************/ /** Sync the slave clocks. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sync_slaves( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { if (unlikely(!ctx->requested)) { return -EPERM; } ecrt_master_sync_slave_clocks(master); return 0; } /*****************************************************************************/ /** Get the system time of the reference clock. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_ref_clock_time( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { uint32_t time; int ret; if (unlikely(!ctx->requested)) { return -EPERM; } ret = ecrt_master_reference_clock_time(master, &time); if (ret) { return ret; } if (copy_to_user((void __user *) arg, &time, sizeof(time))) { return -EFAULT; } return 0; } /*****************************************************************************/ /** Queue the sync monitoring datagram. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sync_mon_queue( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { if (unlikely(!ctx->requested)) { return -EPERM; } ecrt_master_sync_monitor_queue(master); return 0; } /*****************************************************************************/ /** Processes the sync monitoring datagram. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sync_mon_process( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { uint32_t time_diff; if (unlikely(!ctx->requested)) return -EPERM; time_diff = ecrt_master_sync_monitor_process(master); if (copy_to_user((void __user *) arg, &time_diff, sizeof(time_diff))) return -EFAULT; return 0; } /*****************************************************************************/ /** Reset configuration. * * \return Always zero (success). */ static ATTRIBUTES int ec_ioctl_reset( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { down(&master->master_sem); ecrt_master_reset(master); up(&master->master_sem); return 0; } /*****************************************************************************/ /** Configure a sync manager. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_sync( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_config_t data; ec_slave_config_t *sc; unsigned int i; int ret = 0; if (unlikely(!ctx->requested)) { ret = -EPERM; goto out_return; } if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { ret = -EFAULT; goto out_return; } if (down_interruptible(&master->master_sem)) { ret = -EINTR; goto out_return; } if (!(sc = ec_master_get_config(master, data.config_index))) { ret = -ENOENT; goto out_up; } for (i = 0; i < EC_MAX_SYNC_MANAGERS; i++) { if (data.syncs[i].config_this) { ret = ecrt_slave_config_sync_manager(sc, i, data.syncs[i].dir, data.syncs[i].watchdog_mode); if (ret) { goto out_up; } } } out_up: up(&master->master_sem); out_return: return ret; } /*****************************************************************************/ /** Configure a slave's watchdogs. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_watchdog( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_config_t data; ec_slave_config_t *sc; int ret = 0; if (unlikely(!ctx->requested)) { ret = -EPERM; goto out_return; } if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { ret = -EFAULT; goto out_return; } if (down_interruptible(&master->master_sem)) { ret = -EINTR; goto out_return; } if (!(sc = ec_master_get_config(master, data.config_index))) { ret = -ENOENT; goto out_up; } ecrt_slave_config_watchdog(sc, data.watchdog_divider, data.watchdog_intervals); out_up: up(&master->master_sem); out_return: return ret; } /*****************************************************************************/ /** Add a PDO to the assignment. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_add_pdo( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_config_pdo_t data; ec_slave_config_t *sc; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config(master, data.config_index))) { up(&master->master_sem); return -ENOENT; } up(&master->master_sem); /** \todo sc could be invalidated */ return ecrt_slave_config_pdo_assign_add(sc, data.sync_index, data.index); } /*****************************************************************************/ /** Clears the PDO assignment. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_clear_pdos( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_config_pdo_t data; ec_slave_config_t *sc; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config(master, data.config_index))) { up(&master->master_sem); return -ENOENT; } up(&master->master_sem); /** \todo sc could be invalidated */ ecrt_slave_config_pdo_assign_clear(sc, data.sync_index); return 0; } /*****************************************************************************/ /** Add an entry to a PDO's mapping. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_add_entry( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_add_pdo_entry_t data; ec_slave_config_t *sc; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config(master, data.config_index))) { up(&master->master_sem); return -ENOENT; } up(&master->master_sem); /** \todo sc could be invalidated */ return ecrt_slave_config_pdo_mapping_add(sc, data.pdo_index, data.entry_index, data.entry_subindex, data.entry_bit_length); } /*****************************************************************************/ /** Clears the mapping of a PDO. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_clear_entries( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_config_pdo_t data; ec_slave_config_t *sc; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config(master, data.config_index))) { up(&master->master_sem); return -ENOENT; } up(&master->master_sem); /** \todo sc could be invalidated */ ecrt_slave_config_pdo_mapping_clear(sc, data.index); return 0; } /*****************************************************************************/ /** Registers a PDO entry. * * \return Process data offset on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_reg_pdo_entry( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_reg_pdo_entry_t data; ec_slave_config_t *sc; ec_domain_t *domain; int ret; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config(master, data.config_index))) { up(&master->master_sem); return -ENOENT; } if (!(domain = ec_master_find_domain(master, data.domain_index))) { up(&master->master_sem); return -ENOENT; } up(&master->master_sem); /** \todo sc or domain could be invalidated */ ret = ecrt_slave_config_reg_pdo_entry(sc, data.entry_index, data.entry_subindex, domain, &data.bit_position); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return ret; } /*****************************************************************************/ /** Registers a PDO entry by its position. * * \return Process data offset on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_reg_pdo_pos( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_reg_pdo_pos_t io; ec_slave_config_t *sc; ec_domain_t *domain; int ret; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } if (down_interruptible(&master->master_sem)) { return -EINTR; } if (!(sc = ec_master_get_config(master, io.config_index))) { up(&master->master_sem); return -ENOENT; } if (!(domain = ec_master_find_domain(master, io.domain_index))) { up(&master->master_sem); return -ENOENT; } up(&master->master_sem); /** \todo sc or domain could be invalidated */ ret = ecrt_slave_config_reg_pdo_entry_pos(sc, io.sync_index, io.pdo_pos, io.entry_pos, domain, &io.bit_position); if (copy_to_user((void __user *) arg, &io, sizeof(io))) return -EFAULT; return ret; } /*****************************************************************************/ /** Sets the DC AssignActivate word and the sync signal times. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_dc( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_config_t data; ec_slave_config_t *sc; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (down_interruptible(&master->master_sem)) return -EINTR; if (!(sc = ec_master_get_config(master, data.config_index))) { up(&master->master_sem); return -ENOENT; } ecrt_slave_config_dc(sc, data.dc_assign_activate, data.dc_sync[0].cycle_time, data.dc_sync[0].shift_time, data.dc_sync[1].cycle_time, data.dc_sync[1].shift_time); up(&master->master_sem); return 0; } /*****************************************************************************/ /** Configures an SDO. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_sdo( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sc_sdo_t data; ec_slave_config_t *sc; uint8_t *sdo_data = NULL; int ret; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (!data.size) return -EINVAL; if (!(sdo_data = kmalloc(data.size, GFP_KERNEL))) { return -ENOMEM; } if (copy_from_user(sdo_data, (void __user *) data.data, data.size)) { kfree(sdo_data); return -EFAULT; } if (down_interruptible(&master->master_sem)) { kfree(sdo_data); return -EINTR; } if (!(sc = ec_master_get_config(master, data.config_index))) { up(&master->master_sem); kfree(sdo_data); return -ENOENT; } up(&master->master_sem); /** \todo sc could be invalidated */ if (data.complete_access) { ret = ecrt_slave_config_complete_sdo(sc, data.index, sdo_data, data.size); } else { ret = ecrt_slave_config_sdo(sc, data.index, data.subindex, sdo_data, data.size); } kfree(sdo_data); return ret; } /*****************************************************************************/ /** Set the emergency ring buffer size. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_emerg_size( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sc_emerg_t io; ec_slave_config_t *sc; int ret; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&io, (void __user *) arg, sizeof(io))) return -EFAULT; if (down_interruptible(&master->master_sem)) { return -EINTR; } if (!(sc = ec_master_get_config(master, io.config_index))) { up(&master->master_sem); return -ENOENT; } ret = ecrt_slave_config_emerg_size(sc, io.size); up(&master->master_sem); return ret; } /*****************************************************************************/ /** Get an emergency message from the ring. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_emerg_pop( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sc_emerg_t io; ec_slave_config_t *sc; u8 msg[EC_COE_EMERGENCY_MSG_SIZE]; int ret; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } /* no locking of master_sem needed, because configuration will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, io.config_index))) { return -ENOENT; } ret = ecrt_slave_config_emerg_pop(sc, msg); if (ret < 0) { return ret; } if (copy_to_user((void __user *) io.target, msg, sizeof(msg))) { return -EFAULT; } return ret; } /*****************************************************************************/ /** Clear the emergency ring. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_emerg_clear( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sc_emerg_t io; ec_slave_config_t *sc; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } /* no locking of master_sem needed, because configuration will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, io.config_index))) { return -ENOENT; } return ecrt_slave_config_emerg_clear(sc); } /*****************************************************************************/ /** Get the number of emergency overruns. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_emerg_overruns( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sc_emerg_t io; ec_slave_config_t *sc; int ret; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } /* no locking of master_sem needed, because configuration will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, io.config_index))) { return -ENOENT; } ret = ecrt_slave_config_emerg_overruns(sc); if (ret < 0) { return ret; } io.overruns = ret; if (copy_to_user((void __user *) arg, &io, sizeof(io))) { return -EFAULT; } return 0; } /*****************************************************************************/ /** Create an SDO request. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_create_sdo_request( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sdo_request_t data; ec_slave_config_t *sc; ec_sdo_request_t *req; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } data.request_index = 0; if (down_interruptible(&master->master_sem)) return -EINTR; sc = ec_master_get_config(master, data.config_index); if (!sc) { up(&master->master_sem); return -ENOENT; } list_for_each_entry(req, &sc->sdo_requests, list) { data.request_index++; } up(&master->master_sem); /** \todo sc could be invalidated */ req = ecrt_slave_config_create_sdo_request_err(sc, data.sdo_index, data.sdo_subindex, data.size); if (IS_ERR(req)) return PTR_ERR(req); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Create a register request. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_create_reg_request( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_reg_request_t io; ec_slave_config_t *sc; ec_reg_request_t *reg; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } io.request_index = 0; if (down_interruptible(&master->master_sem)) { return -EINTR; } sc = ec_master_get_config(master, io.config_index); if (!sc) { up(&master->master_sem); return -ENOENT; } list_for_each_entry(reg, &sc->reg_requests, list) { io.request_index++; } up(&master->master_sem); /** \todo sc could be invalidated */ reg = ecrt_slave_config_create_reg_request_err(sc, io.mem_size); if (IS_ERR(reg)) { return PTR_ERR(reg); } if (copy_to_user((void __user *) arg, &io, sizeof(io))) { return -EFAULT; } return 0; } /*****************************************************************************/ /** Create a VoE handler. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_create_voe_handler( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_voe_t data; ec_slave_config_t *sc; ec_voe_handler_t *voe; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } data.voe_index = 0; if (down_interruptible(&master->master_sem)) return -EINTR; sc = ec_master_get_config(master, data.config_index); if (!sc) { up(&master->master_sem); return -ENOENT; } list_for_each_entry(voe, &sc->voe_handlers, list) { data.voe_index++; } up(&master->master_sem); /** \todo sc could be invalidated */ voe = ecrt_slave_config_create_voe_handler_err(sc, data.size); if (IS_ERR(voe)) return PTR_ERR(voe); if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Get the slave configuration's state. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_state( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sc_state_t data; const ec_slave_config_t *sc; ec_slave_config_state_t state; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } /* no locking of master_sem needed, because sc will not be deleted in the * meantime. */ if (!(sc = ec_master_get_config_const(master, data.config_index))) { return -ENOENT; } ecrt_slave_config_state(sc, &state); if (copy_to_user((void __user *) data.state, &state, sizeof(state))) return -EFAULT; return 0; } /*****************************************************************************/ /** Configures an IDN. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sc_idn( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sc_idn_t ioctl; ec_slave_config_t *sc; uint8_t *data = NULL; int ret; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) return -EFAULT; if (!ioctl.size) return -EINVAL; if (!(data = kmalloc(ioctl.size, GFP_KERNEL))) { return -ENOMEM; } if (copy_from_user(data, (void __user *) ioctl.data, ioctl.size)) { kfree(data); return -EFAULT; } if (down_interruptible(&master->master_sem)) { kfree(data); return -EINTR; } if (!(sc = ec_master_get_config(master, ioctl.config_index))) { up(&master->master_sem); kfree(data); return -ENOENT; } up(&master->master_sem); /** \todo sc could be invalidated */ ret = ecrt_slave_config_idn( sc, ioctl.drive_no, ioctl.idn, ioctl.al_state, data, ioctl.size); kfree(data); return ret; } /*****************************************************************************/ /** Gets the domain's data size. * * \return Domain size, or a negative error code. */ static ATTRIBUTES int ec_ioctl_domain_size( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { const ec_domain_t *domain; if (unlikely(!ctx->requested)) { return -EPERM; } if (down_interruptible(&master->master_sem)) { return -EINTR; } list_for_each_entry(domain, &master->domains, list) { if (domain->index == (unsigned long) arg) { size_t size = ecrt_domain_size(domain); up(&master->master_sem); return size; } } up(&master->master_sem); return -ENOENT; } /*****************************************************************************/ /** Gets the domain's offset in the total process data. * * \return Domain offset, or a negative error code. */ static ATTRIBUTES int ec_ioctl_domain_offset( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { int offset = 0; const ec_domain_t *domain; if (unlikely(!ctx->requested)) return -EPERM; if (down_interruptible(&master->master_sem)) { return -EINTR; } list_for_each_entry(domain, &master->domains, list) { if (domain->index == (unsigned long) arg) { up(&master->master_sem); return offset; } offset += ecrt_domain_size(domain); } up(&master->master_sem); return -ENOENT; } /*****************************************************************************/ /** Process the domain. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_domain_process( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_domain_t *domain; if (unlikely(!ctx->requested)) return -EPERM; /* no locking of master_sem needed, because domain will not be deleted in * the meantime. */ if (!(domain = ec_master_find_domain(master, (unsigned long) arg))) { return -ENOENT; } ecrt_domain_process(domain); return 0; } /*****************************************************************************/ /** Queue the domain. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_domain_queue( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_domain_t *domain; if (unlikely(!ctx->requested)) return -EPERM; /* no locking of master_sem needed, because domain will not be deleted in * the meantime. */ if (!(domain = ec_master_find_domain(master, (unsigned long) arg))) { return -ENOENT; } ecrt_domain_queue(domain); return 0; } /*****************************************************************************/ /** Get the domain state. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_domain_state( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_domain_state_t data; const ec_domain_t *domain; ec_domain_state_t state; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) { return -EFAULT; } /* no locking of master_sem needed, because domain will not be deleted in * the meantime. */ if (!(domain = ec_master_find_domain_const(master, data.domain_index))) { return -ENOENT; } ecrt_domain_state(domain, &state); if (copy_to_user((void __user *) data.state, &state, sizeof(state))) return -EFAULT; return 0; } /*****************************************************************************/ /** Sets an SDO request's SDO index and subindex. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sdo_request_index( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sdo_request_t data; ec_slave_config_t *sc; ec_sdo_request_t *req; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor req will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { return -ENOENT; } ecrt_sdo_request_index(req, data.sdo_index, data.sdo_subindex); return 0; } /*****************************************************************************/ /** Sets an SDO request's timeout. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sdo_request_timeout( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sdo_request_t data; ec_slave_config_t *sc; ec_sdo_request_t *req; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor req will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { return -ENOENT; } ecrt_sdo_request_timeout(req, data.timeout); return 0; } /*****************************************************************************/ /** Gets an SDO request's state. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sdo_request_state( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sdo_request_t data; ec_slave_config_t *sc; ec_sdo_request_t *req; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor req will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { return -ENOENT; } data.state = ecrt_sdo_request_state(req); if (data.state == EC_REQUEST_SUCCESS && req->dir == EC_DIR_INPUT) data.size = ecrt_sdo_request_data_size(req); else data.size = 0; if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Starts an SDO read operation. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sdo_request_read( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sdo_request_t data; ec_slave_config_t *sc; ec_sdo_request_t *req; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor req will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { return -ENOENT; } ecrt_sdo_request_read(req); return 0; } /*****************************************************************************/ /** Starts an SDO write operation. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sdo_request_write( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sdo_request_t data; ec_slave_config_t *sc; ec_sdo_request_t *req; int ret; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (!data.size) { EC_MASTER_ERR(master, "SDO download: Data size may not be zero!\n"); return -EINVAL; } /* no locking of master_sem needed, because neither sc nor req will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { return -ENOENT; } ret = ec_sdo_request_alloc(req, data.size); if (ret) return ret; if (copy_from_user(req->data, (void __user *) data.data, data.size)) return -EFAULT; req->data_size = data.size; ecrt_sdo_request_write(req); return 0; } /*****************************************************************************/ /** Read SDO data. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_sdo_request_data( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_sdo_request_t data; ec_slave_config_t *sc; ec_sdo_request_t *req; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor req will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(req = ec_slave_config_find_sdo_request(sc, data.request_index))) { return -ENOENT; } if (copy_to_user((void __user *) data.data, ecrt_sdo_request_data(req), ecrt_sdo_request_data_size(req))) return -EFAULT; return 0; } /*****************************************************************************/ /** Read register data. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_reg_request_data( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_reg_request_t io; ec_slave_config_t *sc; ec_reg_request_t *reg; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } if (io.mem_size <= 0) { return 0; } /* no locking of master_sem needed, because neither sc nor reg will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, io.config_index))) { return -ENOENT; } if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { return -ENOENT; } if (copy_to_user((void __user *) io.data, ecrt_reg_request_data(reg), min(reg->mem_size, io.mem_size))) { return -EFAULT; } return 0; } /*****************************************************************************/ /** Gets an register request's state. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_reg_request_state( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_reg_request_t io; ec_slave_config_t *sc; ec_reg_request_t *reg; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } /* no locking of master_sem needed, because neither sc nor reg will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, io.config_index))) { return -ENOENT; } if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { return -ENOENT; } io.state = ecrt_reg_request_state(reg); io.new_data = io.state == EC_REQUEST_SUCCESS && reg->dir == EC_DIR_INPUT; if (copy_to_user((void __user *) arg, &io, sizeof(io))) { return -EFAULT; } return 0; } /*****************************************************************************/ /** Starts an register write operation. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_reg_request_write( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_reg_request_t io; ec_slave_config_t *sc; ec_reg_request_t *reg; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } /* no locking of master_sem needed, because neither sc nor reg will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, io.config_index))) { return -ENOENT; } if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { return -ENOENT; } if (io.transfer_size > reg->mem_size) { return -EOVERFLOW; } if (copy_from_user(reg->data, (void __user *) io.data, io.transfer_size)) { return -EFAULT; } ecrt_reg_request_write(reg, io.address, io.transfer_size); return 0; } /*****************************************************************************/ /** Starts an register read operation. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_reg_request_read( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_reg_request_t io; ec_slave_config_t *sc; ec_reg_request_t *reg; if (unlikely(!ctx->requested)) { return -EPERM; } if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } /* no locking of master_sem needed, because neither sc nor reg will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, io.config_index))) { return -ENOENT; } if (!(reg = ec_slave_config_find_reg_request(sc, io.request_index))) { return -ENOENT; } if (io.transfer_size > reg->mem_size) { return -EOVERFLOW; } ecrt_reg_request_read(reg, io.address, io.transfer_size); return 0; } /*****************************************************************************/ /** Sets the VoE send header. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_voe_send_header( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_voe_t data; ec_slave_config_t *sc; ec_voe_handler_t *voe; uint32_t vendor_id; uint16_t vendor_type; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; if (get_user(vendor_id, data.vendor_id)) return -EFAULT; if (get_user(vendor_type, data.vendor_type)) return -EFAULT; /* no locking of master_sem needed, because neither sc nor voe will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { return -ENOENT; } ecrt_voe_handler_send_header(voe, vendor_id, vendor_type); return 0; } /*****************************************************************************/ /** Gets the received VoE header. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_voe_rec_header( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_voe_t data; ec_slave_config_t *sc; ec_voe_handler_t *voe; uint32_t vendor_id; uint16_t vendor_type; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor voe will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { return -ENOENT; } ecrt_voe_handler_received_header(voe, &vendor_id, &vendor_type); if (likely(data.vendor_id)) if (put_user(vendor_id, data.vendor_id)) return -EFAULT; if (likely(data.vendor_type)) if (put_user(vendor_type, data.vendor_type)) return -EFAULT; return 0; } /*****************************************************************************/ /** Starts a VoE read operation. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_voe_read( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_voe_t data; ec_slave_config_t *sc; ec_voe_handler_t *voe; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor voe will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { return -ENOENT; } ecrt_voe_handler_read(voe); return 0; } /*****************************************************************************/ /** Starts a VoE read operation without sending a sync message first. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_voe_read_nosync( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_voe_t data; ec_slave_config_t *sc; ec_voe_handler_t *voe; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor voe will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { return -ENOENT; } ecrt_voe_handler_read_nosync(voe); return 0; } /*****************************************************************************/ /** Starts a VoE write operation. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_voe_write( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_voe_t data; ec_slave_config_t *sc; ec_voe_handler_t *voe; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor voe will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { return -ENOENT; } if (data.size) { if (data.size > ec_voe_handler_mem_size(voe)) return -EOVERFLOW; if (copy_from_user(ecrt_voe_handler_data(voe), (void __user *) data.data, data.size)) return -EFAULT; } ecrt_voe_handler_write(voe, data.size); return 0; } /*****************************************************************************/ /** Executes the VoE state machine. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_voe_exec( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_voe_t data; ec_slave_config_t *sc; ec_voe_handler_t *voe; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor voe will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { return -ENOENT; } data.state = ecrt_voe_handler_execute(voe); if (data.state == EC_REQUEST_SUCCESS && voe->dir == EC_DIR_INPUT) data.size = ecrt_voe_handler_data_size(voe); else data.size = 0; if (copy_to_user((void __user *) arg, &data, sizeof(data))) return -EFAULT; return 0; } /*****************************************************************************/ /** Reads the received VoE data. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_voe_data( ec_master_t *master, /**< EtherCAT master. */ void *arg, /**< ioctl() argument. */ ec_ioctl_context_t *ctx /**< Private data structure of file handle. */ ) { ec_ioctl_voe_t data; ec_slave_config_t *sc; ec_voe_handler_t *voe; if (unlikely(!ctx->requested)) return -EPERM; if (copy_from_user(&data, (void __user *) arg, sizeof(data))) return -EFAULT; /* no locking of master_sem needed, because neither sc nor voe will not be * deleted in the meantime. */ if (!(sc = ec_master_get_config(master, data.config_index))) { return -ENOENT; } if (!(voe = ec_slave_config_find_voe_handler(sc, data.voe_index))) { return -ENOENT; } if (copy_to_user((void __user *) data.data, ecrt_voe_handler_data(voe), ecrt_voe_handler_data_size(voe))) return -EFAULT; return 0; } /*****************************************************************************/ /** Read a file from a slave via FoE. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_foe_read( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_foe_t io; ec_foe_request_t request; ec_slave_t *slave; int ret; if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } ec_foe_request_init(&request, io.file_name); ret = ec_foe_request_alloc(&request, 10000); // FIXME if (ret) { ec_foe_request_clear(&request); return ret; } ec_foe_request_read(&request); if (down_interruptible(&master->master_sem)) { ec_foe_request_clear(&request); return -EINTR; } if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { up(&master->master_sem); ec_foe_request_clear(&request); EC_MASTER_ERR(master, "Slave %u does not exist!\n", io.slave_position); return -EINVAL; } EC_SLAVE_DBG(slave, 1, "Scheduling FoE read request.\n"); // schedule request. list_add_tail(&request.list, &slave->foe_requests); up(&master->master_sem); // wait for processing through FSM if (wait_event_interruptible(master->request_queue, request.state != EC_INT_REQUEST_QUEUED)) { // interrupted by signal down(&master->master_sem); if (request.state == EC_INT_REQUEST_QUEUED) { list_del(&request.list); up(&master->master_sem); ec_foe_request_clear(&request); return -EINTR; } // request already processing: interrupt not possible. up(&master->master_sem); } // wait until master FSM has finished processing wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); io.result = request.result; io.error_code = request.error_code; if (request.state != EC_INT_REQUEST_SUCCESS) { io.data_size = 0; ret = -EIO; } else { if (request.data_size > io.buffer_size) { EC_SLAVE_ERR(slave, "%s(): Buffer too small.\n", __func__); ec_foe_request_clear(&request); return -EOVERFLOW; } io.data_size = request.data_size; if (copy_to_user((void __user *) io.buffer, request.buffer, io.data_size)) { ec_foe_request_clear(&request); return -EFAULT; } ret = 0; } if (__copy_to_user((void __user *) arg, &io, sizeof(io))) { ret = -EFAULT; } ec_foe_request_clear(&request); return ret; } /*****************************************************************************/ /** Write a file to a slave via FoE * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_foe_write( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_foe_t io; ec_foe_request_t request; ec_slave_t *slave; int ret; if (copy_from_user(&io, (void __user *) arg, sizeof(io))) { return -EFAULT; } ec_foe_request_init(&request, io.file_name); ret = ec_foe_request_alloc(&request, io.buffer_size); if (ret) { ec_foe_request_clear(&request); return ret; } if (copy_from_user(request.buffer, (void __user *) io.buffer, io.buffer_size)) { ec_foe_request_clear(&request); return -EFAULT; } request.data_size = io.buffer_size; ec_foe_request_write(&request); if (down_interruptible(&master->master_sem)) { ec_foe_request_clear(&request); return -EINTR; } if (!(slave = ec_master_find_slave(master, 0, io.slave_position))) { up(&master->master_sem); EC_MASTER_ERR(master, "Slave %u does not exist!\n", io.slave_position); ec_foe_request_clear(&request); return -EINVAL; } EC_SLAVE_DBG(slave, 1, "Scheduling FoE write request.\n"); // schedule FoE write request. list_add_tail(&request.list, &slave->foe_requests); up(&master->master_sem); // wait for processing through FSM if (wait_event_interruptible(master->request_queue, request.state != EC_INT_REQUEST_QUEUED)) { // interrupted by signal down(&master->master_sem); if (request.state == EC_INT_REQUEST_QUEUED) { // abort request list_del(&request.list); up(&master->master_sem); ec_foe_request_clear(&request); return -EINTR; } up(&master->master_sem); } // wait until master FSM has finished processing wait_event(master->request_queue, request.state != EC_INT_REQUEST_BUSY); io.result = request.result; io.error_code = request.error_code; ret = request.state == EC_INT_REQUEST_SUCCESS ? 0 : -EIO; if (__copy_to_user((void __user *) arg, &io, sizeof(io))) { ret = -EFAULT; } ec_foe_request_clear(&request); return ret; } /*****************************************************************************/ /** Read an SoE IDN. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_soe_read( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_soe_read_t ioctl; u8 *data; int retval; if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { return -EFAULT; } data = kmalloc(ioctl.mem_size, GFP_KERNEL); if (!data) { EC_MASTER_ERR(master, "Failed to allocate %zu bytes of IDN data.\n", ioctl.mem_size); return -ENOMEM; } retval = ecrt_master_read_idn(master, ioctl.slave_position, ioctl.drive_no, ioctl.idn, data, ioctl.mem_size, &ioctl.data_size, &ioctl.error_code); if (retval) { kfree(data); return retval; } if (copy_to_user((void __user *) ioctl.data, data, ioctl.data_size)) { kfree(data); return -EFAULT; } kfree(data); if (__copy_to_user((void __user *) arg, &ioctl, sizeof(ioctl))) { retval = -EFAULT; } EC_MASTER_DBG(master, 1, "Finished SoE read request.\n"); return retval; } /*****************************************************************************/ /** Write an IDN to a slave via SoE. * * \return Zero on success, otherwise a negative error code. */ static ATTRIBUTES int ec_ioctl_slave_soe_write( ec_master_t *master, /**< EtherCAT master. */ void *arg /**< ioctl() argument. */ ) { ec_ioctl_slave_soe_write_t ioctl; u8 *data; int retval; if (copy_from_user(&ioctl, (void __user *) arg, sizeof(ioctl))) { return -EFAULT; } data = kmalloc(ioctl.data_size, GFP_KERNEL); if (!data) { EC_MASTER_ERR(master, "Failed to allocate %zu bytes of IDN data.\n", ioctl.data_size); return -ENOMEM; } if (copy_from_user(data, (void __user *) ioctl.data, ioctl.data_size)) { kfree(data); return -EFAULT; } retval = ecrt_master_write_idn(master, ioctl.slave_position, ioctl.drive_no, ioctl.idn, data, ioctl.data_size, &ioctl.error_code); kfree(data); if (retval) { return retval; } if (__copy_to_user((void __user *) arg, &ioctl, sizeof(ioctl))) { retval = -EFAULT; } EC_MASTER_DBG(master, 1, "Finished SoE write request.\n"); return retval; } /*****************************************************************************/ /** ioctl() function to use. */ #ifdef EC_IOCTL_RTDM #define EC_IOCTL ec_ioctl_rtdm #else #define EC_IOCTL ec_ioctl #endif /** Called when an ioctl() command is issued. * * \return ioctl() return code. */ long EC_IOCTL( ec_master_t *master, /**< EtherCAT master. */ ec_ioctl_context_t *ctx, /**< Device context. */ unsigned int cmd, /**< ioctl() command identifier. */ void *arg /**< ioctl() argument. */ ) { #if DEBUG_LATENCY cycles_t a = get_cycles(), b; unsigned int t; #endif int ret; switch (cmd) { case EC_IOCTL_MODULE: ret = ec_ioctl_module(arg); break; case EC_IOCTL_MASTER: ret = ec_ioctl_master(master, arg); break; case EC_IOCTL_SLAVE: ret = ec_ioctl_slave(master, arg); break; case EC_IOCTL_SLAVE_SYNC: ret = ec_ioctl_slave_sync(master, arg); break; case EC_IOCTL_SLAVE_SYNC_PDO: ret = ec_ioctl_slave_sync_pdo(master, arg); break; case EC_IOCTL_SLAVE_SYNC_PDO_ENTRY: ret = ec_ioctl_slave_sync_pdo_entry(master, arg); break; case EC_IOCTL_DOMAIN: ret = ec_ioctl_domain(master, arg); break; case EC_IOCTL_DOMAIN_FMMU: ret = ec_ioctl_domain_fmmu(master, arg); break; case EC_IOCTL_DOMAIN_DATA: ret = ec_ioctl_domain_data(master, arg); break; case EC_IOCTL_MASTER_DEBUG: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_master_debug(master, arg); break; case EC_IOCTL_MASTER_RESCAN: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_master_rescan(master, arg); break; case EC_IOCTL_SLAVE_STATE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_slave_state(master, arg); break; case EC_IOCTL_SLAVE_SDO: ret = ec_ioctl_slave_sdo(master, arg); break; case EC_IOCTL_SLAVE_SDO_ENTRY: ret = ec_ioctl_slave_sdo_entry(master, arg); break; case EC_IOCTL_SLAVE_SDO_UPLOAD: ret = ec_ioctl_slave_sdo_upload(master, arg); break; case EC_IOCTL_SLAVE_SDO_DOWNLOAD: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_slave_sdo_download(master, arg); break; case EC_IOCTL_SLAVE_SII_READ: ret = ec_ioctl_slave_sii_read(master, arg); break; case EC_IOCTL_SLAVE_SII_WRITE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_slave_sii_write(master, arg); break; case EC_IOCTL_SLAVE_REG_READ: ret = ec_ioctl_slave_reg_read(master, arg); break; case EC_IOCTL_SLAVE_REG_WRITE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_slave_reg_write(master, arg); break; case EC_IOCTL_SLAVE_FOE_READ: ret = ec_ioctl_slave_foe_read(master, arg); break; case EC_IOCTL_SLAVE_FOE_WRITE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_slave_foe_write(master, arg); break; case EC_IOCTL_SLAVE_SOE_READ: ret = ec_ioctl_slave_soe_read(master, arg); break; case EC_IOCTL_SLAVE_SOE_WRITE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_slave_soe_write(master, arg); break; case EC_IOCTL_CONFIG: ret = ec_ioctl_config(master, arg); break; case EC_IOCTL_CONFIG_PDO: ret = ec_ioctl_config_pdo(master, arg); break; case EC_IOCTL_CONFIG_PDO_ENTRY: ret = ec_ioctl_config_pdo_entry(master, arg); break; case EC_IOCTL_CONFIG_SDO: ret = ec_ioctl_config_sdo(master, arg); break; case EC_IOCTL_CONFIG_IDN: ret = ec_ioctl_config_idn(master, arg); break; #ifdef EC_EOE case EC_IOCTL_EOE_HANDLER: ret = ec_ioctl_eoe_handler(master, arg); break; #endif case EC_IOCTL_REQUEST: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_request(master, arg, ctx); break; case EC_IOCTL_CREATE_DOMAIN: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_create_domain(master, arg, ctx); break; case EC_IOCTL_CREATE_SLAVE_CONFIG: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_create_slave_config(master, arg, ctx); break; case EC_IOCTL_SELECT_REF_CLOCK: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_select_ref_clock(master, arg, ctx); break; case EC_IOCTL_ACTIVATE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_activate(master, arg, ctx); break; case EC_IOCTL_DEACTIVATE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_deactivate(master, arg, ctx); break; case EC_IOCTL_SEND: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_send(master, arg, ctx); break; case EC_IOCTL_RECEIVE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_receive(master, arg, ctx); break; case EC_IOCTL_MASTER_STATE: ret = ec_ioctl_master_state(master, arg, ctx); break; case EC_IOCTL_MASTER_LINK_STATE: ret = ec_ioctl_master_link_state(master, arg, ctx); break; case EC_IOCTL_APP_TIME: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_app_time(master, arg, ctx); break; case EC_IOCTL_SYNC_REF: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sync_ref(master, arg, ctx); break; case EC_IOCTL_SYNC_REF_TO: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sync_ref_to(master, arg, ctx); break; case EC_IOCTL_SYNC_SLAVES: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sync_slaves(master, arg, ctx); break; case EC_IOCTL_REF_CLOCK_TIME: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_ref_clock_time(master, arg, ctx); break; case EC_IOCTL_SYNC_MON_QUEUE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sync_mon_queue(master, arg, ctx); break; case EC_IOCTL_SYNC_MON_PROCESS: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sync_mon_process(master, arg, ctx); break; case EC_IOCTL_RESET: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_reset(master, arg, ctx); break; case EC_IOCTL_SC_SYNC: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_sync(master, arg, ctx); break; case EC_IOCTL_SC_WATCHDOG: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_watchdog(master, arg, ctx); break; case EC_IOCTL_SC_ADD_PDO: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_add_pdo(master, arg, ctx); break; case EC_IOCTL_SC_CLEAR_PDOS: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_clear_pdos(master, arg, ctx); break; case EC_IOCTL_SC_ADD_ENTRY: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_add_entry(master, arg, ctx); break; case EC_IOCTL_SC_CLEAR_ENTRIES: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_clear_entries(master, arg, ctx); break; case EC_IOCTL_SC_REG_PDO_ENTRY: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_reg_pdo_entry(master, arg, ctx); break; case EC_IOCTL_SC_REG_PDO_POS: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_reg_pdo_pos(master, arg, ctx); break; case EC_IOCTL_SC_DC: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_dc(master, arg, ctx); break; case EC_IOCTL_SC_SDO: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_sdo(master, arg, ctx); break; case EC_IOCTL_SC_EMERG_SIZE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_emerg_size(master, arg, ctx); break; case EC_IOCTL_SC_EMERG_POP: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_emerg_pop(master, arg, ctx); break; case EC_IOCTL_SC_EMERG_CLEAR: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_emerg_clear(master, arg, ctx); break; case EC_IOCTL_SC_EMERG_OVERRUNS: ret = ec_ioctl_sc_emerg_overruns(master, arg, ctx); break; case EC_IOCTL_SC_SDO_REQUEST: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_create_sdo_request(master, arg, ctx); break; case EC_IOCTL_SC_REG_REQUEST: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_create_reg_request(master, arg, ctx); break; case EC_IOCTL_SC_VOE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_create_voe_handler(master, arg, ctx); break; case EC_IOCTL_SC_STATE: ret = ec_ioctl_sc_state(master, arg, ctx); break; case EC_IOCTL_SC_IDN: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sc_idn(master, arg, ctx); break; case EC_IOCTL_DOMAIN_SIZE: ret = ec_ioctl_domain_size(master, arg, ctx); break; case EC_IOCTL_DOMAIN_OFFSET: ret = ec_ioctl_domain_offset(master, arg, ctx); break; case EC_IOCTL_DOMAIN_PROCESS: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_domain_process(master, arg, ctx); break; case EC_IOCTL_DOMAIN_QUEUE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_domain_queue(master, arg, ctx); break; case EC_IOCTL_DOMAIN_STATE: ret = ec_ioctl_domain_state(master, arg, ctx); break; case EC_IOCTL_SDO_REQUEST_INDEX: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sdo_request_index(master, arg, ctx); break; case EC_IOCTL_SDO_REQUEST_TIMEOUT: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sdo_request_timeout(master, arg, ctx); break; case EC_IOCTL_SDO_REQUEST_STATE: ret = ec_ioctl_sdo_request_state(master, arg, ctx); break; case EC_IOCTL_SDO_REQUEST_READ: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sdo_request_read(master, arg, ctx); break; case EC_IOCTL_SDO_REQUEST_WRITE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_sdo_request_write(master, arg, ctx); break; case EC_IOCTL_SDO_REQUEST_DATA: ret = ec_ioctl_sdo_request_data(master, arg, ctx); break; case EC_IOCTL_REG_REQUEST_DATA: ret = ec_ioctl_reg_request_data(master, arg, ctx); break; case EC_IOCTL_REG_REQUEST_STATE: ret = ec_ioctl_reg_request_state(master, arg, ctx); break; case EC_IOCTL_REG_REQUEST_WRITE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_reg_request_write(master, arg, ctx); break; case EC_IOCTL_REG_REQUEST_READ: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_reg_request_read(master, arg, ctx); break; case EC_IOCTL_VOE_SEND_HEADER: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_voe_send_header(master, arg, ctx); break; case EC_IOCTL_VOE_REC_HEADER: ret = ec_ioctl_voe_rec_header(master, arg, ctx); break; case EC_IOCTL_VOE_READ: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_voe_read(master, arg, ctx); break; case EC_IOCTL_VOE_READ_NOSYNC: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_voe_read_nosync(master, arg, ctx); break; case EC_IOCTL_VOE_WRITE: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_voe_write(master, arg, ctx); break; case EC_IOCTL_VOE_EXEC: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_voe_exec(master, arg, ctx); break; case EC_IOCTL_VOE_DATA: ret = ec_ioctl_voe_data(master, arg, ctx); break; case EC_IOCTL_SET_SEND_INTERVAL: if (!ctx->writable) { ret = -EPERM; break; } ret = ec_ioctl_set_send_interval(master, arg, ctx); break; default: ret = -ENOTTY; break; } #if DEBUG_LATENCY b = get_cycles(); t = (unsigned int) ((b - a) * 1000LL) / cpu_khz; if (t > 50) { EC_MASTER_WARN(master, "ioctl(0x%02x) took %u us.\n", _IOC_NR(cmd), t); } #endif return ret; } /*****************************************************************************/