Memory-mapped process data.
authorFlorian Pose <fp@igh-essen.com>
Fri, 10 Oct 2008 08:34:15 +0000
changeset 1258 900f1124e8f8
parent 1257 9844ac126275
child 1259 5f9d1abbee71
Memory-mapped process data.
examples/user/main.c
include/ecrt.h
lib/common.c
lib/domain.c
lib/domain.h
lib/master.c
lib/master.h
master/cdev.c
master/ioctl.h
--- a/examples/user/main.c	Fri Oct 10 07:58:48 2008 +0000
+++ b/examples/user/main.c	Fri Oct 10 08:34:15 2008 +0000
@@ -46,13 +46,29 @@
 
 /****************************************************************************/
 
+// Application parameters
+#define PRIORITY 1
+
 // Optional features
 #define CONFIGURE_PDOS  1
-#define EL3152_ALT_PDOS 0
-#define SDO_ACCESS      0
-#define VOE_ACCESS      0
-
-#define PRIORITY 1
+
+/****************************************************************************/
+
+// EtherCAT
+static ec_master_t *master = NULL;
+
+static ec_domain_t *domain1 = NULL;
+
+static ec_slave_config_t *sc_ana_in = NULL;
+
+// Timer
+static unsigned int sig_alarms = 0;
+static unsigned int user_alarms = 0;
+
+/****************************************************************************/
+
+// process data
+static uint8_t *domain1_pd = NULL;
 
 #define BusCouplerPos  0, 0
 #define AnaOutSlavePos 0, 1
@@ -64,29 +80,20 @@
 #define Beckhoff_EL3152 0x00000002, 0x0c503052
 #define Beckhoff_EL4102 0x00000002, 0x10063052
 
-/****************************************************************************/
-
-static unsigned int sig_alarms = 0;
-static unsigned int user_alarms = 0;
-
 // offsets for Pdo entries
-static unsigned int off_ana_in;
+static unsigned int off_ana_in_status;
+static unsigned int off_ana_in_value;
 static unsigned int off_ana_out;
 static unsigned int off_dig_out;
 
 const static ec_pdo_entry_reg_t domain1_regs[] = {
-#if EL3152_ALT_PDOS
-    {AnaInSlavePos,  Beckhoff_EL3152, 0x6401, 1, &off_ana_in},
-#else
-    {AnaInSlavePos,  Beckhoff_EL3152, 0x3101, 2, &off_ana_in},
-#endif
+    {AnaInSlavePos,  Beckhoff_EL3152, 0x3101, 1, &off_ana_in_status},
+    {AnaInSlavePos,  Beckhoff_EL3152, 0x3101, 2, &off_ana_in_value},
 	{AnaOutSlavePos, Beckhoff_EL4102, 0x3001, 1, &off_ana_out},
 	{DigOutSlavePos, Beckhoff_EL2004, 0x3001, 1, &off_dig_out},
 	{}
 };
 
-static ec_slave_config_t *sc_ana_in = NULL;
-
 /*****************************************************************************/
 
 #if CONFIGURE_PDOS
@@ -102,17 +109,6 @@
     {0x6401, 2, 16}  // channel 2 value (alt.)
 };
 
-#if EL3152_ALT_PDOS
-static ec_pdo_info_t el3152_pdos[] = {
-    {0x1A10, 2, el3152_pdo_entries + 4},
-};
-
-static ec_sync_info_t el3152_syncs[] = {
-    {2, EC_DIR_OUTPUT},
-    {3, EC_DIR_INPUT, 1, el3152_pdos},
-    {0xff}
-};
-#else
 static ec_pdo_info_t el3152_pdos[] = {
     {0x1A00, 2, el3152_pdo_entries},
     {0x1A01, 2, el3152_pdo_entries + 2}
@@ -123,7 +119,6 @@
     {3, EC_DIR_INPUT, 2, el3152_pdos},
     {0xff}
 };
-#endif
 
 // Analog out -------------------------
 
@@ -168,6 +163,28 @@
 
 /****************************************************************************/
 
