lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20110209083325.GA7256@core.coreip.homeip.net>
Date:	Wed, 9 Feb 2011 00:33:25 -0800
From:	Dmitry Torokhov <dmitry.torokhov@...il.com>
To:	"Ira W. Snyder" <iws@...o.caltech.edu>
Cc:	linuxppc-dev@...ts.ozlabs.org, linux-kernel@...r.kernel.org
Subject: Re: [PATCH 1/2] misc: add CARMA DATA-FPGA Access Driver

Hi Ira,

On Tue, Feb 08, 2011 at 03:37:46PM -0800, Ira W. Snyder wrote:
> This driver allows userspace to access the data processing FPGAs on the
> OVRO CARMA board. It has two modes of operation:
> 

Thank you for making the changes, some more comments below.

> +
> +#define inode_to_dev(inode) container_of(inode->i_cdev, struct fpga_device, cdev)
> +

Does not seem to be used.

> +/*
> + * Data Buffer Allocation Helpers
> + */
> +
> +static int data_map_buffer(struct device *dev, struct data_buf *buf)
> +{
> +	return videobuf_dma_map(dev, &buf->vb);
> +}
> +
> +static void data_unmap_buffer(struct device *dev, struct data_buf *buf)
> +{
> +	videobuf_dma_unmap(dev, &buf->vb);
> +}

Why can't we all videobuf_dma_map() and videobuf_dma_unmap() directly?
What these helpers supposed to solve?

