static ssize_t comedi_write()

in comedi_fops.c [2461:2595]


static ssize_t comedi_write(struct file *file, const char __user *buf,
			    size_t nbytes, loff_t *offset)
{
	struct comedi_subdevice *s;
	struct comedi_async *async;
	unsigned int n, m;
	ssize_t count = 0;
	int retval = 0;
	DECLARE_WAITQUEUE(wait, current);
	struct comedi_file *cfp = file->private_data;
	struct comedi_device *dev = cfp->dev;
	bool become_nonbusy = false;
	bool attach_locked;
	unsigned int old_detach_count;

	/* Protect against device detachment during operation. */
	down_read(&dev->attach_lock);
	attach_locked = true;
	old_detach_count = dev->detach_count;

	if (!dev->attached) {
		dev_dbg(dev->class_dev, "no driver attached\n");
		retval = -ENODEV;
		goto out;
	}

	s = comedi_file_write_subdevice(file);
	if (!s || !s->async) {
		retval = -EIO;
		goto out;
	}

	async = s->async;
	if (s->busy != file || !(async->cmd.flags & CMDF_WRITE)) {
		retval = -EINVAL;
		goto out;
	}

	add_wait_queue(&async->wait_head, &wait);
	while (count == 0 && !retval) {
		unsigned int runflags;
		unsigned int wp, n1, n2;

		set_current_state(TASK_INTERRUPTIBLE);

		runflags = comedi_get_subdevice_runflags(s);
		if (!comedi_is_runflags_running(runflags)) {
			if (comedi_is_runflags_in_error(runflags))
				retval = -EPIPE;
			if (retval || nbytes)
				become_nonbusy = true;
			break;
		}
		if (nbytes == 0)
			break;

		/* Allocate all free buffer space. */
		comedi_buf_write_alloc(s, async->prealloc_bufsz);
		m = comedi_buf_write_n_allocated(s);
		n = min_t(size_t, m, nbytes);

		if (n == 0) {
			if (file->f_flags & O_NONBLOCK) {
				retval = -EAGAIN;
				break;
			}
			schedule();
			if (signal_pending(current)) {
				retval = -ERESTARTSYS;
				break;
			}
			if (s->busy != file ||
			    !(async->cmd.flags & CMDF_WRITE)) {
				retval = -EINVAL;
				break;
			}
			continue;
		}

		set_current_state(TASK_RUNNING);
		wp = async->buf_write_ptr;
		n1 = min(n, async->prealloc_bufsz - wp);
		n2 = n - n1;
		m = copy_from_user(async->prealloc_buf + wp, buf, n1);
		if (m)
			m += n2;
		else if (n2)
			m = copy_from_user(async->prealloc_buf, buf + n1, n2);
		if (m) {
			n -= m;
			retval = -EFAULT;
		}
		comedi_buf_write_free(s, n);

		count += n;
		nbytes -= n;

		buf += n;
	}
	remove_wait_queue(&async->wait_head, &wait);
	set_current_state(TASK_RUNNING);
	if (become_nonbusy && count == 0) {
		struct comedi_subdevice *new_s;

		/*
		 * To avoid deadlock, cannot acquire dev->mutex
		 * while dev->attach_lock is held.
		 */
		up_read(&dev->attach_lock);
		attach_locked = false;
		mutex_lock(&dev->mutex);
		/*
		 * Check device hasn't become detached behind our back.
		 * Checking dev->detach_count is unchanged ought to be
		 * sufficient (unless there have been 2**32 detaches in the
		 * meantime!), but check the subdevice pointer as well just in
		 * case.
		 *
		 * Also check the subdevice is still in a suitable state to
		 * become non-busy in case it changed behind our back.
		 */
		new_s = comedi_file_write_subdevice(file);
		if (dev->attached && old_detach_count == dev->detach_count &&
		    s == new_s && new_s->async == async && s->busy == file &&
		    (async->cmd.flags & CMDF_WRITE) &&
		    !comedi_is_subdevice_running(s))
			do_become_nonbusy(dev, s);
		mutex_unlock(&dev->mutex);
	}
out:
	if (attach_locked)
		up_read(&dev->attach_lock);

	return count ? count : retval;
}