+void cyclic_task()
+{
+    ecrt_master_receive(master);
+    ecrt_domain_process(domain1);
+
+#if 0
+#if EL3152_ALT_PDOS
+    printf("AnaIn: value %u\n",
+            EC_READ_U16(domain1_pd + off_ana_in_value));
+#else
+    printf("AnaIn: state %u value %u\n",
+            EC_READ_U8(domain1_pd + off_ana_in_status),
+            EC_READ_U16(domain1_pd + off_ana_in_value));
+#endif
+#endif
+
+    ecrt_domain_queue(domain1);
+    ecrt_master_send(master);
+}
+
+/****************************************************************************/
+
 void signal_handler(int signum) {
     switch (signum) {
         case SIGALRM:
@@ -180,8 +197,6 @@
 
 int main(int argc, char **argv)
 {
-	ec_master_t *master;
-	ec_domain_t *domain1;
 	ec_slave_config_t *sc;
     struct sigaction sa;
     struct itimerval tv;
@@ -244,6 +259,10 @@
     if (ecrt_master_activate(master))
         return -1;
 
+    if (!(domain1_pd = ecrt_domain_data(domain1))) {
+        return -1;
+    }
+
 #if PRIORITY
     pid_t pid = getpid();
     if (setpriority(PRIO_PROCESS, pid, -19))
@@ -271,7 +290,7 @@
 
     printf("Started.\n");
 	while (1) {
-        sleep(1);
+        sleep(1); // always interrupted by SIGALRM
 
 #if 0
         struct timeval t;
@@ -280,9 +299,7 @@
 #endif
 
         while (sig_alarms != user_alarms) {
-            ecrt_master_receive(master);
-            ecrt_master_send(master);
-
+            cyclic_task();
             user_alarms++;
         }
 	}
--- a/include/ecrt.h	Fri Oct 10 07:58:48 2008 +0000
+++ b/include/ecrt.h	Fri Oct 10 08:34:15 2008 +0000
@@ -780,6 +780,8 @@
                                                    registrations. */
         );
 
+#ifdef __KERNEL__
+
 /** Returns the current size of the domain's process data.
  *
  * \return Size of the process data image.
@@ -788,8 +790,6 @@
         const ec_domain_t *domain /**< Domain. */
         );
 
-#ifdef __KERNEL__
-
 /** Provide external memory to store the domain's process data.
  *
  * Call this after all Pdo entries have been registered and before activating
@@ -808,12 +808,14 @@
 
 /** Returns the domain's process data.
  *
- * If external memory was provided with ecrt_domain_external_memory(), the
- * returned pointer will contain the address of that memory. Otherwise it will
- * point to the internally allocated memory.
- *
- * \attention In case of internal domain memory (default), this method may not
- * be called before ecrt_master_activate().
+ * - In kernel context: If external memory was provided with
+ * ecrt_domain_external_memory(), the returned pointer will contain the
+ * address of that memory. Otherwise it will point to the internally allocated
+ * memory. In the latter case, this method may not be called before
+ * ecrt_master_activate().
+ *
+ * - In userspace context: This method has to be called after
+ * ecrt_master_activate() to get the mapped domain process data memory.
  *
  * \return Pointer to the process data memory.
  */
--- a/lib/common.c	Fri Oct 10 07:58:48 2008 +0000
+++ b/lib/common.c	Fri Oct 10 08:34:15 2008 +0000
@@ -39,6 +39,7 @@
 #include <errno.h>
 #include <string.h>
 #include <sys/ioctl.h>
+#include <sys/mman.h>
 
 #include "master.h"
 #include "master/ioctl.h"
@@ -65,6 +66,9 @@
         return 0;
     }
 
+    master->process_data = NULL;
+    master->process_data_size = 0;
+
     snprintf(path, MAX_PATH_LEN - 1, "/dev/EtherCAT%u", master_index);
 
     master->fd = open(path, O_RDWR);
@@ -89,6 +93,10 @@
 
 void ecrt_release_master(ec_master_t *master)
 {
+    if (master->process_data)  {
+        munmap(master->process_data, master->process_data_size);
+    }
+
     close(master->fd);
     free(master);
 }
--- a/lib/domain.c	Fri Oct 10 07:58:48 2008 +0000
+++ b/lib/domain.c	Fri Oct 10 08:34:15 2008 +0000
@@ -38,7 +38,15 @@
 
 /*****************************************************************************/
 
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
 #include "domain.h"
+#include "master.h"
+#include "master/ioctl.h"
 
 /*****************************************************************************/
 
