rtdm/module.c
author Florian Pose <fp@igh-essen.com>
Thu, 05 Jan 2012 16:53:08 +0100
changeset 2195 d9146c0ff00f
parent 2071 e797dc47018b
permissions -rw-r--r--
Allow gaps between SDO subindices when fetching dictionary.
/******************************************************************************
 *
 *  $Id$
 *
 *  ec_rtdm.c	Copyright (C) 2009-2010  Moehwald GmbH B.Benner
 *                            2011       IgH Andreas Stewering-Bone
 *								  
 *								  
 *  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 Lesser General
 *  Public License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  The IgH EtherCAT master userspace library 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with the IgH EtherCAT master userspace library. If not, see
 *  <http://www.gnu.org/licenses/>.
 *  
 *  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.
 *
 *****************************************************************************/

#include <linux/module.h>
#include <linux/mman.h>


#ifdef ENABLE_XENOMAI
#include <native/task.h>
#include <native/sem.h>
#include <native/mutex.h>
#include <native/timer.h>
#endif

#ifdef ENABLE_RTAI
#include <rtai_sched.h>
#include <rtai_sem.h>
#endif


#include <rtdm/rtdm_driver.h>

#include "../include/ecrt.h"
#include "../include/ec_rtdm.h"

#ifdef ENABLE_XENOMAI
#define my_mutex_create(X,Y)  rt_mutex_create(X, Y)
#define my_mutex_acquire(X,Y) rt_mutex_acquire(X,Y)
#define my_mutex_release(X)   rt_mutex_release(X)
#define my_mutex_delete(X)    rt_mutex_delete(X)
#endif

#ifdef ENABLE_RTAI
#define my_mutex_create(X,Y)  rt_sem_init(X, 1)
#define my_mutex_acquire(X,Y) rt_sem_wait(X)
#define my_mutex_release(X)   rt_sem_signal(X)
#define my_mutex_delete(X)    rt_sem_delete(X)
#define TM_INFINITE
#endif




#define EC_RTDM_MAX_MASTERS 5 /**< Maximum number of masters. */