> +static int data_alloc_buffers(struct fpga_device *priv)
> +{
> +	struct data_buf *buf;
> +	int i, ret;
> +
> +	for (i = 0; i < MAX_DATA_BUFS; i++) {
> +
> +		/* allocate a buffer */
> +		buf = data_alloc_buffer(priv->bufsize);
> +		if (!buf)
> +			continue;

break?

> +
> +		/* map it for DMA */
> +		ret = data_map_buffer(priv->dev, buf);
> +		if (ret) {
> +			data_free_buffer(buf);
> +			continue;

and here as well?

> +		}
> +
> +		/* add it to the list of free buffers */
> +		list_add_tail(&buf->entry, &priv->free);
> +		priv->num_buffers++;
> +	}
> +
> +	/* Make sure we allocated the minimum required number of buffers */
> +	if (priv->num_buffers < MIN_DATA_BUFS) {
> +		dev_err(priv->dev, "Unable to allocate enough data buffers\n");
> +		data_free_buffers(priv);
> +		return -ENOMEM;
> +	}
> +
> +	/* Warn if we are running in a degraded state, but do not fail */
> +	if (priv->num_buffers < MAX_DATA_BUFS) {
> +		dev_warn(priv->dev, "Unable to allocate one second worth of "
> +				    "buffers, using %d buffers instead\n", i);

The latest style is not break strings even if they exceed 80 column
limit to make sure they are easily greppable.

> +static void data_dma_cb(void *data)
> +{
> +	struct fpga_device *priv = data;
> +	struct data_buf *buf;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&priv->lock, flags);
> +
> +	/* clear the FPGA status and re-enable interrupts */
> +	data_enable_interrupts(priv);
> +
> +	/* If the inflight list is empty, we've got a bug */
> +	BUG_ON(list_empty(&priv->inflight));
> +
> +	/* Grab the first buffer from the inflight list */
> +	buf = list_first_entry(&priv->inflight, struct data_buf, entry);
> +	list_del_init(&buf->entry);
> +
> +	/* Add it to the used list */
> +	list_add_tail(&buf->entry, &priv->used);

or
	list_move_tail(&buf->entry, &priv->used);

> +
> +static irqreturn_t data_irq(int irq, void *dev_id)
> +{
> +	struct fpga_device *priv = dev_id;
> +	struct data_buf *buf;
> +	u32 status;
> +	int i;
> +
> +	/* detect spurious interrupts via FPGA status */
> +	for (i = 0; i < 4; i++) {
> +		status = fpga_read_reg(priv, i, MMAP_REG_STATUS);
> +		if (!(status & (CORL_DONE | CORL_ERR))) {
> +			dev_err(priv->dev, "spurious irq detected (FPGA)\n");
> +			return IRQ_NONE;
> +		}
> +	}
> +
> +	/* detect spurious interrupts via raw IRQ pin readback */
> +	status = ioread32be(priv->regs + SYS_IRQ_INPUT_DATA);
> +	if (status & IRQ_CORL_DONE) {
> +		dev_err(priv->dev, "spurious irq detected (IRQ)\n");
> +		return IRQ_NONE;
> +	}
> +
> +	spin_lock(&priv->lock);
> +
> +	/* hide the interrupt by switching the IRQ driver to GPIO */
> +	data_disable_interrupts(priv);
> +
> +	/* Check that we actually have a free buffer */
> +	if (list_empty(&priv->free)) {
> +		priv->num_dropped++;
> +		data_enable_interrupts(priv);
> +		goto out_unlock;
> +	}
> +
> +	buf = list_first_entry(&priv->free, struct data_buf, entry);
> +	list_del_init(&buf->entry);
> +
> +	/* Check the buffer size */
> +	BUG_ON(buf->size != priv->bufsize);
> +
> +	/* Submit a DMA transfer to get the correlation data */
> +	if (data_submit_dma(priv, buf)) {
> +		dev_err(priv->dev, "Unable to setup DMA transfer\n");
> +		list_add_tail(&buf->entry, &priv->free);
> +		data_enable_interrupts(priv);
> +		goto out_unlock;
> +	}
> +
> +	/* DMA setup succeeded, GO!!! */
> +	list_add_tail(&buf->entry, &priv->inflight);
> +	dma_async_memcpy_issue_pending(priv->chan);
> +
> +out_unlock:
> +	spin_unlock(&priv->lock);
> +	return IRQ_HANDLED;
> +}

Hmm, I wonder if we could simplify this a bit by using list_move()...

	bool submitted = false;
	...

	if (list_empty(&priv->free)) {
		priv->num_dropped++;
		goto out;
	}

	buf = list_first_entry(&priv->free, struct data_buf, entry);
	BUG_ON(buf->size != priv->bufsize);

	/* Submit a DMA transfer to get the correlation data */
	if (data_submit_dma(priv, buf)) {
		dev_err(priv->dev, "Unable to setup DMA transfer\n");
		goto out;
	}

	/* DMA setup succeeded, GO!!! */
	list_move_tail(&buf->entry, &priv->inflight);
	dma_async_memcpy_issue_pending(priv->chan);
	submitted = true;

out:
	if (!submitted)
		data_enable_interrupts(priv);
	spin_unlock(&priv->lock);
	return IRQ_HANDLED;
}

OTOH, we can't have more than 1 in-flight buffer, can we? Should
we even have a list?

> +
> +/*
> + * Realtime Device Enable Helpers
> + */
> +
> +/**
> + * data_device_enable() - enable the device for buffered dumping
> + * @priv: the driver's private data structure
> + *
> + * Enable the device for buffered dumping. Allocates buffers and hooks up
> + * the interrupt handler. When this finishes, data will come pouring in.
> + *
> + * LOCKING: must hold dev->mutex
> + * CONTEXT: user context only
> + *
> + * Returns 0 on success, -ERRNO otherwise
> + */
> +static int data_device_enable(struct fpga_device *priv)
> +{
> +	u32 val;
> +	int ret;
> +
> +	/* multiple enables are safe: they do nothing */
> +	if (priv->enabled)
> +		return 0;
> +
> +	/* check that the FPGAs are programmed */
> +	val = ioread32be(priv->regs + SYS_FPGA_CONFIG_STATUS);
> +	if (!(val & (1 << 18))) {
> +		dev_err(priv->dev, "DATA-FPGAs are not enabled\n");
> +		return -ENODATA;
> +	}
> +
> +	/* read the FPGAs to calculate the buffer size */
> +	ret = data_calculate_bufsize(priv);
> +	if (ret) {
> +		dev_err(priv->dev, "unable to calculate buffer size\n");
> +		goto out_error;
> +	}
> +
> +	/* allocate the correlation data buffers */
> +	ret = data_alloc_buffers(priv);
> +	if (ret) {
> +		dev_err(priv->dev, "unable to allocate buffers\n");
> +		goto out_error;
> +	}
> +
> +	/* setup the source scatterlist for dumping correlation data */
> +	ret = data_setup_corl_table(priv);
> +	if (ret) {
> +		dev_err(priv->dev, "unable to setup correlation DMA table\n");
> +		goto out_error;
> +	}
> +
> +	/* switch to the external FPGA IRQ line */
> +	data_enable_interrupts(priv);

Not after setting interrupt handler? Can't that lead to "unhandled
interrupt" scenarios?

> +
> +	/* hookup the irq handler */
> +	ret = request_irq(priv->irq, data_irq, IRQF_SHARED, drv_name, priv);
> +	if (ret) {
> +		dev_err(priv->dev, "unable to request IRQ handler\n");
> +		goto out_error;
> +	}
> +
> +	/* success, we're enabled */
> +	priv->enabled = true;
> +	return 0;
> +
> +out_error:
> +	sg_free_table(&priv->corl_table);
> +	priv->corl_nents = 0;
> +
> +	data_free_buffers(priv);
> +	return ret;
> +}
> +
> +/**
> + * data_device_disable() - disable the device for buffered dumping
> + * @priv: the driver's private data structure
> + *
> + * Disable the device for buffered dumping. Stops new DMA transactions from
> + * being generated, waits for all outstanding DMA to complete, and then frees
> + * all buffers.
> + *
> + * LOCKING: must hold dev->mutex
> + * CONTEXT: user only
> + *
> + * Returns 0 on success, -ERRNO otherwise
> + */
> +static int data_device_disable(struct fpga_device *priv)
> +{
> +	struct list_head *list;
> +	int ret;
> +
> +	/* allow multiple disable */
> +	if (!priv->enabled)
> +		return 0;
> +
> +	/* switch to the internal GPIO IRQ line */
> +	data_disable_interrupts(priv);
> +
> +	/* unhook the irq handler */
> +	free_irq(priv->irq, priv);
> +
> +	/*
> +	 * wait for all outstanding DMA to complete
> +	 *
> +	 * Device interrupts are disabled, so no more buffers can be added to
> +	 * the inflight list. Therefore we do not need any locking.
> +	 */
> +	list = &priv->inflight;
> +	while (!list_empty(list)) {
> +		ret = wait_event_interruptible(priv->wait, list_empty(list));
> +		if (ret)
> +			return ret;
> +	}

Simply

	ret = wait_event_interruptible(priv->wait, list_empty(&prov->inflight));
	if (ret)
		return ret;

is enough, no need to loop.

> +
> +	/* free the correlation table */
> +	sg_free_table(&priv->corl_table);
> +	priv->corl_nents = 0;
> +
> +	/* free all of the buffers */
> +	data_free_buffers(priv);
> +	priv->enabled = false;

I think this should be:

	/*
	 * We are taking lock not to protect priv->enabled but to make
	 * sure there are no readers in process of altering free/used
	 * lists while we are setting the flag.
	 */
	spin_lock_irq(&priv->lock);
	priv->enabled = false;
	spin_unlock_irq(&priv->lock);

	/*
	 * Wake up readers so they error out and hopefully close their
	 * file descriptors.
	 */
	wake_up(&priv->wait);

	/* We now know that readers will not touch free/used lists */
	data_free_buffers();

Also please see the comments in data_read().


> +	return 0;
> +}
> +
> +/*
> + * SYSFS Attributes
> + */
> +
> +/*
> + * Count the number of entries in the given list
> + */
> +static unsigned int list_num_entries(struct list_head *list)
> +{
> +	struct list_head *entry;
> +	unsigned int ret = 0;
> +
> +	list_for_each(entry, list)
> +		ret++;
> +
> +	return ret;
> +}
> +
> +static ssize_t data_num_buffers_show(struct device *dev,
> +				     struct device_attribute *attr, char *buf)
> +{
> +	struct fpga_device *priv = dev_get_drvdata(dev);
> +	return snprintf(buf, PAGE_SIZE, "%u\n", priv->num_buffers);
> +}
> +
> +static ssize_t data_bufsize_show(struct device *dev,
> +				 struct device_attribute *attr, char *buf)
> +{
> +	struct fpga_device *priv = dev_get_drvdata(dev);
> +	return snprintf(buf, PAGE_SIZE, "%zu\n", priv->bufsize);
> +}
> +
> +static ssize_t data_inflight_show(struct device *dev,
> +				  struct device_attribute *attr, char *buf)
> +{
> +	struct fpga_device *priv = dev_get_drvdata(dev);
> +	unsigned int num = list_num_entries(&priv->inflight);
> +	return snprintf(buf, PAGE_SIZE, "%u\n", num);
> +}
> +
> +static ssize_t data_free_show(struct device *dev,
> +			      struct device_attribute *attr, char *buf)
> +{
> +	struct fpga_device *priv = dev_get_drvdata(dev);
> +	unsigned int num = list_num_entries(&priv->free);
> +	return snprintf(buf, PAGE_SIZE, "%u\n", num);
> +}
> +
> +static ssize_t data_used_show(struct device *dev,
> +			      struct device_attribute *attr, char *buf)
> +{
> +	struct fpga_device *priv = dev_get_drvdata(dev);
> +	unsigned int num = list_num_entries(&priv->used);
> +	return snprintf(buf, PAGE_SIZE, "%u\n", num);
> +}
> +
> +static ssize_t data_num_dropped_show(struct device *dev,
> +				     struct device_attribute *attr, char *buf)
> +{
> +	struct fpga_device *priv = dev_get_drvdata(dev);
> +	return snprintf(buf, PAGE_SIZE, "%u\n", priv->num_dropped);
> +}
> +
> +static ssize_t data_en_show(struct device *dev, struct device_attribute *attr,
> +			    char *buf)
> +{
> +	struct fpga_device *priv = dev_get_drvdata(dev);
> +	ssize_t count;
> +
> +	count = mutex_lock_interruptible(&priv->mutex);
> +	if (count)
> +		return count;
> +
> +	count = snprintf(buf, PAGE_SIZE, "%u\n", priv->enabled);
> +	mutex_unlock(&priv->mutex);

No locking needed.

> +	return count;
> +}
> +
> +static ssize_t data_en_set(struct device *dev, struct device_attribute *attr,
> +			   const char *buf, size_t count)
> +{
> +	struct fpga_device *priv = dev_get_drvdata(dev);
> +	unsigned long enable;
> +	int ret;
> +
> +	ret = strict_strtoul(buf, 0, &enable);
> +	if (ret) {
> +		dev_err(priv->dev, "unable to parse enable input\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = mutex_lock_interruptible(&priv->mutex);
> +	if (ret)
> +		return ret;
> +
> +	if (enable)
> +		ret = data_device_enable(priv);
> +	else
> +		ret = data_device_disable(priv);
> +

Have you considered enabling the device when someone opens the device
node and closing it when last user drops off?


> +	if (ret) {
> +		dev_err(priv->dev, "device %s failed\n",
> +			enable ? "enable" : "disable");
> +		count = ret;
> +		goto out_unlock;
> +	}
> +
> +out_unlock:
> +	mutex_unlock(&priv->mutex);
> +	return count;
> +}
> +
> +static DEVICE_ATTR(num_buffers, S_IRUGO, data_num_buffers_show, NULL);
> +static DEVICE_ATTR(buffer_size, S_IRUGO, data_bufsize_show, NULL);
> +static DEVICE_ATTR(num_inflight, S_IRUGO, data_inflight_show, NULL);
> +static DEVICE_ATTR(num_free, S_IRUGO, data_free_show, NULL);
> +static DEVICE_ATTR(num_used, S_IRUGO, data_used_show, NULL);
> +static DEVICE_ATTR(num_dropped, S_IRUGO, data_num_dropped_show, NULL);
> +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, data_en_show, data_en_set);
> +
> +static struct attribute *data_sysfs_attrs[] = {
> +	&dev_attr_num_buffers.attr,
> +	&dev_attr_buffer_size.attr,
> +	&dev_attr_num_inflight.attr,
> +	&dev_attr_num_free.attr,
> +	&dev_attr_num_used.attr,
> +	&dev_attr_num_dropped.attr,
> +	&dev_attr_enable.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group rt_sysfs_attr_group = {
> +	.attrs = data_sysfs_attrs,
> +};

As I mentioned, consider switching to debugfs - you won't need multiple
attributes and will be able to get full picture (number of
free/used/in-flight/etc) in one file and will not be constrained with
ABI concerns.

> +
> +/*
> + * FPGA Realtime Data Character Device
> + */
> +
> +static int data_open(struct inode *inode, struct file *filp)
> +{
> +	/*
> +	 * The miscdevice layer puts our struct miscdevice into the
> +	 * filp->private_data field. We use this to find our private
> +	 * data and then overwrite it with our own private structure.
> +	 */
> +	struct fpga_device *priv = container_of(filp->private_data,
> +						struct fpga_device, miscdev);
> +	struct fpga_reader *reader;
> +	int ret;
> +
> +	/* allocate private data */
> +	reader = kzalloc(sizeof(*reader), GFP_KERNEL);
> +	if (!reader)
> +		return -ENOMEM;
> +
> +	reader->priv = priv;
> +	reader->buf = NULL;
> +
> +	filp->private_data = reader;
> +	ret = nonseekable_open(inode, filp);
> +	if (ret) {
> +		dev_err(priv->dev, "nonseekable-open failed\n");
> +		kfree(reader);
> +		return ret;
> +	}
> +

I wonder if the device should allow only exclusive open... Unless you
distribute copies of data between all readers.

> +	return 0;
> +}
> +
> +static int data_release(struct inode *inode, struct file *filp)
> +{
> +	struct fpga_reader *reader = filp->private_data;
> +
> +	/* free the per-reader structure */
> +	data_free_buffer(reader->buf);
> +	kfree(reader);
> +	filp->private_data = NULL;
> +	return 0;
> +}
> +
> +static ssize_t data_read(struct file *filp, char __user *ubuf, size_t count,
> +			 loff_t *f_pos)
> +{
> +	struct fpga_reader *reader = filp->private_data;
> +	struct fpga_device *priv = reader->priv;
> +	struct list_head *used = &priv->used;
> +	struct data_buf *dbuf;
> +	size_t avail;
> +	void *data;
> +	int ret;
> +
> +	/* check if we already have a partial buffer */
> +	if (reader->buf) {
> +		dbuf = reader->buf;
> +		goto have_buffer;
> +	}
> +
> +	spin_lock_irq(&priv->lock);
> +
> +	/* Block until there is at least one buffer on the used list */
> +	while (list_empty(used)) {

I believe we should stop and return error when device is disabled so the
condition should be:

	while (!reader->buf && list_empty() && priv->enabled)


> +		spin_unlock_irq(&priv->lock);
> +
> +		if (filp->f_flags & O_NONBLOCK)
> +			return -EAGAIN;
> +
> +		ret = wait_event_interruptible(priv->wait, !list_empty(used));

		ret = wait_event_interruptible(priv->wait,
					!list_empty(used) || !priv->enabled);
> +		if (ret)
> +			return ret;
> +
> +		spin_lock_irq(&priv->lock);
> +	}
> +

	if (!priv->enabled) {
		spin_unlock_irq(&priv->lock);
		return -ENXIO;
	}

	if (reader->buf) {
		dbuf = reader->buf;
	} else {
		dbuf = list_first_entry(used, struct data_buf, entry);
		list_del_init(&dbuf->entry);
	}

> +	/* Grab the first buffer off of the used list */
> +	dbuf = list_first_entry(used, struct data_buf, entry);
> +	list_del_init(&dbuf->entry);
> +
> +	spin_unlock_irq(&priv->lock);
> +
> +	/* Buffers are always mapped: unmap it */
> +	data_unmap_buffer(priv->dev, dbuf);
> +
> +	/* save the buffer for later */
> +	reader->buf = dbuf;
> +	reader->buf_start = 0;
> +
> +	/* we removed a buffer from the used list: wake any waiters */
> +	wake_up(&priv->wait);

I do not think anyone waits on this...

> +
> +have_buffer:
> +	/* Get the number of bytes available */
> +	avail = dbuf->size - reader->buf_start;
> +	data = dbuf->vb.vaddr + reader->buf_start;
> +
> +	/* Get the number of bytes we can transfer */
> +	count = min(count, avail);
> +
> +	/* Copy the data to the userspace buffer */
> +	if (copy_to_user(ubuf, data, count))
> +		return -EFAULT;
> +
> +	/* Update the amount of available space */
> +	avail -= count;
> +
> +	/* Lock against concurrent enable/disable */
> +	ret = mutex_lock_interruptible(&priv->mutex);
> +	if (ret)
> +		return ret;

Mutex is not needed here, we shall rely on priv->lock. Map buffer
beforehand, take lock, check if the device is enabled and if it still is
put the buffer on free list. Release lock and exit if device was
enabled; otherwise dispose the buffer.

> +
> +	/* Still some space available: save the buffer for later */
> +	if (avail != 0) {
> +		reader->buf_start += count;
> +		reader->buf = dbuf;
> +		goto out_unlock;
> +	}
> +
> +	/*
> +	 * No space is available in this buffer
> +	 *
> +	 * This is a complicated decision:
> +	 * - if the device is not enabled: free the buffer
> +	 * - if the buffer is too small: free the buffer
> +	 */
> +	if (!priv->enabled || dbuf->size != priv->bufsize) {
> +		data_free_buffer(dbuf);
> +		reader->buf = NULL;
> +		goto out_unlock;
> +	}
> +
> +	/*
> +	 * The buffer is safe to recycle: remap it and finish
> +	 *
> +	 * If this fails, we pretend that the read never happened, and return
> +	 * -EFAULT to userspace. They'll retry the read again.
> +	 */
> +	ret = data_map_buffer(priv->dev, dbuf);
> +	if (ret) {
> +		dev_err(priv->dev, "unable to remap buffer for DMA\n");
> +		count = -EFAULT;
> +		goto out_unlock;
> +	}
> +
> +	/* Add the buffer back to the free list */
> +	reader->buf = NULL;
> +	spin_lock_irq(&priv->lock);
> +	list_add_tail(&dbuf->entry, &priv->free);
> +	spin_unlock_irq(&priv->lock);
> +
> +out_unlock:
> +	mutex_unlock(&priv->mutex);
> +	return count;
> +}
> +
> +static unsigned int data_poll(struct file *filp, struct poll_table_struct *tbl)
> +{
> +	struct fpga_reader *reader = filp->private_data;
> +	struct fpga_device *priv = reader->priv;
> +	unsigned int mask = 0;
> +
> +	poll_wait(filp, &priv->wait, tbl);
> +
> +	spin_lock_irq(&priv->lock);
> +

Like I mentioned, the spinlock is not useful here - all threads will get
woken up and will try to read since you are not resetting the wakeup
condition.

> +	if (!list_empty(&priv->used))
> +		mask |= POLLIN | POLLRDNORM;

I think you should also add:

	if (!priv->)
		mask |= POLLHUP | POLLERR;

to tell the readers that they should close their file descriptors. 

Thanks.

-- 
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