@@ -68,19 +76,43 @@
 
 uint8_t *ecrt_domain_data(ec_domain_t *domain)
 {
-    return 0;
+    if (!domain->process_data) {
+        int offset = 0;
+
+        offset = ioctl(domain->master->fd, EC_IOCTL_DOMAIN_OFFSET,
+                domain->index);
+        if (offset == -1) {
+            fprintf(stderr, "Failed to get domain offset: %s\n",
+                    strerror(errno));
+            return NULL; 
+        }
+    
+        domain->process_data = domain->master->process_data + offset;
+    }
+
+    return domain->process_data;
 }
 
 /*****************************************************************************/
 
 void ecrt_domain_process(ec_domain_t *domain)
 {
+    if (ioctl(domain->master->fd, EC_IOCTL_DOMAIN_PROCESS,
+                domain->index) == -1) {
+        fprintf(stderr, "Failed to process domain offset: %s\n",
+                strerror(errno));
+    }
 }
 
 /*****************************************************************************/
 
 void ecrt_domain_queue(ec_domain_t *domain)
 {
+    if (ioctl(domain->master->fd, EC_IOCTL_DOMAIN_QUEUE,
+                domain->index) == -1) {
+        fprintf(stderr, "Failed to queue domain offset: %s\n",
+                strerror(errno));
+    }
 }
 
 /*****************************************************************************/
--- a/lib/domain.h	Fri Oct 10 07:58:48 2008 +0000
+++ b/lib/domain.h	Fri Oct 10 08:34:15 2008 +0000
@@ -38,6 +38,7 @@
 struct ec_domain {
     unsigned int index;
     ec_master_t *master;
+    uint8_t *process_data;
 };
 
 /*****************************************************************************/
--- a/lib/master.c	Fri Oct 10 07:58:48 2008 +0000
+++ b/lib/master.c	Fri Oct 10 08:34:15 2008 +0000
@@ -36,6 +36,7 @@
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
+#include <sys/mman.h>
 
 #include "master.h"
 #include "domain.h"
@@ -64,6 +65,7 @@
 
     domain->index = (unsigned int) index;
     domain->master = master;
+    domain->process_data = NULL;
     return domain;
 }
 
@@ -106,12 +108,27 @@
 
 int ecrt_master_activate(ec_master_t *master)
 {
-    if (ioctl(master->fd, EC_IOCTL_ACTIVATE, NULL) == -1) {
+    if (ioctl(master->fd, EC_IOCTL_ACTIVATE,
+                &master->process_data_size) == -1) {
         fprintf(stderr, "Failed to activate master: %s\n",
                 strerror(errno));
         return -1; 
     }
 
+    if (master->process_data_size) {
+        master->process_data = mmap(0, master->process_data_size,
+                PROT_READ | PROT_WRITE, MAP_PRIVATE, master->fd, 0);
+        if (master->process_data == MAP_FAILED) {
+            fprintf(stderr, "Failed to map process data: %s", strerror(errno));
+            master->process_data = NULL;
+            master->process_data_size = 0;
+            return -1;
+        }
+
+        // Access the mapped region to cause the initial page fault
+        printf("pd: %x\n", master->process_data[0]);
+    }
+
     return 0;
 }
 
--- a/lib/master.h	Fri Oct 10 07:58:48 2008 +0000
+++ b/lib/master.h	Fri Oct 10 08:34:15 2008 +0000
@@ -37,6 +37,8 @@
 
 struct ec_master {
     int fd;
+    uint8_t *process_data;
+    size_t process_data_size;
 };
 
 /*****************************************************************************/
--- a/master/cdev.c	Fri Oct 10 07:58:48 2008 +0000
+++ b/master/cdev.c	Fri Oct 10 08:34:15 2008 +0000
@@ -39,6 +39,8 @@
 /*****************************************************************************/
 
 #include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
 
 #include "cdev.h"
 #include "master.h"
@@ -52,6 +54,10 @@
 int eccdev_open(struct inode *, struct file *);
 int eccdev_release(struct inode *, struct file *);
 long eccdev_ioctl(struct file *, unsigned int, unsigned long);
+int eccdev_mmap(struct file *, struct vm_area_struct *);
+
+static struct page *eccdev_vma_nopage(
+        struct vm_area_struct *, unsigned long, int *);
 
 /*****************************************************************************/
 
@@ -59,7 +65,12 @@
     .owner          = THIS_MODULE,
     .open           = eccdev_open,
     .release        = eccdev_release,
-    .unlocked_ioctl = eccdev_ioctl
+    .unlocked_ioctl = eccdev_ioctl,
+    .mmap           = eccdev_mmap
+};
+
+struct vm_operations_struct eccdev_vm_ops = {
+    .nopage = eccdev_vma_nopage
 };
 
 /** \endcond */