#define EC_RTDM_GINFO(fmt, args...) \
    rtdm_printk(KERN_INFO "EtherCATrtdm: " fmt,  ##args)

#define EC_RTDM_GERR(fmt, args...) \
    rtdm_printk(KERN_ERR "EtherCATrtdm ERROR: " fmt, ##args)

#define EC_RTDM_GWARN(fmt, args...) \
    rtdm_printk(KERN_WARNING "EtherCATrtdm WARNING: " fmt, ##args)


#define EC_RTDM_INFO(devno, fmt, args...) \
    rtdm_printk(KERN_INFO "EtherCATrtdm %u: " fmt, devno, ##args)

#define EC_RTDM_ERR(devno, fmt, args...) \
    rtdm_printk(KERN_ERR "EtherCATrtdm %u ERROR: " fmt, devno, ##args)

#define EC_RTDM_WARN(devno, fmt, args...) \
    rtdm_printk(KERN_WARNING "EtherCATrtdm %u WARNING: " fmt, devno, ##args)




typedef struct _EC_RTDM_DRV_STRUCT {
    unsigned int	    isattached;
    ec_master_t *	    master;
    ec_domain_t *	    domain;	
#ifdef ENABLE_XENOMAI			   
    RT_MUTEX           masterlock;
#endif
#ifdef ENABLE_RTAI
    SEM                masterlock;
#endif
    unsigned int	    sendcnt;
    unsigned int	    reccnt;
    unsigned int	    sendcntlv;
    unsigned int	    reccntlv;
    char                    mutexname[64];
    unsigned int            masterno;
} EC_RTDM_DRV_STRUCT;


static EC_RTDM_DRV_STRUCT ec_rtdm_masterintf[EC_RTDM_MAX_MASTERS];


/* import from ethercat */
ec_master_t *ecrt_attach_master(unsigned int master_index /**< Index of the master to request. */
        );

// driver context struct: used for storing various information
typedef struct _EC_RTDM_DRV_CONTEXT {
    int                       dev_id;
    EC_RTDM_DRV_STRUCT*  pdrvstruc;
} EC_RTDM_DRV_CONTEXT;



/**********************************************************/
/*            Utilities                                   */
/**********************************************************/

static int _atoi(const char* text)
{
  char  b;
  int wd=-1;
  int nfak=1;

  wd=0;

  while ((*text==' ') || (*text=='\t')) text++;
  if (*text=='-') 
    {
      nfak=-1;
	  text++;
    }
  if (*text=='+') 
    {
      text++;
    }
  while (*text!=0)
    {
 	  b = *text;

	  if ( (b>='0') && (b<='9') )
		{
		   b=b-'0';
		   wd=wd*10+b;
		}
	  text++;
	}
  return (nfak*wd);
}


/**********************************************************/
/*            DRIVER lockcallback                         */
/**********************************************************/
void request_lock_callback(void *cb_data)
{
    EC_RTDM_DRV_STRUCT * pdrvstruc;
    
    pdrvstruc = (EC_RTDM_DRV_STRUCT*)cb_data;
    if (pdrvstruc->master)
        {
            my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
        }
}

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

void release_lock_callback(void *cb_data)
{
    EC_RTDM_DRV_STRUCT * pdrvstruc;

    pdrvstruc = (EC_RTDM_DRV_STRUCT*)cb_data;
    if (pdrvstruc->master)
      {
          my_mutex_release(&pdrvstruc->masterlock);      
      }
}




void detach_master(EC_RTDM_DRV_STRUCT * pdrvstruc)
{

  if (pdrvstruc->isattached)
      {
          EC_RTDM_INFO(pdrvstruc->masterno,"reseting callbacks!\n");
          ecrt_master_callbacks(pdrvstruc->master,NULL,NULL,NULL);
          EC_RTDM_INFO(pdrvstruc->masterno,"deleting mutex!\n");
          my_mutex_delete(&pdrvstruc->masterlock);
          pdrvstruc->master = NULL;
          pdrvstruc->isattached=0;
          EC_RTDM_INFO(pdrvstruc->masterno,"master detach done!\n");
      }
}




/**********************************************************/
/*            DRIVER OPEN                                 */
/**********************************************************/
int ec_rtdm_open_rt(struct rtdm_dev_context    *context,
                 rtdm_user_info_t           *user_info,
                 int                        oflags)
{
    EC_RTDM_DRV_CONTEXT* my_context;
    EC_RTDM_DRV_STRUCT * pdrvstruc;
    const char * p;
    int dev_no;
    unsigned int namelen;

    //int ret;
    int dev_id;

    // get the context for our driver - used to store driver info
    my_context = (EC_RTDM_DRV_CONTEXT*)context->dev_private;

    dev_no = -1;
    namelen   = strlen(context->device->driver_name);
    p = &context->device->driver_name[namelen-1];
    if (p!=&context->device->driver_name[0])
      {
	  while ((*p>='0') && (*p<='9')) 
	    {
	       p--;
	       if (p==&context->device->driver_name[0]) break;
   	    }
	  dev_no=_atoi(p);
	  if  ((dev_no!=-1) && (dev_no<EC_RTDM_MAX_MASTERS))
	    { 
		dev_id    = context->device->device_id;
    		pdrvstruc = (EC_RTDM_DRV_STRUCT*)&ec_rtdm_masterintf[dev_no];

    		my_context->dev_id         = dev_id;
    		my_context->pdrvstruc      = pdrvstruc;
		 
    		// enable interrupt in RTDM
    		return 0;	
	    }	
      }
   EC_RTDM_GERR("open - Cannot detect master device no\n");
   return -EFAULT;
}

/**********************************************************/
/*            DRIVER CLOSE                                */
/**********************************************************/
int ec_rtdm_close_rt(struct rtdm_dev_context   *context,
                  rtdm_user_info_t          *user_info)
{
    EC_RTDM_DRV_CONTEXT* my_context;
    EC_RTDM_DRV_STRUCT * pdrvstruc;

    // get the context
    my_context = (EC_RTDM_DRV_CONTEXT*)context->dev_private;

    pdrvstruc =  my_context->pdrvstruc;
    EC_RTDM_INFO(pdrvstruc->masterno,"close called!\n");
    detach_master(pdrvstruc);
    return 0;
	
}

/**********************************************************/
/*            DRIVER IOCTL                                */
/**********************************************************/
int ec_rtdm_ioctl_rt(struct rtdm_dev_context   *context,
                  rtdm_user_info_t          *user_info,
                  int                       request,
                  void                      *arg)
{
    EC_RTDM_DRV_CONTEXT* my_context;
    EC_RTDM_DRV_STRUCT * pdrvstruc;
    int ret;
    unsigned int l_ioctlvalue[]={0,0,0,0,0,0,0,0};
    ec_domain_state_t ds;
    ec_master_state_t ms;
    uint64_t app_time;


    ret = 0;

    // get the context
    my_context = (EC_RTDM_DRV_CONTEXT*)context->dev_private;
    pdrvstruc =  my_context->pdrvstruc;
    
    switch (request) {
    case EC_RTDM_MASTERSTATE:
    {
		if (!pdrvstruc->isattached)
            {
                return -EFAULT;
            }
		if (pdrvstruc->master)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                
                ecrt_master_state(pdrvstruc->master, &ms);
                
                my_mutex_release(&pdrvstruc->masterlock);
                
            }
        if  (rtdm_rw_user_ok(user_info, arg, sizeof(ms)))
            {
                // copy data to user
                if (rtdm_copy_to_user(user_info, arg, &ms,sizeof(ms)))
                    {
                        return -EFAULT;
                    }
            }
        
    }
    break;
    case EC_RTDM_DOMAINSTATE:
    {
		if (!pdrvstruc->isattached)
            {
                return -EFAULT;
            }
	   	if (pdrvstruc->domain)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                
                ecrt_domain_state(pdrvstruc->domain, &ds);
                
                my_mutex_release(&pdrvstruc->masterlock);
            }
        if  (rtdm_rw_user_ok(user_info, arg, sizeof(ds)))
            {
                // copy data to user
                if (rtdm_copy_to_user(user_info, arg, &ds,sizeof(ds)))
                    {
                        return -EFAULT;
                    }
            }
    }
    break;
    case EC_RTDM_MASTER_RECEIVE:
    {	       
        if (pdrvstruc->isattached)
            {
                if (pdrvstruc->master)
                    {
                        my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                        ecrt_master_receive(pdrvstruc->master);
                        pdrvstruc->reccnt++;
                        my_mutex_release(&pdrvstruc->masterlock);
                    }
            }
    }
    break;
    case EC_RTDM_DOMAIN_PROCESS:
    {	       
        if (pdrvstruc->isattached)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                ecrt_domain_process(pdrvstruc->domain);
                my_mutex_release(&pdrvstruc->masterlock);
            }
    }
    break;
    case EC_RTDM_MASTER_SEND:
    {
        
        if (pdrvstruc->isattached)
            {
                if (pdrvstruc->master)
                    {
                        my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                        ecrt_master_send(pdrvstruc->master);
                        pdrvstruc->sendcnt++;
                        my_mutex_release(&pdrvstruc->masterlock);
                    }
            }
    }
    break;
    case EC_RTDM_DOMAIN_QUEQUE:
    {	       
        if (pdrvstruc->isattached)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                ecrt_domain_queue(pdrvstruc->domain);
                my_mutex_release(&pdrvstruc->masterlock);
            }
    }
    break;

    case EC_RTDM_MASTER_APP_TIME:
    {
		if (!pdrvstruc->isattached)
            {
                rtdm_printk("ERROR : No Master attached\n");
                return -EFAULT;
            }
        if (rtdm_safe_copy_from_user(user_info, &app_time, arg, sizeof(app_time)))
            {
                rtdm_printk("ERROR : can't copy data to driver\n");
                return -EFAULT;
            }
            
        if (pdrvstruc->master)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                
                ecrt_master_application_time(pdrvstruc->master, app_time);
                my_mutex_release(&pdrvstruc->masterlock);
                
            }
    }
    break;
    case EC_RTDM_SYNC_REF_CLOCK:
    {
		if (!pdrvstruc->isattached)
            {
                return -EFAULT;
            }
        if (pdrvstruc->master)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                
                ecrt_master_sync_reference_clock(pdrvstruc->master);
                
                my_mutex_release(&pdrvstruc->masterlock);
                
            }
    }
    break;
    case EC_RTDM_SYNC_SLAVE_CLOCK:
    {
		if (!pdrvstruc->isattached)
            {
                return -EFAULT;
            }
        if (pdrvstruc->master)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                
                ecrt_master_sync_slave_clocks(pdrvstruc->master);
                
                my_mutex_release(&pdrvstruc->masterlock);
                
            }
    }
    break;
    case EC_RTDM_MASTER_SYNC_MONITOR_QUEQUE:
    {
		if (!pdrvstruc->isattached)
            {
                return -EFAULT;
            }
        if (pdrvstruc->master)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                ecrt_master_sync_monitor_queue(pdrvstruc->master);
                my_mutex_release(&pdrvstruc->masterlock);
            }
    }
    break;
    case EC_RTDM_MASTER_SYNC_MONITOR_PROCESS:
    {
        uint32_t ret;
		if (!pdrvstruc->isattached)
            {
                return -EFAULT;
            }
        if (pdrvstruc->master)
            {
                my_mutex_acquire(&pdrvstruc->masterlock,TM_INFINITE);
                ret = ecrt_master_sync_monitor_process(pdrvstruc->master);
                my_mutex_release(&pdrvstruc->masterlock);
                if (rtdm_safe_copy_to_user(user_info, arg, &ret, sizeof(ret)))
                    {
                        EC_RTDM_ERR(pdrvstruc->masterno,"copy to user param failed!\n");
                        ret=-EFAULT;
                    }
            }
    }
    break;
    case EC_RTDM_MSTRATTACH:
    {
        unsigned int mstridx;
        
        mstridx = 0;
        ret = 0;
        
        EC_RTDM_INFO(pdrvstruc->masterno,"Master attach start!\n");
        if (user_info) 
            {
                if (rtdm_read_user_ok(user_info, arg, sizeof(unsigned int)))
                    {
                        if (rtdm_copy_from_user(user_info, &l_ioctlvalue[0], arg,sizeof(unsigned int))==0)
                            {
                                pdrvstruc->domain = (ec_domain_t*)l_ioctlvalue[0];
                            }
                        else
                            {
                                EC_RTDM_ERR(pdrvstruc->masterno,"copy user param failed!\n");
                                ret=-EFAULT;
                            }		
                    }
                else
                    { 
                        EC_RTDM_ERR(pdrvstruc->masterno,"user parameter domain missing!\n");
                        ret=-EFAULT;
                    }	
            }
		if (ret!=0) 
            {
                return ret;
            }
        
		if ( (pdrvstruc->master) && (pdrvstruc->isattached))
            // master is allready attached
            {
                // master is allready attached
                EC_RTDM_ERR(pdrvstruc->masterno,"Master is allready attached!\n");
                ret = -EFAULT;
            }
	    else
            {
                //mstr=ecrt_request_master(0);
                mstridx = pdrvstruc->masterno;
	        	
                pdrvstruc->master=ecrt_attach_master(mstridx);
                
                if (pdrvstruc->master)
                    {
                        // Ok
                        EC_RTDM_INFO(pdrvstruc->masterno,"Master searching for domain!\n");
                        pdrvstruc->domain = ecrt_master_find_domain(pdrvstruc->master,l_ioctlvalue[0]);
                        if (!pdrvstruc->domain)
                            {
                                //
                                EC_RTDM_ERR(pdrvstruc->masterno,"Cannot find domain from index %u!\n",l_ioctlvalue[0]);
                                ret = -EFAULT;
                            }
                        else
                            {
                                
                                // set device name
                                snprintf(&pdrvstruc->mutexname[0],sizeof(pdrvstruc->mutexname)-1,"ETHrtdmLOCK%d",pdrvstruc->masterno);
                                EC_RTDM_INFO(pdrvstruc->masterno,"Creating Master mutex %s!\n",&pdrvstruc->mutexname[0]);
                                my_mutex_create(&pdrvstruc->masterlock,&pdrvstruc->mutexname[0]);
                                ecrt_master_callbacks(pdrvstruc->master, request_lock_callback, release_lock_callback, pdrvstruc);
                                EC_RTDM_INFO(pdrvstruc->masterno,"MSTR ATTACH done domain=%u!\n",(unsigned int)pdrvstruc->domain);
                                pdrvstruc->isattached=1;
                                ret = 0;
                            }
                        
                    }
                else
                    {
                        EC_RTDM_ERR(pdrvstruc->masterno,"Master attach failed!\n");
                        pdrvstruc->master = NULL;
                        ret = -EFAULT;
                    }
            }
    }
    break;
    default:
        ret = -ENOTTY;
    }
    return ret;
}


