/****************************************************************************** * * $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 * . * * 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 #include #ifdef ENABLE_XENOMAI #include #include #include #include #endif #ifdef ENABLE_RTAI #include #include #endif #include #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 sendcallback */ /**********************************************************/ void send_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); ecrt_master_send_ext(pdrvstruc->master); my_mutex_release(&pdrvstruc->masterlock); } } /*****************************************************************************/ void receive_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); ecrt_master_receive(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_nodevice->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_release_master(mstr); ecrt_master_callbacks(pdrvstruc->master, send_callback, receive_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