@@ -71,6 +82,8 @@
 typedef struct {
     ec_cdev_t *cdev;
     unsigned int requested;
+    uint8_t *process_data;
+    size_t process_data_size;
 } ec_cdev_priv_t;
 
 /*****************************************************************************/
@@ -1462,12 +1475,48 @@
         ec_cdev_priv_t *priv /**< Private data structure of file handle. */
         )
 {
+    ec_domain_t *domain;
+    off_t offset;
+    
 	if (unlikely(!priv->requested))
 		return -EPERM;
 
+    /* Get the sum of the domains' process data sizes. */
+    
+    priv->process_data_size = 0;
+
+    if (down_interruptible(&master->master_sem))
+        return -EINTR;
+
+    list_for_each_entry(domain, &master->domains, list) {
+        priv->process_data_size += ecrt_domain_size(domain);
+    }
+    
+    up(&master->master_sem);
+
+    if (priv->process_data_size) {
+        priv->process_data = vmalloc(priv->process_data_size);
+        if (!priv->process_data) {
+            priv->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, priv->process_data + offset);
+            offset += ecrt_domain_size(domain);
+        }
+    }
+
     if (ecrt_master_activate(master))
         return -EIO;
 
+    if (copy_to_user((void __user *) arg,
+                &priv->process_data_size, sizeof(size_t)))
+        return -EFAULT;
+
     return 0;
 }
 
@@ -1788,6 +1837,94 @@
     return ret;
 }
 
+/*****************************************************************************/
+
+/** Gets the domain's offset in the total process data.
+ */
+int ec_cdev_ioctl_domain_offset(
+        ec_master_t *master, /**< EtherCAT master. */
+        unsigned long arg, /**< ioctl() argument. */
+        ec_cdev_priv_t *priv /**< Private data structure of file handle. */
+        )
+{
+    int offset = 0;
+    const ec_domain_t *domain;
+
+	if (unlikely(!priv->requested))
+        return -EPERM;
+
+    if (down_interruptible(&master->master_sem)) {
+        return -EINTR;
+    }
+
+    list_for_each_entry(domain, &master->domains, list) {
+        if (domain->index == arg) {
+            up(&master->master_sem);
+            return offset;
+        }
+        offset += ecrt_domain_size(domain);
+    }
+
+    up(&master->master_sem);
+    return -ESRCH;
+}
+
+/*****************************************************************************/
+
+/** Process the domain.
+ */
+int ec_cdev_ioctl_domain_process(
+        ec_master_t *master, /**< EtherCAT master. */
+        unsigned long arg, /**< ioctl() argument. */
+        ec_cdev_priv_t *priv /**< Private data structure of file handle. */
+        )
+{
+    ec_domain_t *domain;
+
+	if (unlikely(!priv->requested))
+        return -EPERM;
+
+    if (down_interruptible(&master->master_sem))
+        return -EINTR;
+
+    if (!(domain = ec_master_find_domain(master, arg))) {
+        up(&master->master_sem);
+        return -ESRCH;
+    }
+
+    ecrt_domain_process(domain);
+    up(&master->master_sem);
+    return 0;
+}
+
+/*****************************************************************************/
+
+/** Queue the domain.
+ */
+int ec_cdev_ioctl_domain_queue(
+        ec_master_t *master, /**< EtherCAT master. */
+        unsigned long arg, /**< ioctl() argument. */
+        ec_cdev_priv_t *priv /**< Private data structure of file handle. */
+        )
+{
+    ec_domain_t *domain;
+
+	if (unlikely(!priv->requested))
+        return -EPERM;
+
+    if (down_interruptible(&master->master_sem))
+        return -EINTR;
+
+    if (!(domain = ec_master_find_domain(master, arg))) {
+        up(&master->master_sem);
+        return -ESRCH;
+    }
+
+    ecrt_domain_queue(domain);
+    up(&master->master_sem);
+    return 0;
+}
+
 /******************************************************************************
  * File operations
  *****************************************************************************/
@@ -1808,6 +1945,8 @@
 
     priv->cdev = cdev;
     priv->requested = 0;