/**********************************************************/
/*            DRIVER READ                                 */
/**********************************************************/
int ec_rtdm_read_rt(struct rtdm_dev_context *context,
                    rtdm_user_info_t *user_info, void *buf, size_t nbyte)
{
    int                     ret;
#if defined(USE_THIS)
    EC_RTDM_DRV_CONTEXT* my_context;
    char                    *out_pos;
    int                     dev_id;
    rtdm_toseq_t            timeout_seq;
    int                     ret;

    out_pos = (char *)buf;
    
    my_context = (EC_RTDM_DRV_CONTEXT*)context->dev_private;
    
    // zero bytes requested ? return!
    if (nbyte == 0)
        return 0;

    // check if R/W actions to user-space are allowed
    if (user_info && !rtdm_rw_user_ok(user_info, buf, nbyte))
        return -EFAULT;

    dev_id = my_context->dev_id;

    // in case we need to check if reading is allowed (locking)
/*    if (test_and_set_bit(0, &ctx->in_lock))
        return -EBUSY;
*/
/*  // if we need to do some stuff with preemption disabled:
    rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
    // stuff here
    rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
*/

    // wait: if ctx->timeout = 0, it will block infintely until
    //       rtdm_event_signal(&ctx->irq_event); is called from our
    //       interrupt routine
    //ret = rtdm_event_timedwait(&ctx->irq_event, ctx->timeout, &timeout_seq);

    // now write the requested stuff to user-space
    if (rtdm_copy_to_user(user_info, out_pos,
                          dummy_buffer, BUFSIZE) != 0) {
        ret = -EFAULT;
    } else {
        ret = BUFSIZE;
    }
#else
    ret = -EFAULT;
#endif
    return ret;
}

