This character device can give daemons an interface similar to the kernel's /sys and /proc interfaces. It is a nice way to give user space drivers real device nodes in /dev.
thanks Bob Smith From 7ee4391af95b828179cf5627f8b431c3301c5057 Mon Sep 17 00:00:00 2001 From: Bob Smith <bsm...@linuxtoys.org> Date: Fri, 2 Aug 2013 16:44:48 -0700 Subject: [PATCH] PROXY, a driver that gives daemons a /sys like interface --- Documentation/proxy.txt | 36 ++++ drivers/char/Kconfig | 8 + drivers/char/Makefile | 2 +- drivers/char/proxy.c | 539 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 584 insertions(+), 1 deletion(-) create mode 100644 Documentation/proxy.txt create mode 100644 drivers/char/proxy.c diff --git a/Documentation/proxy.txt b/Documentation/proxy.txt new file mode 100644 index 0000000..6b9206a --- /dev/null +++ b/Documentation/proxy.txt @@ -0,0 +1,36 @@ +Proxy Character Devices + + +Proxy is a small character device that connects two user space +processes. It is intended to give user space daemons a /sys like +interface for configuration and status. + +As an example consider a daemon that controls a stepper motor. The +daemon would create and open one proxy device to read and write +configuration (/dev/stepper/config) and another proxy device to +accept a motor step count (/dev/stepper/count). +Shell commands to illustrate this example: + $ stepper_daemon # start the stepper control daemon + $ # Set config to full steps, clockwise and 400 step/sec + $ echo "full, cw, 400" > /dev/stepper/config + $ # Now tell the motor to step 4000 steps + $ echo 4000 > /dev/stepper/count + $ sleep 2 + $ # How many steps remain? + $ cat /dev/stepper/count + + +Proxy has some unique features that make ideal for providing a +/sys like interface. It has no internal buffering. The means +the daemon can not write until a client program is listening. +Both named pipes and pseudo-ttys have internal buffers. + +Proxy will succeed on a write of zero bytes. A zero byte write +gives the client an EOF. The daemon in the example above would +use a zero byte write in the last command after it had written the +number of steps remaining. No other IPC mechanism can close one +side of a device and leave the other side open. + +Proxy works well with select(), an important feature for daemons. +In contrast, the FUSE filesystem has some issues with select() on +the client side. diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 1421997..d21ea1d 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -566,6 +566,14 @@ config TELCLOCK /sys/devices/platform/telco_clock, with a number of files for controlling the behavior of this hardware. +config PROXY + tristate "Proxy char device that gives daemons a /sys-like interface" + default n + help + Proxy is a character device that minimally connects two user space + processes. It is intended to give user space daemons a /sys like + interface for configuration and status. + config DEVPORT bool depends on !M68K diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 7ff1d0d..7009038 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -48,7 +48,7 @@ obj-$(CONFIG_PC8736x_GPIO) += pc8736x_gpio.o obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_GPIO_TB0219) += tb0219.o obj-$(CONFIG_TELCLOCK) += tlclk.o - +obj-$(CONFIG_PROXY) += proxy.o obj-$(CONFIG_MWAVE) += mwave/ obj-$(CONFIG_AGP) += agp/ obj-$(CONFIG_PCMCIA) += pcmcia/ diff --git a/drivers/char/proxy.c b/drivers/char/proxy.c new file mode 100644 index 0000000..e56fa65 --- /dev/null +++ b/drivers/char/proxy.c @@ -0,0 +1,539 @@ +/* + * proxy.c: A bidirectional pipe device + * + * This device is meant as a simple proxy to connect two user-space + * programs through a device, allowing each of the user space programs + * to select() on the device. The first program to open the device gets + * immediately blocked on either reads or writes until the other side is + * opened. The idea of "two sides" is enforced by limiting the number + * of opens on the device to two. + * This device is different from named pipes and pseudo terminals in + * that it is bidirectional and it doesn't block writes when the buffer + * is full, it blocks when the buffer is full _OR_ if other end is closed. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of Version 2 of the GNU General Public License as + * published by the Free Software Foundatio + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc. ; 51 Franklin Street, Fifth Floor ; Boston, MA 02110-1301 ; USA + * + * + * Copyright (C) 2013 Demand Peripherals, Inc. + * + * Initial release: Bob Smith + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/cdev.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + + +/* Limits and other defines */ +/* The # proxy devices. Max minor # is one less than this */ +#define NUM_PX_DEVS (255) +#define DEVNAME "proxy" +#define DEBUGLEVEL (2) + + +/* Data structure definitions */ +/* This structure describes the buffer and queues in one direction */ +struct cirbuf { + char *buf; /* points to sf circular buffer */ + int widx; /* where to write next sf character */ + int ridx; /* where to read next sf character */ + int cidx; /* file closed at this index. ==-1 while open */ + wait_queue_head_t que; /* sf readers wait on this queue */ +}; + +/* This data structure describes one proxy device. There + * is one of these for each instance (minor #) of proxy. + * Since data flow is completely symmetric, we differentiate + * the two endpoints as East (e) and West (w), with the + * two corresponding directions ew and we. + */ +struct px { + int minor; /* minor number of this proxy instance */ + struct cirbuf ewbuf; + struct cirbuf webuf; + struct semaphore sem; /* lock to protect nopen */ + int nopen; /* number of opens on this device */ + struct file *east; /* used to tell which cirbuf to use */ + struct file *west; /* used to tell which cirbuf to use */ + int eastaccmode; /* Access mode (O_RDONLY, O_WRONLY) */ + int westaccmode; /* needed even after one side closes */ +}; + + +/* Function prototypes. */ +static int proxy_init_module(void); +static void proxy_exit_module(void); +static int proxy_open(struct inode *, struct file *); +static int proxy_release(struct inode *, struct file *); +static ssize_t proxy_read(struct file *, char *, size_t, loff_t *); +static ssize_t proxy_write(struct file *, const char *, size_t, loff_t *); +static unsigned int proxy_poll(struct file *, poll_table *); + + +/* Global variables */ +static int buffersize = 0x1000; /* circular buffer is 0x1000 4K */ +static unsigned char numberofdevs = NUM_PX_DEVS; +static int px_major; /* major device number */ +/* Debuglvl controls whether a printk is executed + * 0 = no printk at all + * 1 = printk on error only + * 2 = printk on errors and on init/remove + * 3 = debug prink to trace calls into proxy + * 4 = debug trace inside of proxy calls + */ +static unsigned char debuglevel = DEBUGLEVEL; /* printk verbosity */ + +struct cdev px_cdev; /* a char device global */ +dev_t px_devicenumber; /* first device number */ + +module_param(buffersize, int, S_IRUSR); +module_param(debuglevel, byte, S_IRUSR); +module_param(numberofdevs, byte, S_IRUSR); + +static struct px *px_devices; /* point to devices (minors) */ + +/* map the callbacks into this driver */ +const struct file_operations proxy_fops = { + .owner = THIS_MODULE, + .open = proxy_open, + .read = proxy_read, + .write = proxy_write, + .poll = proxy_poll, + .release = proxy_release +}; + + +/* Module description and macros */ + +MODULE_DESCRIPTION +("Transparently connects two user-space programs through a device"); +MODULE_AUTHOR("Bob Smith"); +MODULE_LICENSE("GPL"); +MODULE_PARM_DESC(buffersize, "Size of each buffer. default=4096 (4K) "); +MODULE_PARM_DESC(debuglevel, "Debug level. Higher=verbose. default=2"); +MODULE_PARM_DESC(numberofdevs, + "Create this many minor devices. default=16"); + + + +int proxy_init_module(void) +{ + int i, err; + px_devices = kmalloc(numberofdevs * sizeof(struct px), GFP_KERNEL); + if (px_devices == NULL) { + /* no memory available */ + if (debuglevel >= 1) + pr_err("%s: init fails: no memory.\n", + DEVNAME); + return 0; + } + memset(px_devices, 0, numberofdevs * sizeof(struct px)); + + /* init devices in this block */ + for (i = 0; i < numberofdevs; i++) { /* for every minor device */ + px_devices[i].minor = i; /* set minor number */ + px_devices[i].ewbuf.buf = (char *) 0; + px_devices[i].webuf.buf = (char *) 0; + px_devices[i].ewbuf.widx = 0; + px_devices[i].webuf.widx = 0; + px_devices[i].ewbuf.ridx = 0; + px_devices[i].webuf.ridx = 0; + px_devices[i].ewbuf.cidx = -1; + px_devices[i].webuf.cidx = -1; + px_devices[i].east = (struct file *) 0; /* !=0 if open */ + px_devices[i].west = (struct file *) 0; + init_waitqueue_head(&px_devices[i].ewbuf.que); + init_waitqueue_head(&px_devices[i].webuf.que); + px_devices[i].nopen = 0; +#ifdef init_MUTEX + init_MUTEX(&px_devices[i].sem); +#else + sema_init(&px_devices[i].sem, 1); +#endif + } + + /* alloc number of char devs in kernel */ + err = alloc_chrdev_region(&px_devicenumber, 0, numberofdevs, DEVNAME); + if (err < 0) { + if (debuglevel >= 1) + pr_err("%s: init fails. err=%d.\n", + DEVNAME, err); + return err; + } + px_major = MAJOR(px_devicenumber); /* save assign major */ + cdev_init(&px_cdev, &proxy_fops); /* init dev structures */ + kobject_set_name(&(px_cdev.kobj), "proxy%d", px_devicenumber); + + err = cdev_add(&px_cdev, px_devicenumber, numberofdevs); + if (err < 0) { + if (debuglevel >= 1) + pr_err("%s: init fails. err=%d.\n", + DEVNAME, err); + return err; + } + + if (debuglevel >= 2) + pr_info("%s: Installed %d minor devices on major number %d.\n", + DEVNAME, numberofdevs, px_major); + return 0; /* success */ +} + + +void proxy_exit_module(void) +{ + int i; + if (!px_devices) + return; + + for (i = 0; i < numberofdevs; i++) { + kfree(px_devices[i].ewbuf.buf); + kfree(px_devices[i].webuf.buf); + } + + cdev_del(&px_cdev); /* delete major device */ + kfree(px_devices); /* free */ + px_devices = NULL; /* reset pointer */ + unregister_chrdev_region(px_devicenumber, numberofdevs); + + if (debuglevel >= 2) + pr_info("%s: Uninstalled.\n", DEVNAME); +} + + +static int proxy_open(struct inode *inode, struct file *filp) +{ + int mnr = iminor(inode); + struct px *dev = &px_devices[mnr]; + + if (debuglevel >= 3) + pr_info("%s open. Minor#=%d.\n", DEVNAME, mnr); + + if (down_interruptible(&dev->sem)) /* prevent races on open */ + return -ERESTARTSYS; + + if (dev->nopen >= 2) { /* Only two opens please! */ + up(&dev->sem); + return -EBUSY; + } + dev->nopen = dev->nopen + 1; + + if (!dev->ewbuf.buf) { /* get east-to-west buffer */ + dev->ewbuf.buf = kmalloc(buffersize, GFP_KERNEL); + if (!dev->ewbuf.buf) { + if (debuglevel >= 1) + pr_err("%s: No memory dev=%d.\n", + DEVNAME, mnr); + up(&dev->sem); + return -ENOMEM; + } + } + if (!dev->webuf.buf) { /* get west-to-east buffer */ + dev->webuf.buf = kmalloc(buffersize, GFP_KERNEL); + if (!dev->webuf.buf) { + if (debuglevel >= 1) + pr_err("%s: No memory dev=%d.\n", + DEVNAME, mnr); + up(&dev->sem); + return -ENOMEM; + } + } + + /* store the proxy device in the file's private data */ + filp->private_data = (void *) dev; + if (dev->east == (struct file *) 0) { + dev->east = filp; /* tells west from east */ + dev->webuf.ridx = dev->webuf.widx; /* reader starts caught up */ + dev->ewbuf.cidx = -1; /* xmit is open */ + dev->webuf.cidx = -1; /* xmit is open */ + if (dev->nopen == 2) { /* wake up other end */ + wake_up_interruptible(&dev->webuf.que); + } + dev->eastaccmode = filp->f_flags; + } else if (dev->west == (struct file *) 0) { + dev->west = filp; /* tells east from west */ + dev->ewbuf.ridx = dev->ewbuf.widx; /* reader starts caught up */ + dev->webuf.cidx = -1; + dev->ewbuf.cidx = -1; + if (dev->nopen == 2) { /* wake up other end */ + wake_up_interruptible(&dev->ewbuf.que); + } + dev->westaccmode = filp->f_flags; + } else if (debuglevel >= 1) + pr_err("%s: inconsistent open count.\n", DEVNAME); + + up(&dev->sem); /* unlock sema we are done */ + + return nonseekable_open(inode, filp); /* success */ +} + + +static int proxy_release(struct inode *inode, struct file *filp) +{ + struct px *dev = (struct px *) filp->private_data; + + if (debuglevel >= 3) + pr_info("%s release. Minor#=%d.\n", DEVNAME, + ((struct px *) filp->private_data)->minor); + + if (down_interruptible(&dev->sem)) /* prevent races on close */ + return -ERESTARTSYS; + + dev->nopen = dev->nopen - 1; + + if (dev->east == filp) { + dev->east = (struct file *) 0; /* mark as not in use */ + dev->ewbuf.cidx = dev->ewbuf.widx; /* set close index */ + } else if (dev->west == filp) { + dev->west = (struct file *) 0; /* mark as not in use */ + dev->webuf.cidx = dev->webuf.widx; /* set close index */ + } else if (debuglevel >= 1) + pr_err("%s: inconsistent open count.\n", DEVNAME); + + up(&dev->sem); /* unlock sema we are done */ + + return 0; /* success */ +} + + +/* Utility to look for a full circular buffer */ +int is_full(struct cirbuf *pcbuffer) +{ + if ((pcbuffer->ridx - pcbuffer->widx == 1) || + ((pcbuffer->ridx == 0) && (pcbuffer->widx == buffersize - 1))) + return 1; + else + return 0; +} + + +static ssize_t proxy_read( + struct file *filp, char __user *buff, + size_t count, + loff_t *offset) +{ + int ret; + int xfer; /* num bytes read from proxy buf */ + int cpcnt; /* cp count and start location */ + struct cirbuf *pcbuffer; + + struct px *dev = (struct px *) filp->private_data; + + if (debuglevel >= 3) + pr_info("%s: read %d char from dev%d, off=%lld.\n", + DEVNAME, count, dev->minor, *offset); + + if (filp == dev->east) + pcbuffer = &dev->webuf; + else if (filp == dev->west) + pcbuffer = &dev->ewbuf; + else + return 0; /* should not get here */ + + /* cidx is set if writer is trying to close the file */ + if (pcbuffer->ridx == pcbuffer->cidx) + return 0; + + /* Wait here until new data is available */ + while (pcbuffer->ridx == pcbuffer->widx) { + if (filp->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + /* wait on event queue, predicate is .. */ + if (wait_event_interruptible(pcbuffer->que, + (pcbuffer->ridx != pcbuffer->widx))) { + if (debuglevel >= 1) + pr_err("%s: read failed in wait_event_interruptible\n", + DEVNAME); + return -ERESTARTSYS; + } + } + + /* Copy the new data out to the user */ + xfer = pcbuffer->widx - pcbuffer->ridx; + xfer = (xfer < 0) ? (xfer + buffersize) : xfer; + xfer = min_t(int, (int) count, xfer); + ret = xfer; /* we will handle these bytes */ + + cpcnt = buffersize - pcbuffer->ridx; + cpcnt = (cpcnt < xfer) ? cpcnt : xfer; + if (cpcnt) { + if (copy_to_user(buff, pcbuffer->buf + pcbuffer->ridx, cpcnt)) { + if (debuglevel >= 1) + pr_err("%s: read failed in copy_to_user.\n", + DEVNAME); + return -EFAULT; + } + } + + if (xfer - cpcnt > 0) { + if (copy_to_user(buff + cpcnt, pcbuffer->buf, xfer - cpcnt)) { + if (debuglevel >= 1) + pr_err("%s: read failed in copy_to_user.\n", + DEVNAME); + return -EFAULT; + } + } + pcbuffer->ridx += xfer; + pcbuffer->ridx -= (pcbuffer->ridx > buffersize - 1) ? buffersize : 0; + + /* This is what the writers have been waiting for */ + wake_up_interruptible(&pcbuffer->que); + + if (debuglevel >= 3) + pr_info("%s: read %d bytes.\n", DEVNAME, xfer); + return ret; +} + + +static ssize_t proxy_write( + struct file *filp, + const char __user *buff, + size_t count, loff_t *off) +{ + int ret; + int xfer; /* num bytes to read from user */ + int cpcnt; /* num bytes in a single copy */ + struct cirbuf *pcbuffer; + + struct px *dev = (struct px *) filp->private_data; + + if (debuglevel >= 3) + pr_info("%s: write %d char from dev%d\n", + DEVNAME, count, dev->minor); + + if (filp == dev->east) + pcbuffer = &dev->ewbuf; + else if (filp == dev->west) + pcbuffer = &dev->webuf; + else { + if (debuglevel >= 3) + pr_err("%s: can't tell east from west.\n", + DEVNAME); + return 0; /* should not get here */ + } + + /* Wait here until new data is available to write */ + while ((dev->nopen != 2) || is_full(pcbuffer)) { + if (filp->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + /* wait on event queue, predicate is .. */ + if (wait_event_interruptible(pcbuffer->que, + ((dev->nopen == 2) && (!is_full(pcbuffer))))) { + if (debuglevel >= 1) + pr_err("%s: write failed in wait_event_interruptible.\n", + DEVNAME); + return -ERESTARTSYS; + } + } + + xfer = pcbuffer->ridx - 1 - pcbuffer->widx; + xfer = (xfer < 0) ? xfer + buffersize : xfer; + xfer = min_t(int, (int) count, xfer); + ret = xfer; + + cpcnt = min(xfer, buffersize - pcbuffer->widx); + if (cpcnt) { + if (copy_from_user(pcbuffer->buf + pcbuffer->widx, + buff, cpcnt)) { + if (debuglevel >= 1) + printk( + "%s: read failed in copy_from_user.\n", + DEVNAME); + return -EFAULT; + } + } + + if (xfer - cpcnt > 0) { + if (copy_from_user(pcbuffer->buf, buff + cpcnt, xfer - cpcnt)) { + if (debuglevel >= 1) + printk( + "%s: read failed in copy_from_user.\n", + DEVNAME); + return -EFAULT; + } + } + pcbuffer->widx += xfer; + pcbuffer->widx -= (pcbuffer->widx > buffersize - 1) ? buffersize : 0; + + /* Count=0 if writer is trying to close the file */ + if (count == 0) + pcbuffer->cidx = pcbuffer->widx; + + /* This is what the readers have been waiting for */ + wake_up_interruptible(&pcbuffer->que); + + if (debuglevel >= 3) + printk("%s: wrote %d bytes.\n", DEVNAME, ret); + return ret; +} + + +static unsigned int proxy_poll(struct file *filp, poll_table *ppt) +{ + int ready_mask = 0; + struct px *dev = filp->private_data; + + poll_wait(filp, &dev->ewbuf.que, ppt); + poll_wait(filp, &dev->webuf.que, ppt); + + + if (filp == dev->west) { + /* Writable if there's space, the other end is connected, + * we haven't already written an end-of-file marker, + * the other side is not WRONLY, and our side is not O_RDONLY + */ + if (!is_full(&dev->webuf) && (dev->nopen == 2) + && (dev->webuf.cidx != dev->webuf.widx) + && ((dev->eastaccmode & O_ACCMODE) != O_WRONLY) + && ((filp->f_flags & O_ACCMODE) != O_RDONLY)) { + ready_mask = POLLOUT | POLLWRNORM; + } + /* Readable if the buffer has data or we're at end of file, + * and the other sice is not RDONLY, + * and our side is not O_WRONLY + */ + if (((dev->ewbuf.widx != dev->ewbuf.ridx) + || (dev->ewbuf.ridx == dev->ewbuf.cidx)) + && ((dev->eastaccmode & O_ACCMODE) != O_RDONLY) + && ((filp->f_flags & O_ACCMODE) != O_WRONLY)) { + ready_mask |= (POLLIN | POLLRDNORM); + } + } else if (filp == dev->east) { + if (!is_full(&dev->ewbuf) && (dev->nopen == 2) + && (dev->ewbuf.cidx != dev->ewbuf.widx) + && ((dev->westaccmode & O_ACCMODE) != O_WRONLY) + && ((filp->f_flags & O_ACCMODE) != O_RDONLY)) { + ready_mask = POLLOUT | POLLWRNORM; + } + if (((dev->webuf.widx != dev->webuf.ridx) + || (dev->webuf.ridx == dev->webuf.cidx)) + && ((dev->westaccmode & O_ACCMODE) != O_RDONLY) + && ((filp->f_flags & O_ACCMODE) != O_WRONLY)) { + ready_mask |= (POLLIN | POLLRDNORM); + } + } + + if (debuglevel >= 3) + pr_info("%s: poll returns 0x%x.\n", + DEVNAME, ready_mask); + return ready_mask; +} + +module_init(proxy_init_module); +module_exit(proxy_exit_module); -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/