+    priv->process_data = NULL;
+    priv->process_data_size = 0;
 
     filp->private_data = priv;
     if (master->debug_level)
@@ -1827,6 +1966,9 @@
     if (priv->requested)
         ecrt_release_master(master);
 
+    if (priv->process_data)
+        vfree(priv->process_data);
+
     if (master->debug_level)
         EC_DBG("File closed.\n");
     kfree(priv);
@@ -1953,9 +2095,68 @@
             if (!(filp->f_mode & FMODE_WRITE))
 				return -EPERM;
 			return ec_cdev_ioctl_sc_sdo(master, arg, priv);
+        case EC_IOCTL_DOMAIN_OFFSET:
+			return ec_cdev_ioctl_domain_offset(master, arg, priv);
+        case EC_IOCTL_DOMAIN_PROCESS:
+            if (!(filp->f_mode & FMODE_WRITE))
+				return -EPERM;
+			return ec_cdev_ioctl_domain_process(master, arg, priv);
+        case EC_IOCTL_DOMAIN_QUEUE:
+            if (!(filp->f_mode & FMODE_WRITE))
+				return -EPERM;
+			return ec_cdev_ioctl_domain_queue(master, arg, priv);
         default:
             return -ENOTTY;
     }
 }
 
 /*****************************************************************************/
+
+int eccdev_mmap(
+        struct file *filp,
+        struct vm_area_struct *vma
+        )
+{
+    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data;
+
+    if (priv->cdev->master->debug_level)
+        EC_DBG("mmap()\n");
+
+    vma->vm_ops = &eccdev_vm_ops;
+    vma->vm_flags |= VM_RESERVED; /* Pages will not be swapped out */
+    vma->vm_private_data = priv;
+
+    return 0;
+}
+
+/*****************************************************************************/
+
+struct page *eccdev_vma_nopage(
+        struct vm_area_struct *vma,
+        unsigned long address,
+        int *type
+        )
+{
+    unsigned long offset;
+    struct page *page = NOPAGE_SIGBUS;
+    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) vma->vm_private_data;
+
+    offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT);
+
+    if (offset >= priv->process_data_size)
+        return NOPAGE_SIGBUS;
+
+    page = vmalloc_to_page(priv->process_data + offset);
+
+    if (priv->cdev->master->debug_level)
+        EC_DBG("Nopage fault vma, address = %#lx, offset = %#lx, page = %p\n",
+                address, offset, page);
+
+    get_page(page);
+    if (type)
+        *type = VM_FAULT_MINOR;
+
+    return page;
+}
+
+/*****************************************************************************/
--- a/master/ioctl.h	Fri Oct 10 07:58:48 2008 +0000
+++ b/master/ioctl.h	Fri Oct 10 08:34:15 2008 +0000
@@ -82,7 +82,7 @@
 #define EC_IOCTL_REQUEST                EC_IO(0x16)
 #define EC_IOCTL_CREATE_DOMAIN          EC_IO(0x17)
 #define EC_IOCTL_CREATE_SLAVE_CONFIG  EC_IOWR(0x18, ec_ioctl_config_t)
-#define EC_IOCTL_ACTIVATE               EC_IO(0x19)
+#define EC_IOCTL_ACTIVATE              EC_IOR(0x19, size_t)
 #define EC_IOCTL_SEND                   EC_IO(0x1a)
 #define EC_IOCTL_RECEIVE                EC_IO(0x1b)
 #define EC_IOCTL_SC_SYNC               EC_IOW(0x1c, ec_ioctl_config_t)
@@ -92,6 +92,9 @@
 #define EC_IOCTL_SC_CLEAR_ENTRIES      EC_IOW(0x1f, ec_ioctl_config_pdo_t)
 #define EC_IOCTL_SC_REG_PDO_ENTRY     EC_IOWR(0x20, ec_ioctl_reg_pdo_entry_t)
 #define EC_IOCTL_SC_SDO                EC_IOW(0x21, ec_ioctl_sc_sdo_t)
+#define EC_IOCTL_DOMAIN_OFFSET          EC_IO(0x22)
+#define EC_IOCTL_DOMAIN_PROCESS         EC_IO(0x23)
+#define EC_IOCTL_DOMAIN_QUEUE           EC_IO(0x24)
 
 #define EC_IOCTL_STRING_SIZE 64