/**********************************************************/
/*            DRIVER WRITE                                */
/**********************************************************/
int ec_rtdm_write_rt(struct rtdm_dev_context *context,
                   rtdm_user_info_t *user_info,
                   const void *buf, size_t nbyte)
{
    int                     ret;

#if defined(USE_THIS)
    int                     dev_id;
    char                    *in_pos = (char *)buf;

    EC_RTDM_DRV_CONTEXT* my_context;
    

    my_context = (EC_RTDM_DRV_CONTEXT*)context->dev_private;
    

    if (nbyte == 0)
        return 0;
    if (user_info && !rtdm_read_user_ok(user_info, buf, nbyte))
        return -EFAULT;

    dev_id = my_context->dev_id;

    if (rtdm_copy_from_user(user_info, dummy_buffer,
                             in_pos, BUFSIZE) != 0) {
        ret = -EFAULT;
    } else {
       ret = BUFSIZE;
    }
#else
    ret = -EFAULT;
#endif
    // used when it is atomic
//   rtdm_mutex_unlock(&ctx->out_lock);
    return ret;
}

/**********************************************************/
/*            DRIVER OPERATIONS                           */
/**********************************************************/

// Template

static struct rtdm_device ec_rtdm_device_t = {
    struct_version:     RTDM_DEVICE_STRUCT_VER,

