master/ioctl.c
author Patrick Bruenn <p.bruenn@beckhoff.com>
Tue, 12 Apr 2016 11:17:36 +0200
branchstable-1.5
changeset 2654 b3f6b3e5ef29
parent 2529 c7e1f2616a9d
child 2693 8051d05cb935
permissions -rw-r--r--
devices/ccat: revert "limit rx processing to one frame per poll"

revert "limit rx processing to one frame per poll", which caused etherlab
frame timeouts in setups with more than one frame per cycle.
/******************************************************************************
 *
 *  $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 <linux/module.h>
#include <linux/vmalloc.h>

#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.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. */
        )
{
    ec_ioctl_app_time_t data;

    if (unlikely(!ctx->requested))
        return -EPERM;

    if (copy_from_user(&data, (void __user *) arg, sizeof(data))) {
        return -EFAULT;
    }

    ecrt_master_application_time(master, data.app_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 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_MASTER_ERR(master, "Buffer too small.\n");
            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_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;
}

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