/****************************************************************************** * * $Id$ * * Copyright (C) 2006-2008 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 device methods. */ /*****************************************************************************/ #include #include #include #include #include "device.h" #include "master.h" #ifdef EC_DEBUG_RING #define timersub(a, b, result) \ do { \ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ if ((result)->tv_usec < 0) { \ --(result)->tv_sec; \ (result)->tv_usec += 1000000; \ } \ } while (0) #endif /** List of intervals for frame statistics [s]. */ static const unsigned int rate_intervals[] = { 1, 10, 60 }; /*****************************************************************************/ /** Constructor. * * \return 0 in case of success, else < 0 */ int ec_device_init( ec_device_t *device, /**< EtherCAT device */ ec_master_t *master /**< master owning the device */ ) { int ret; unsigned int i; struct ethhdr *eth; #ifdef EC_DEBUG_IF char ifname[10]; char mb = 'x'; #endif #ifdef EC_DEBUG_RING device->debug_frame_index = 0; device->debug_frame_count = 0; #endif device->master = master; device->tx_ring_index = 0; #ifdef EC_DEBUG_IF if (device == &master->main_device) mb = 'm'; else if (device == &master->backup_device) mb = 'b'; sprintf(ifname, "ecdbg%c%u", mb, master->index); ret = ec_debug_init(&device->dbg, device, ifname); if (ret < 0) { EC_MASTER_ERR(master, "Failed to init debug device!\n"); goto out_return; } #endif for (i = 0; i < EC_TX_RING_SIZE; i++) device->tx_skb[i] = NULL; for (i = 0; i < EC_TX_RING_SIZE; i++) { if (!(device->tx_skb[i] = dev_alloc_skb(ETH_FRAME_LEN))) { EC_MASTER_ERR(master, "Error allocating device socket buffer!\n"); ret = -ENOMEM; goto out_tx_ring; } // add Ethernet-II-header skb_reserve(device->tx_skb[i], ETH_HLEN); eth = (struct ethhdr *) skb_push(device->tx_skb[i], ETH_HLEN); eth->h_proto = htons(0x88A4); memset(eth->h_dest, 0xFF, ETH_ALEN); } ec_device_detach(device); // resets remaining fields return 0; out_tx_ring: for (i = 0; i < EC_TX_RING_SIZE; i++) if (device->tx_skb[i]) dev_kfree_skb(device->tx_skb[i]); #ifdef EC_DEBUG_IF ec_debug_clear(&device->dbg); out_return: #endif return ret; } /*****************************************************************************/ /** Destructor. */ void ec_device_clear( ec_device_t *device /**< EtherCAT device */ ) { unsigned int i; if (device->open) ec_device_close(device); for (i = 0; i < EC_TX_RING_SIZE; i++) dev_kfree_skb(device->tx_skb[i]); #ifdef EC_DEBUG_IF ec_debug_clear(&device->dbg); #endif } /*****************************************************************************/ /** Associate with net_device. */ void ec_device_attach( ec_device_t *device, /**< EtherCAT device */ struct net_device *net_dev, /**< net_device structure */ ec_pollfunc_t poll, /**< pointer to device's poll function */ struct module *module /**< the device's module */ ) { unsigned int i; struct ethhdr *eth; ec_device_detach(device); // resets fields device->dev = net_dev; device->poll = poll; device->module = module; for (i = 0; i < EC_TX_RING_SIZE; i++) { device->tx_skb[i]->dev = net_dev; eth = (struct ethhdr *) (device->tx_skb[i]->data); memcpy(eth->h_source, net_dev->dev_addr, ETH_ALEN); } #ifdef EC_DEBUG_IF ec_debug_register(&device->dbg, net_dev); #endif } /*****************************************************************************/ /** Disconnect from net_device. */ void ec_device_detach( ec_device_t *device /**< EtherCAT device */ ) { unsigned int i; #ifdef EC_DEBUG_IF ec_debug_unregister(&device->dbg); #endif device->dev = NULL; device->poll = NULL; device->module = NULL; device->open = 0; device->link_state = 0; // down ec_device_clear_stats(device); for (i = 0; i < EC_TX_RING_SIZE; i++) device->tx_skb[i]->dev = NULL; } /*****************************************************************************/ /** Opens the EtherCAT device. * * \return 0 in case of success, else < 0 */ int ec_device_open( ec_device_t *device /**< EtherCAT device */ ) { int ret; if (!device->dev) { EC_MASTER_ERR(device->master, "No net_device to open!\n"); return -ENODEV; } if (device->open) { EC_MASTER_WARN(device->master, "Device already opened!\n"); return 0; } device->link_state = 0; ec_device_clear_stats(device); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) ret = device->dev->netdev_ops->ndo_open(device->dev); #else ret = device->dev->open(device->dev); #endif if (!ret) device->open = 1; return ret; } /*****************************************************************************/ /** Stops the EtherCAT device. * * \return 0 in case of success, else < 0 */ int ec_device_close( ec_device_t *device /**< EtherCAT device */ ) { int ret; if (!device->dev) { EC_MASTER_ERR(device->master, "No device to close!\n"); return -ENODEV; } if (!device->open) { EC_MASTER_WARN(device->master, "Device already closed!\n"); return 0; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) ret = device->dev->netdev_ops->ndo_stop(device->dev); #else ret = device->dev->stop(device->dev); #endif if (!ret) device->open = 0; return ret; } /*****************************************************************************/ /** Returns a pointer to the device's transmit memory. * * \return pointer to the TX socket buffer */ uint8_t *ec_device_tx_data( ec_device_t *device /**< EtherCAT device */ ) { /* cycle through socket buffers, because otherwise there is a race * condition, if multiple frames are sent and the DMA is not scheduled in * between. */ device->tx_ring_index++; device->tx_ring_index %= EC_TX_RING_SIZE; return device->tx_skb[device->tx_ring_index]->data + ETH_HLEN; } /*****************************************************************************/ /** Sends the content of the transmit socket buffer. * * Cuts the socket buffer content to the (now known) size, and calls the * start_xmit() function of the assigned net_device. */ void ec_device_send( ec_device_t *device, /**< EtherCAT device */ size_t size /**< number of bytes to send */ ) { struct sk_buff *skb = device->tx_skb[device->tx_ring_index]; // frame statistics if (unlikely(jiffies - device->stats_jiffies >= HZ)) { unsigned int i; u32 tx_frame_rate = (u32) (device->tx_count - device->last_tx_count) * 1000; u32 tx_byte_rate = (device->tx_bytes - device->last_tx_bytes); u64 loss = device->tx_count - device->rx_count; s32 loss_rate = (s32) (loss - device->last_loss) * 1000; for (i = 0; i < EC_RATE_COUNT; i++) { unsigned int n = rate_intervals[i]; device->tx_frame_rates[i] = (device->tx_frame_rates[i] * (n - 1) + tx_frame_rate) / n; device->tx_byte_rates[i] = (device->tx_byte_rates[i] * (n - 1) + tx_byte_rate) / n; device->loss_rates[i] = (device->loss_rates[i] * (n - 1) + loss_rate) / n; } device->last_tx_count = device->tx_count; device->last_tx_bytes = device->tx_bytes; device->last_loss = loss; device->stats_jiffies = jiffies; } // set the right length for the data skb->len = ETH_HLEN + size; if (unlikely(device->master->debug_level > 1)) { EC_MASTER_DBG(device->master, 2, "Sending frame:\n"); ec_print_data(skb->data, ETH_HLEN + size); } // start sending #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) if (device->dev->netdev_ops->ndo_start_xmit(skb, device->dev) == NETDEV_TX_OK) #else if (device->dev->hard_start_xmit(skb, device->dev) == NETDEV_TX_OK) #endif { device->tx_count++; device->tx_bytes += ETH_HLEN + size; #ifdef EC_DEBUG_IF ec_debug_send(&device->dbg, skb->data, ETH_HLEN + size); #endif #ifdef EC_DEBUG_RING ec_device_debug_ring_append( device, TX, skb->data + ETH_HLEN, size); #endif } else { device->tx_errors++; } } /*****************************************************************************/ /** Clears the frame statistics. */ void ec_device_clear_stats( ec_device_t *device /**< EtherCAT device */ ) { unsigned int i; // zero frame statistics device->tx_count = 0; device->rx_count = 0; device->tx_errors = 0; device->tx_bytes = 0; device->last_tx_count = 0; device->last_tx_bytes = 0; device->last_loss = 0; for (i = 0; i < EC_RATE_COUNT; i++) { device->tx_frame_rates[i] = 0; device->tx_byte_rates[i] = 0; device->loss_rates[i] = 0; } } /*****************************************************************************/ #ifdef EC_DEBUG_RING /** Appends frame data to the debug ring. */ void ec_device_debug_ring_append( ec_device_t *device, /**< EtherCAT device */ ec_debug_frame_dir_t dir, /**< direction */ const void *data, /**< frame data */ size_t size /**< data size */ ) { ec_debug_frame_t *df = &device->debug_frames[device->debug_frame_index]; df->dir = dir; if (dir == TX) do_gettimeofday(&df->t); else df->t = device->timeval_poll; memcpy(df->data, data, size); df->data_size = size; device->debug_frame_index++; device->debug_frame_index %= EC_DEBUG_RING_SIZE; if (unlikely(device->debug_frame_count < EC_DEBUG_RING_SIZE)) device->debug_frame_count++; } /*****************************************************************************/ /** Outputs the debug ring. */ void ec_device_debug_ring_print( const ec_device_t *device /**< EtherCAT device */ ) { int i; unsigned int ring_index; const ec_debug_frame_t *df; struct timeval t0, diff; // calculate index of the newest frame in the ring to get its time ring_index = (device->debug_frame_index + EC_DEBUG_RING_SIZE - 1) % EC_DEBUG_RING_SIZE; t0 = device->debug_frames[ring_index].t; EC_MASTER_DBG(device->master, 1, "Debug ring %u:\n", ring_index); // calculate index of the oldest frame in the ring ring_index = (device->debug_frame_index + EC_DEBUG_RING_SIZE - device->debug_frame_count) % EC_DEBUG_RING_SIZE; for (i = 0; i < device->debug_frame_count; i++) { df = &device->debug_frames[ring_index]; timersub(&t0, &df->t, &diff); EC_MASTER_DBG(device->master, 1, "Frame %u, dt=%u.%06u s, %s:\n", i + 1 - device->debug_frame_count, (unsigned int) diff.tv_sec, (unsigned int) diff.tv_usec, (df->dir == TX) ? "TX" : "RX"); ec_print_data(df->data, df->data_size); ring_index++; ring_index %= EC_DEBUG_RING_SIZE; } } #endif /*****************************************************************************/ /** Calls the poll function of the assigned net_device. * * The master itself works without using interrupts. Therefore the processing * of received data and status changes of the network device has to be * done by the master calling the ISR "manually". */ void ec_device_poll( ec_device_t *device /**< EtherCAT device */ ) { #ifdef EC_HAVE_CYCLES device->cycles_poll = get_cycles(); #endif device->jiffies_poll = jiffies; #ifdef EC_DEBUG_RING do_gettimeofday(&device->timeval_poll); #endif device->poll(device->dev); } /****************************************************************************** * Device interface *****************************************************************************/ /** Withdraws an EtherCAT device from the master. * * The device is disconnected from the master and all device ressources * are freed. * * \attention Before calling this function, the ecdev_stop() function has * to be called, to be sure that the master does not use the device * any more. * \ingroup DeviceInterface */ void ecdev_withdraw(ec_device_t *device /**< EtherCAT device */) { ec_master_t *master = device->master; char str[20]; ec_mac_print(device->dev->dev_addr, str); EC_MASTER_INFO(master, "Releasing main device %s.\n", str); down(&master->device_sem); ec_device_detach(device); up(&master->device_sem); } /*****************************************************************************/ /** Opens the network device and makes the master enter IDLE phase. * * \return 0 on success, else < 0 * \ingroup DeviceInterface */ int ecdev_open(ec_device_t *device /**< EtherCAT device */) { int ret; ret = ec_device_open(device); if (ret) { EC_MASTER_ERR(device->master, "Failed to open device!\n"); return ret; } ret = ec_master_enter_idle_phase(device->master); if (ret) { EC_MASTER_ERR(device->master, "Failed to enter IDLE phase!\n"); return ret; } return 0; } /*****************************************************************************/ /** Makes the master leave IDLE phase and closes the network device. * * \return 0 on success, else < 0 * \ingroup DeviceInterface */ void ecdev_close(ec_device_t *device /**< EtherCAT device */) { ec_master_leave_idle_phase(device->master); if (ec_device_close(device)) EC_MASTER_WARN(device->master, "Failed to close device!\n"); } /*****************************************************************************/ /** Accepts a received frame. * * Forwards the received data to the master. The master will analyze the frame * and dispatch the received commands to the sending instances. * * \ingroup DeviceInterface */ void ecdev_receive( ec_device_t *device, /**< EtherCAT device */ const void *data, /**< pointer to received data */ size_t size /**< number of bytes received */ ) { const void *ec_data = data + ETH_HLEN; size_t ec_size = size - ETH_HLEN; if (unlikely(!data)) { EC_MASTER_WARN(device->master, "%s() called with NULL data.\n", __func__); return; } device->rx_count++; if (unlikely(device->master->debug_level > 1)) { EC_MASTER_DBG(device->master, 2, "Received frame:\n"); ec_print_data(data, size); } #ifdef EC_DEBUG_IF ec_debug_send(&device->dbg, data, size); #endif #ifdef EC_DEBUG_RING ec_device_debug_ring_append(device, RX, ec_data, ec_size); #endif ec_master_receive_datagrams(device->master, ec_data, ec_size); } /*****************************************************************************/ /** Sets a new link state. * * If the device notifies the master about the link being down, the master * will not try to send frames using this device. * * \ingroup DeviceInterface */ void ecdev_set_link( ec_device_t *device, /**< EtherCAT device */ uint8_t state /**< new link state */ ) { if (unlikely(!device)) { EC_MASTER_WARN(device->master, "ecdev_set_link(): No device!\n"); return; } if (likely(state != device->link_state)) { device->link_state = state; EC_MASTER_INFO(device->master, "Link state changed to %s.\n", (state ? "UP" : "DOWN")); } } /*****************************************************************************/ /** Reads the link state. * * \ingroup DeviceInterface */ uint8_t ecdev_get_link( const ec_device_t *device /**< EtherCAT device */ ) { if (unlikely(!device)) { EC_MASTER_WARN(device->master, "ecdev_get_link(): No device!\n"); return 0; } return device->link_state; } /*****************************************************************************/ /** \cond */ EXPORT_SYMBOL(ecdev_withdraw); EXPORT_SYMBOL(ecdev_open); EXPORT_SYMBOL(ecdev_close); EXPORT_SYMBOL(ecdev_receive); EXPORT_SYMBOL(ecdev_get_link); EXPORT_SYMBOL(ecdev_set_link); /** \endcond */ /*****************************************************************************/