    device_flags:       RTDM_NAMED_DEVICE,
    context_size:   	sizeof(EC_RTDM_DRV_CONTEXT),
    device_name:        EC_RTDM_DEV_FILE_NAME,

/* open and close functions are not real-time safe due kmalloc
   and kfree. If you do not use kmalloc and kfree, and you made
   sure that there is no syscall in the open/close handler, you
   can declare the open_rt and close_rt handler.
*/
    open_rt:            NULL,
    open_nrt:           ec_rtdm_open_rt,

    ops: {
        close_rt:       NULL,
        close_nrt:      ec_rtdm_close_rt,

        ioctl_rt:       ec_rtdm_ioctl_rt,
        ioctl_nrt:      ec_rtdm_ioctl_rt, // rtdm_mmap_to_user is not RT safe

        read_rt:        ec_rtdm_read_rt,
        read_nrt:       NULL,

        write_rt:       ec_rtdm_write_rt,
        write_nrt:      NULL,

        recvmsg_rt:     NULL,
        recvmsg_nrt:    NULL,

        sendmsg_rt:     NULL,
        sendmsg_nrt:    NULL,
    },

    device_class:       RTDM_CLASS_EXPERIMENTAL,
    device_sub_class:   222,
    driver_name:        EC_RTDM_DEV_FILE_NAME,
    driver_version:     RTDM_DRIVER_VER(1,0,1),
    peripheral_name:    EC_RTDM_DEV_FILE_NAME,
    provider_name:      "EtherLab Community",
//    proc_name:          ethcatrtdm_device.device_name,
};


