#include "comedi_internal.h"
 
+/**
+ * struct comedi_file - per-file private data for comedi device
+ * @dev: comedi_device struct
+ * @read_subdev: current "read" subdevice
+ * @write_subdev: current "write" subdevice
+ * @last_detach_count: last known detach count
+ * @last_attached: last known attached/detached state
+ */
+struct comedi_file {
+       struct comedi_device *dev;
+       struct comedi_subdevice *read_subdev;
+       struct comedi_subdevice *write_subdev;
+       unsigned int last_detach_count;
+       bool last_attached:1;
+};
+
 #define COMEDI_NUM_MINORS 0x100
 #define COMEDI_NUM_SUBDEVICE_MINORS    \
        (COMEDI_NUM_MINORS - COMEDI_NUM_BOARD_MINORS)
        return dev->write_subdev;
 }
 
+static void comedi_file_reset(struct file *file)
+{
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
+       struct comedi_subdevice *s, *read_s, *write_s;
+       unsigned int minor = iminor(file_inode(file));
+
+       read_s = dev->read_subdev;
+       write_s = dev->write_subdev;
+       if (minor >= COMEDI_NUM_BOARD_MINORS) {
+               s = comedi_subdevice_from_minor(dev, minor);
+               if (s == NULL || s->subdev_flags & SDF_CMD_READ)
+                       read_s = s;
+               if (s == NULL || s->subdev_flags & SDF_CMD_WRITE)
+                       write_s = s;
+       }
+       cfp->last_attached = dev->attached;
+       cfp->last_detach_count = dev->detach_count;
+       ACCESS_ONCE(cfp->read_subdev) = read_s;
+       ACCESS_ONCE(cfp->write_subdev) = write_s;
+}
+
+static void comedi_file_check(struct file *file)
+{
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
+
+       if (cfp->last_attached != dev->attached ||
+           cfp->last_detach_count != dev->detach_count)
+               comedi_file_reset(file);
+}
+
+static struct comedi_subdevice *comedi_file_read_subdevice(struct file *file)
+{
+       struct comedi_file *cfp = file->private_data;
+
+       comedi_file_check(file);
+       return ACCESS_ONCE(cfp->read_subdev);
+}
+
+static struct comedi_subdevice *comedi_file_write_subdevice(struct file *file)
+{
+       struct comedi_file *cfp = file->private_data;
+
+       comedi_file_check(file);
+       return ACCESS_ONCE(cfp->write_subdev);
+}
+
 static int resize_async_buffer(struct comedi_device *dev,
                               struct comedi_subdevice *s, unsigned new_size)
 {
                            struct comedi_devinfo __user *arg,
                            struct file *file)
 {
-       const unsigned minor = iminor(file_inode(file));
        struct comedi_subdevice *s;
        struct comedi_devinfo devinfo;
 
        strlcpy(devinfo.driver_name, dev->driver->driver_name, COMEDI_NAMELEN);
        strlcpy(devinfo.board_name, dev->board_name, COMEDI_NAMELEN);
 
-       s = comedi_read_subdevice(dev, minor);
+       s = comedi_file_read_subdevice(file);
        if (s)
                devinfo.read_subdevice = s->index;
        else
                devinfo.read_subdevice = -1;
 
-       s = comedi_write_subdevice(dev, minor);
+       s = comedi_file_write_subdevice(file);
        if (s)
                devinfo.write_subdevice = s->index;
        else
 static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd,
                                  unsigned long arg)
 {
-       const unsigned minor = iminor(file_inode(file));
-       struct comedi_device *dev = file->private_data;
+       unsigned minor = iminor(file_inode(file));
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
        int rc;
 
        mutex_lock(&dev->mutex);
 
 static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
 {
-       const unsigned minor = iminor(file_inode(file));
-       struct comedi_device *dev = file->private_data;
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
        struct comedi_subdevice *s;
        struct comedi_async *async;
        struct comedi_buf_map *bm = NULL;
        }
 
        if (vma->vm_flags & VM_WRITE)
-               s = comedi_write_subdevice(dev, minor);
+               s = comedi_file_write_subdevice(file);
        else
-               s = comedi_read_subdevice(dev, minor);
+               s = comedi_file_read_subdevice(file);
        if (!s) {
                retval = -EINVAL;
                goto done;
 static unsigned int comedi_poll(struct file *file, poll_table *wait)
 {
        unsigned int mask = 0;
-       const unsigned minor = iminor(file_inode(file));
-       struct comedi_device *dev = file->private_data;
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
        struct comedi_subdevice *s;
 
        mutex_lock(&dev->mutex);
                goto done;
        }
 
-       s = comedi_read_subdevice(dev, minor);
+       s = comedi_file_read_subdevice(file);
        if (s && s->async) {
                poll_wait(file, &s->async->wait_head, wait);
                if (!s->busy || !comedi_is_subdevice_running(s) ||
                        mask |= POLLIN | POLLRDNORM;
        }
 
-       s = comedi_write_subdevice(dev, minor);
+       s = comedi_file_write_subdevice(file);
        if (s && s->async) {
                unsigned int bps = comedi_bytes_per_sample(s);
 
        struct comedi_async *async;
        int n, m, count = 0, retval = 0;
        DECLARE_WAITQUEUE(wait, current);
-       const unsigned minor = iminor(file_inode(file));
-       struct comedi_device *dev = file->private_data;
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
        bool on_wait_queue = false;
        bool attach_locked;
        unsigned int old_detach_count;
                goto out;
        }
 
-       s = comedi_write_subdevice(dev, minor);
+       s = comedi_file_write_subdevice(file);
        if (!s || !s->async) {
                retval = -EIO;
                goto out;
                                 * meantime!), but check the subdevice pointer
                                 * as well just in case.
                                 */
-                               new_s = comedi_write_subdevice(dev, minor);
+                               new_s = comedi_file_write_subdevice(file);
                                if (dev->attached &&
                                    old_detach_count == dev->detach_count &&
                                    s == new_s && new_s->async == async)
        struct comedi_async *async;
        int n, m, count = 0, retval = 0;
        DECLARE_WAITQUEUE(wait, current);
-       const unsigned minor = iminor(file_inode(file));
-       struct comedi_device *dev = file->private_data;
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
        unsigned int old_detach_count;
        bool become_nonbusy = false;
        bool attach_locked;
                goto out;
        }
 
-       s = comedi_read_subdevice(dev, minor);
+       s = comedi_file_read_subdevice(file);
        if (!s || !s->async) {
                retval = -EIO;
                goto out;
                 * meantime!), but check the subdevice pointer as well just in
                 * case.
                 */
-               new_s = comedi_read_subdevice(dev, minor);
+               new_s = comedi_file_read_subdevice(file);
                if (dev->attached && old_detach_count == dev->detach_count &&
                    s == new_s && new_s->async == async) {
                        if (become_nonbusy || comedi_buf_n_bytes_ready(s) == 0)
 static int comedi_open(struct inode *inode, struct file *file)
 {
        const unsigned minor = iminor(inode);
+       struct comedi_file *cfp;
        struct comedi_device *dev = comedi_dev_get_from_minor(minor);
        int rc;
 
                return -ENODEV;
        }
 
+       cfp = kzalloc(sizeof(*cfp), GFP_KERNEL);
+       if (!cfp)
+               return -ENOMEM;
+
+       cfp->dev = dev;
+
        mutex_lock(&dev->mutex);
        if (!dev->attached && !capable(CAP_NET_ADMIN)) {
                dev_dbg(dev->class_dev, "not attached and not CAP_NET_ADMIN\n");
        }
 
        dev->use_count++;
-       file->private_data = dev;
+       file->private_data = cfp;
+       comedi_file_reset(file);
        rc = 0;
 
 out:
        mutex_unlock(&dev->mutex);
-       if (rc)
+       if (rc) {
                comedi_dev_put(dev);
+               kfree(cfp);
+       }
        return rc;
 }
 
 static int comedi_fasync(int fd, struct file *file, int on)
 {
-       struct comedi_device *dev = file->private_data;
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
 
        return fasync_helper(fd, file, on, &dev->async_queue);
 }
 
 static int comedi_close(struct inode *inode, struct file *file)
 {
-       struct comedi_device *dev = file->private_data;
+       struct comedi_file *cfp = file->private_data;
+       struct comedi_device *dev = cfp->dev;
        struct comedi_subdevice *s = NULL;
        int i;
 
 
        mutex_unlock(&dev->mutex);
        comedi_dev_put(dev);
+       kfree(cfp);
 
        return 0;
 }