static struct rtdm_device ec_rtdm_devices[EC_RTDM_MAX_MASTERS];


/**********************************************************/
/*            INIT DRIVER                                 */
/**********************************************************/
int init_module(void)
{
	unsigned int i;
    int ret;

    ret = 0; 	
    
    EC_RTDM_GINFO("Initlializing EtherCAT RTDM Interface to Igh EtherCAT Master\n");
    memset(&ec_rtdm_masterintf[0],0,sizeof(ec_rtdm_masterintf));
    for (i=0;( (i<EC_RTDM_MAX_MASTERS) && (ret==0) ) ;i++)
      {	 
    	// master no to struct
    	ec_rtdm_masterintf[i].masterno = i;
        // copy from template
        memcpy(&ec_rtdm_devices[i],&ec_rtdm_device_t,sizeof(ec_rtdm_devices[0]));

        // set device name
        snprintf(&ec_rtdm_devices[i].device_name[0],RTDM_MAX_DEVNAME_LEN,"%s%d",EC_RTDM_DEV_FILE_NAME,i);
        // set proc_name
	    ec_rtdm_devices[i].proc_name = &ec_rtdm_devices[i].device_name[0];
	    ec_rtdm_devices[i].driver_name = &ec_rtdm_devices[i].device_name[0];
		ec_rtdm_devices[i].peripheral_name = &ec_rtdm_devices[i].device_name[0];
	
		EC_RTDM_GINFO("Registering device %s!\n",ec_rtdm_devices[i].driver_name);
		ret = rtdm_dev_register(&ec_rtdm_devices[i]);

      }	
    if (ret!=0)
      {	
    	// register m
        EC_RTDM_GERR("Initialization of EtherCAT RTDM Interface failed\n");
      }		
    return ret;
}

/**********************************************************/
/*            CLEANUP DRIVER                              */
/**********************************************************/
void cleanup_module(void)
{
    unsigned int i;

    EC_RTDM_GINFO("Cleanup EtherCAT RTDM Interface \n");
    for (i=0;i<EC_RTDM_MAX_MASTERS;i++)
      {
         if (ec_rtdm_masterintf[i].isattached)
           {
	          detach_master(&ec_rtdm_masterintf[i]);
	       }
 		 EC_RTDM_GINFO("Unregistering device %s!\n",ec_rtdm_devices[i].driver_name);
         rtdm_dev_unregister(&ec_rtdm_devices[i],1000);
    }
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EtherCAT RTDM Interface");