162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * comedi_buf.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * COMEDI - Linux Control and Measurement Device Interface
662306a36Sopenharmony_ci * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
762306a36Sopenharmony_ci * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/vmalloc.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/comedi/comedidev.h>
1362306a36Sopenharmony_ci#include "comedi_internal.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#ifdef PAGE_KERNEL_NOCACHE
1662306a36Sopenharmony_ci#define COMEDI_PAGE_PROTECTION		PAGE_KERNEL_NOCACHE
1762306a36Sopenharmony_ci#else
1862306a36Sopenharmony_ci#define COMEDI_PAGE_PROTECTION		PAGE_KERNEL
1962306a36Sopenharmony_ci#endif
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic void comedi_buf_map_kref_release(struct kref *kref)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	struct comedi_buf_map *bm =
2462306a36Sopenharmony_ci		container_of(kref, struct comedi_buf_map, refcount);
2562306a36Sopenharmony_ci	struct comedi_buf_page *buf;
2662306a36Sopenharmony_ci	unsigned int i;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	if (bm->page_list) {
2962306a36Sopenharmony_ci		if (bm->dma_dir != DMA_NONE) {
3062306a36Sopenharmony_ci			/*
3162306a36Sopenharmony_ci			 * DMA buffer was allocated as a single block.
3262306a36Sopenharmony_ci			 * Address is in page_list[0].
3362306a36Sopenharmony_ci			 */
3462306a36Sopenharmony_ci			buf = &bm->page_list[0];
3562306a36Sopenharmony_ci			dma_free_coherent(bm->dma_hw_dev,
3662306a36Sopenharmony_ci					  PAGE_SIZE * bm->n_pages,
3762306a36Sopenharmony_ci					  buf->virt_addr, buf->dma_addr);
3862306a36Sopenharmony_ci		} else {
3962306a36Sopenharmony_ci			for (i = 0; i < bm->n_pages; i++) {
4062306a36Sopenharmony_ci				buf = &bm->page_list[i];
4162306a36Sopenharmony_ci				ClearPageReserved(virt_to_page(buf->virt_addr));
4262306a36Sopenharmony_ci				free_page((unsigned long)buf->virt_addr);
4362306a36Sopenharmony_ci			}
4462306a36Sopenharmony_ci		}
4562306a36Sopenharmony_ci		vfree(bm->page_list);
4662306a36Sopenharmony_ci	}
4762306a36Sopenharmony_ci	if (bm->dma_dir != DMA_NONE)
4862306a36Sopenharmony_ci		put_device(bm->dma_hw_dev);
4962306a36Sopenharmony_ci	kfree(bm);
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic void __comedi_buf_free(struct comedi_device *dev,
5362306a36Sopenharmony_ci			      struct comedi_subdevice *s)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct comedi_async *async = s->async;
5662306a36Sopenharmony_ci	struct comedi_buf_map *bm;
5762306a36Sopenharmony_ci	unsigned long flags;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (async->prealloc_buf) {
6062306a36Sopenharmony_ci		if (s->async_dma_dir == DMA_NONE)
6162306a36Sopenharmony_ci			vunmap(async->prealloc_buf);
6262306a36Sopenharmony_ci		async->prealloc_buf = NULL;
6362306a36Sopenharmony_ci		async->prealloc_bufsz = 0;
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	spin_lock_irqsave(&s->spin_lock, flags);
6762306a36Sopenharmony_ci	bm = async->buf_map;
6862306a36Sopenharmony_ci	async->buf_map = NULL;
6962306a36Sopenharmony_ci	spin_unlock_irqrestore(&s->spin_lock, flags);
7062306a36Sopenharmony_ci	comedi_buf_map_put(bm);
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic struct comedi_buf_map *
7462306a36Sopenharmony_cicomedi_buf_map_alloc(struct comedi_device *dev, enum dma_data_direction dma_dir,
7562306a36Sopenharmony_ci		     unsigned int n_pages)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct comedi_buf_map *bm;
7862306a36Sopenharmony_ci	struct comedi_buf_page *buf;
7962306a36Sopenharmony_ci	unsigned int i;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	bm = kzalloc(sizeof(*bm), GFP_KERNEL);
8262306a36Sopenharmony_ci	if (!bm)
8362306a36Sopenharmony_ci		return NULL;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	kref_init(&bm->refcount);
8662306a36Sopenharmony_ci	bm->dma_dir = dma_dir;
8762306a36Sopenharmony_ci	if (bm->dma_dir != DMA_NONE) {
8862306a36Sopenharmony_ci		/* Need ref to hardware device to free buffer later. */
8962306a36Sopenharmony_ci		bm->dma_hw_dev = get_device(dev->hw_dev);
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	bm->page_list = vzalloc(sizeof(*buf) * n_pages);
9362306a36Sopenharmony_ci	if (!bm->page_list)
9462306a36Sopenharmony_ci		goto err;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (bm->dma_dir != DMA_NONE) {
9762306a36Sopenharmony_ci		void *virt_addr;
9862306a36Sopenharmony_ci		dma_addr_t dma_addr;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci		/*
10162306a36Sopenharmony_ci		 * Currently, the DMA buffer needs to be allocated as a
10262306a36Sopenharmony_ci		 * single block so that it can be mmap()'ed.
10362306a36Sopenharmony_ci		 */
10462306a36Sopenharmony_ci		virt_addr = dma_alloc_coherent(bm->dma_hw_dev,
10562306a36Sopenharmony_ci					       PAGE_SIZE * n_pages, &dma_addr,
10662306a36Sopenharmony_ci					       GFP_KERNEL);
10762306a36Sopenharmony_ci		if (!virt_addr)
10862306a36Sopenharmony_ci			goto err;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		for (i = 0; i < n_pages; i++) {
11162306a36Sopenharmony_ci			buf = &bm->page_list[i];
11262306a36Sopenharmony_ci			buf->virt_addr = virt_addr + (i << PAGE_SHIFT);
11362306a36Sopenharmony_ci			buf->dma_addr = dma_addr + (i << PAGE_SHIFT);
11462306a36Sopenharmony_ci		}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci		bm->n_pages = i;
11762306a36Sopenharmony_ci	} else {
11862306a36Sopenharmony_ci		for (i = 0; i < n_pages; i++) {
11962306a36Sopenharmony_ci			buf = &bm->page_list[i];
12062306a36Sopenharmony_ci			buf->virt_addr = (void *)get_zeroed_page(GFP_KERNEL);
12162306a36Sopenharmony_ci			if (!buf->virt_addr)
12262306a36Sopenharmony_ci				break;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci			SetPageReserved(virt_to_page(buf->virt_addr));
12562306a36Sopenharmony_ci		}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci		bm->n_pages = i;
12862306a36Sopenharmony_ci		if (i < n_pages)
12962306a36Sopenharmony_ci			goto err;
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	return bm;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cierr:
13562306a36Sopenharmony_ci	comedi_buf_map_put(bm);
13662306a36Sopenharmony_ci	return NULL;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic void __comedi_buf_alloc(struct comedi_device *dev,
14062306a36Sopenharmony_ci			       struct comedi_subdevice *s,
14162306a36Sopenharmony_ci			       unsigned int n_pages)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	struct comedi_async *async = s->async;
14462306a36Sopenharmony_ci	struct page **pages = NULL;
14562306a36Sopenharmony_ci	struct comedi_buf_map *bm;
14662306a36Sopenharmony_ci	struct comedi_buf_page *buf;
14762306a36Sopenharmony_ci	unsigned long flags;
14862306a36Sopenharmony_ci	unsigned int i;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (!IS_ENABLED(CONFIG_HAS_DMA) && s->async_dma_dir != DMA_NONE) {
15162306a36Sopenharmony_ci		dev_err(dev->class_dev,
15262306a36Sopenharmony_ci			"dma buffer allocation not supported\n");
15362306a36Sopenharmony_ci		return;
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	bm = comedi_buf_map_alloc(dev, s->async_dma_dir, n_pages);
15762306a36Sopenharmony_ci	if (!bm)
15862306a36Sopenharmony_ci		return;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	spin_lock_irqsave(&s->spin_lock, flags);
16162306a36Sopenharmony_ci	async->buf_map = bm;
16262306a36Sopenharmony_ci	spin_unlock_irqrestore(&s->spin_lock, flags);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	if (bm->dma_dir != DMA_NONE) {
16562306a36Sopenharmony_ci		/*
16662306a36Sopenharmony_ci		 * DMA buffer was allocated as a single block.
16762306a36Sopenharmony_ci		 * Address is in page_list[0].
16862306a36Sopenharmony_ci		 */
16962306a36Sopenharmony_ci		buf = &bm->page_list[0];
17062306a36Sopenharmony_ci		async->prealloc_buf = buf->virt_addr;
17162306a36Sopenharmony_ci	} else {
17262306a36Sopenharmony_ci		pages = vmalloc(sizeof(struct page *) * n_pages);
17362306a36Sopenharmony_ci		if (!pages)
17462306a36Sopenharmony_ci			return;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci		for (i = 0; i < n_pages; i++) {
17762306a36Sopenharmony_ci			buf = &bm->page_list[i];
17862306a36Sopenharmony_ci			pages[i] = virt_to_page(buf->virt_addr);
17962306a36Sopenharmony_ci		}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci		/* vmap the pages to prealloc_buf */
18262306a36Sopenharmony_ci		async->prealloc_buf = vmap(pages, n_pages, VM_MAP,
18362306a36Sopenharmony_ci					   COMEDI_PAGE_PROTECTION);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci		vfree(pages);
18662306a36Sopenharmony_ci	}
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_civoid comedi_buf_map_get(struct comedi_buf_map *bm)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	if (bm)
19262306a36Sopenharmony_ci		kref_get(&bm->refcount);
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ciint comedi_buf_map_put(struct comedi_buf_map *bm)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	if (bm)
19862306a36Sopenharmony_ci		return kref_put(&bm->refcount, comedi_buf_map_kref_release);
19962306a36Sopenharmony_ci	return 1;
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci/* helper for "access" vm operation */
20362306a36Sopenharmony_ciint comedi_buf_map_access(struct comedi_buf_map *bm, unsigned long offset,
20462306a36Sopenharmony_ci			  void *buf, int len, int write)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	unsigned int pgoff = offset_in_page(offset);
20762306a36Sopenharmony_ci	unsigned long pg = offset >> PAGE_SHIFT;
20862306a36Sopenharmony_ci	int done = 0;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	while (done < len && pg < bm->n_pages) {
21162306a36Sopenharmony_ci		int l = min_t(int, len - done, PAGE_SIZE - pgoff);
21262306a36Sopenharmony_ci		void *b = bm->page_list[pg].virt_addr + pgoff;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		if (write)
21562306a36Sopenharmony_ci			memcpy(b, buf, l);
21662306a36Sopenharmony_ci		else
21762306a36Sopenharmony_ci			memcpy(buf, b, l);
21862306a36Sopenharmony_ci		buf += l;
21962306a36Sopenharmony_ci		done += l;
22062306a36Sopenharmony_ci		pg++;
22162306a36Sopenharmony_ci		pgoff = 0;
22262306a36Sopenharmony_ci	}
22362306a36Sopenharmony_ci	return done;
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci/* returns s->async->buf_map and increments its kref refcount */
22762306a36Sopenharmony_cistruct comedi_buf_map *
22862306a36Sopenharmony_cicomedi_buf_map_from_subdev_get(struct comedi_subdevice *s)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	struct comedi_async *async = s->async;
23162306a36Sopenharmony_ci	struct comedi_buf_map *bm = NULL;
23262306a36Sopenharmony_ci	unsigned long flags;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (!async)
23562306a36Sopenharmony_ci		return NULL;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	spin_lock_irqsave(&s->spin_lock, flags);
23862306a36Sopenharmony_ci	bm = async->buf_map;
23962306a36Sopenharmony_ci	/* only want it if buffer pages allocated */
24062306a36Sopenharmony_ci	if (bm && bm->n_pages)
24162306a36Sopenharmony_ci		comedi_buf_map_get(bm);
24262306a36Sopenharmony_ci	else
24362306a36Sopenharmony_ci		bm = NULL;
24462306a36Sopenharmony_ci	spin_unlock_irqrestore(&s->spin_lock, flags);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	return bm;
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cibool comedi_buf_is_mmapped(struct comedi_subdevice *s)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	struct comedi_buf_map *bm = s->async->buf_map;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	return bm && (kref_read(&bm->refcount) > 1);
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ciint comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s,
25762306a36Sopenharmony_ci		     unsigned long new_size)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	struct comedi_async *async = s->async;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	lockdep_assert_held(&dev->mutex);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	/* Round up new_size to multiple of PAGE_SIZE */
26462306a36Sopenharmony_ci	new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	/* if no change is required, do nothing */
26762306a36Sopenharmony_ci	if (async->prealloc_buf && async->prealloc_bufsz == new_size)
26862306a36Sopenharmony_ci		return 0;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	/* deallocate old buffer */
27162306a36Sopenharmony_ci	__comedi_buf_free(dev, s);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	/* allocate new buffer */
27462306a36Sopenharmony_ci	if (new_size) {
27562306a36Sopenharmony_ci		unsigned int n_pages = new_size >> PAGE_SHIFT;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci		__comedi_buf_alloc(dev, s, n_pages);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci		if (!async->prealloc_buf) {
28062306a36Sopenharmony_ci			/* allocation failed */
28162306a36Sopenharmony_ci			__comedi_buf_free(dev, s);
28262306a36Sopenharmony_ci			return -ENOMEM;
28362306a36Sopenharmony_ci		}
28462306a36Sopenharmony_ci	}
28562306a36Sopenharmony_ci	async->prealloc_bufsz = new_size;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	return 0;
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_civoid comedi_buf_reset(struct comedi_subdevice *s)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	struct comedi_async *async = s->async;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	async->buf_write_alloc_count = 0;
29562306a36Sopenharmony_ci	async->buf_write_count = 0;
29662306a36Sopenharmony_ci	async->buf_read_alloc_count = 0;
29762306a36Sopenharmony_ci	async->buf_read_count = 0;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	async->buf_write_ptr = 0;
30062306a36Sopenharmony_ci	async->buf_read_ptr = 0;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	async->cur_chan = 0;
30362306a36Sopenharmony_ci	async->scans_done = 0;
30462306a36Sopenharmony_ci	async->scan_progress = 0;
30562306a36Sopenharmony_ci	async->munge_chan = 0;
30662306a36Sopenharmony_ci	async->munge_count = 0;
30762306a36Sopenharmony_ci	async->munge_ptr = 0;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	async->events = 0;
31062306a36Sopenharmony_ci}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_cistatic unsigned int comedi_buf_write_n_unalloc(struct comedi_subdevice *s)
31362306a36Sopenharmony_ci{
31462306a36Sopenharmony_ci	struct comedi_async *async = s->async;
31562306a36Sopenharmony_ci	unsigned int free_end = async->buf_read_count + async->prealloc_bufsz;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	return free_end - async->buf_write_alloc_count;
31862306a36Sopenharmony_ci}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ciunsigned int comedi_buf_write_n_available(struct comedi_subdevice *s)
32162306a36Sopenharmony_ci{
32262306a36Sopenharmony_ci	struct comedi_async *async = s->async;
32362306a36Sopenharmony_ci	unsigned int free_end = async->buf_read_count + async->prealloc_bufsz;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	return free_end - async->buf_write_count;
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci/**
32962306a36Sopenharmony_ci * comedi_buf_write_alloc() - Reserve buffer space for writing
33062306a36Sopenharmony_ci * @s: COMEDI subdevice.
33162306a36Sopenharmony_ci * @nbytes: Maximum space to reserve in bytes.
33262306a36Sopenharmony_ci *
33362306a36Sopenharmony_ci * Reserve up to @nbytes bytes of space to be written in the COMEDI acquisition
33462306a36Sopenharmony_ci * data buffer associated with the subdevice.  The amount reserved is limited
33562306a36Sopenharmony_ci * by the space available.
33662306a36Sopenharmony_ci *
33762306a36Sopenharmony_ci * Return: The amount of space reserved in bytes.
33862306a36Sopenharmony_ci */
33962306a36Sopenharmony_ciunsigned int comedi_buf_write_alloc(struct comedi_subdevice *s,
34062306a36Sopenharmony_ci				    unsigned int nbytes)
34162306a36Sopenharmony_ci{
34262306a36Sopenharmony_ci	struct comedi_async *async = s->async;
34362306a36Sopenharmony_ci	unsigned int unalloc = comedi_buf_write_n_unalloc(s);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	if (nbytes > unalloc)
34662306a36Sopenharmony_ci		nbytes = unalloc;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	async->buf_write_alloc_count += nbytes;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	/*
35162306a36Sopenharmony_ci	 * ensure the async buffer 'counts' are read and updated
35262306a36Sopenharmony_ci	 * before we write data to the write-alloc'ed buffer space
35362306a36Sopenharmony_ci	 */
35462306a36Sopenharmony_ci	smp_mb();
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	return nbytes;
35762306a36Sopenharmony_ci}
35862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_buf_write_alloc);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci/*
36162306a36Sopenharmony_ci * munging is applied to data by core as it passes between user
36262306a36Sopenharmony_ci * and kernel space
36362306a36Sopenharmony_ci */
36462306a36Sopenharmony_cistatic unsigned int comedi_buf_munge(struct comedi_subdevice *s,
36562306a36Sopenharmony_ci				     unsigned int num_bytes)
36662306a36Sopenharmony_ci{
36762306a36Sopenharmony_ci	struct comedi_async *async = s->async;
36862306a36Sopenharmony_ci	unsigned int count = 0;
36962306a36Sopenharmony_ci	const unsigned int num_sample_bytes = comedi_bytes_per_sample(s);
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	if (!s->munge || (async->cmd.flags & CMDF_RAWDATA)) {
37262306a36Sopenharmony_ci		async->munge_count += num_bytes;
37362306a36Sopenharmony_ci		return num_bytes;
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	/* don't munge partial samples */
37762306a36Sopenharmony_ci	num_bytes -= num_bytes % num_sample_bytes;
37862306a36Sopenharmony_ci	while (count < num_bytes) {
37962306a36Sopenharmony_ci		int block_size = num_bytes - count;
38062306a36Sopenharmony_ci		unsigned int buf_end;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci		buf_end = async->prealloc_bufsz - async->munge_ptr;
38362306a36Sopenharmony_ci		if (block_size > buf_end)
38462306a36Sopenharmony_ci			block_size = buf_end;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci		s->munge(s->device, s,
38762306a36Sopenharmony_ci			 async->prealloc_buf + async->munge_ptr,
38862306a36Sopenharmony_ci			 block_size, async->munge_chan);
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci		/*
39162306a36Sopenharmony_ci		 * ensure data is munged in buffer before the
39262306a36Sopenharmony_ci		 * async buffer munge_count is incremented
39362306a36Sopenharmony_ci		 */
39462306a36Sopenharmony_ci		smp_wmb();
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci		async->munge_chan += block_size / num_sample_bytes;
39762306a36Sopenharmony_ci		async->munge_chan %= async->cmd.chanlist_len;
39862306a36Sopenharmony_ci		async->munge_count += block_size;
39962306a36Sopenharmony_ci		async->munge_ptr += block_size;
40062306a36Sopenharmony_ci		async->munge_ptr %= async->prealloc_bufsz;
40162306a36Sopenharmony_ci		count += block_size;
40262306a36Sopenharmony_ci	}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	return count;
40562306a36Sopenharmony_ci}
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ciunsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s)
40862306a36Sopenharmony_ci{
40962306a36Sopenharmony_ci	struct comedi_async *async = s->async;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	return async->buf_write_alloc_count - async->buf_write_count;
41262306a36Sopenharmony_ci}
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci/**
41562306a36Sopenharmony_ci * comedi_buf_write_free() - Free buffer space after it is written
41662306a36Sopenharmony_ci * @s: COMEDI subdevice.
41762306a36Sopenharmony_ci * @nbytes: Maximum space to free in bytes.
41862306a36Sopenharmony_ci *
41962306a36Sopenharmony_ci * Free up to @nbytes bytes of space previously reserved for writing in the
42062306a36Sopenharmony_ci * COMEDI acquisition data buffer associated with the subdevice.  The amount of
42162306a36Sopenharmony_ci * space freed is limited to the amount that was reserved.  The freed space is
42262306a36Sopenharmony_ci * assumed to have been filled with sample data by the writer.
42362306a36Sopenharmony_ci *
42462306a36Sopenharmony_ci * If the samples in the freed space need to be "munged", do so here.  The
42562306a36Sopenharmony_ci * freed space becomes available for allocation by the reader.
42662306a36Sopenharmony_ci *
42762306a36Sopenharmony_ci * Return: The amount of space freed in bytes.
42862306a36Sopenharmony_ci */
42962306a36Sopenharmony_ciunsigned int comedi_buf_write_free(struct comedi_subdevice *s,
43062306a36Sopenharmony_ci				   unsigned int nbytes)
43162306a36Sopenharmony_ci{
43262306a36Sopenharmony_ci	struct comedi_async *async = s->async;
43362306a36Sopenharmony_ci	unsigned int allocated = comedi_buf_write_n_allocated(s);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	if (nbytes > allocated)
43662306a36Sopenharmony_ci		nbytes = allocated;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	async->buf_write_count += nbytes;
43962306a36Sopenharmony_ci	async->buf_write_ptr += nbytes;
44062306a36Sopenharmony_ci	comedi_buf_munge(s, async->buf_write_count - async->munge_count);
44162306a36Sopenharmony_ci	if (async->buf_write_ptr >= async->prealloc_bufsz)
44262306a36Sopenharmony_ci		async->buf_write_ptr %= async->prealloc_bufsz;
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	return nbytes;
44562306a36Sopenharmony_ci}
44662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_buf_write_free);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci/**
44962306a36Sopenharmony_ci * comedi_buf_read_n_available() - Determine amount of readable buffer space
45062306a36Sopenharmony_ci * @s: COMEDI subdevice.
45162306a36Sopenharmony_ci *
45262306a36Sopenharmony_ci * Determine the amount of readable buffer space in the COMEDI acquisition data
45362306a36Sopenharmony_ci * buffer associated with the subdevice.  The readable buffer space is that
45462306a36Sopenharmony_ci * which has been freed by the writer and "munged" to the sample data format
45562306a36Sopenharmony_ci * expected by COMEDI if necessary.
45662306a36Sopenharmony_ci *
45762306a36Sopenharmony_ci * Return: The amount of readable buffer space.
45862306a36Sopenharmony_ci */
45962306a36Sopenharmony_ciunsigned int comedi_buf_read_n_available(struct comedi_subdevice *s)
46062306a36Sopenharmony_ci{
46162306a36Sopenharmony_ci	struct comedi_async *async = s->async;
46262306a36Sopenharmony_ci	unsigned int num_bytes;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (!async)
46562306a36Sopenharmony_ci		return 0;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	num_bytes = async->munge_count - async->buf_read_count;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	/*
47062306a36Sopenharmony_ci	 * ensure the async buffer 'counts' are read before we
47162306a36Sopenharmony_ci	 * attempt to read data from the buffer
47262306a36Sopenharmony_ci	 */
47362306a36Sopenharmony_ci	smp_rmb();
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	return num_bytes;
47662306a36Sopenharmony_ci}
47762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_buf_read_n_available);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci/**
48062306a36Sopenharmony_ci * comedi_buf_read_alloc() - Reserve buffer space for reading
48162306a36Sopenharmony_ci * @s: COMEDI subdevice.
48262306a36Sopenharmony_ci * @nbytes: Maximum space to reserve in bytes.
48362306a36Sopenharmony_ci *
48462306a36Sopenharmony_ci * Reserve up to @nbytes bytes of previously written and "munged" buffer space
48562306a36Sopenharmony_ci * for reading in the COMEDI acquisition data buffer associated with the
48662306a36Sopenharmony_ci * subdevice.  The amount reserved is limited to the space available.  The
48762306a36Sopenharmony_ci * reader can read from the reserved space and then free it.  A reader is also
48862306a36Sopenharmony_ci * allowed to read from the space before reserving it as long as it determines
48962306a36Sopenharmony_ci * the amount of readable data available, but the space needs to be marked as
49062306a36Sopenharmony_ci * reserved before it can be freed.
49162306a36Sopenharmony_ci *
49262306a36Sopenharmony_ci * Return: The amount of space reserved in bytes.
49362306a36Sopenharmony_ci */
49462306a36Sopenharmony_ciunsigned int comedi_buf_read_alloc(struct comedi_subdevice *s,
49562306a36Sopenharmony_ci				   unsigned int nbytes)
49662306a36Sopenharmony_ci{
49762306a36Sopenharmony_ci	struct comedi_async *async = s->async;
49862306a36Sopenharmony_ci	unsigned int available;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	available = async->munge_count - async->buf_read_alloc_count;
50162306a36Sopenharmony_ci	if (nbytes > available)
50262306a36Sopenharmony_ci		nbytes = available;
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	async->buf_read_alloc_count += nbytes;
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	/*
50762306a36Sopenharmony_ci	 * ensure the async buffer 'counts' are read before we
50862306a36Sopenharmony_ci	 * attempt to read data from the read-alloc'ed buffer space
50962306a36Sopenharmony_ci	 */
51062306a36Sopenharmony_ci	smp_rmb();
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	return nbytes;
51362306a36Sopenharmony_ci}
51462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_buf_read_alloc);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cistatic unsigned int comedi_buf_read_n_allocated(struct comedi_async *async)
51762306a36Sopenharmony_ci{
51862306a36Sopenharmony_ci	return async->buf_read_alloc_count - async->buf_read_count;
51962306a36Sopenharmony_ci}
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci/**
52262306a36Sopenharmony_ci * comedi_buf_read_free() - Free buffer space after it has been read
52362306a36Sopenharmony_ci * @s: COMEDI subdevice.
52462306a36Sopenharmony_ci * @nbytes: Maximum space to free in bytes.
52562306a36Sopenharmony_ci *
52662306a36Sopenharmony_ci * Free up to @nbytes bytes of buffer space previously reserved for reading in
52762306a36Sopenharmony_ci * the COMEDI acquisition data buffer associated with the subdevice.  The
52862306a36Sopenharmony_ci * amount of space freed is limited to the amount that was reserved.
52962306a36Sopenharmony_ci *
53062306a36Sopenharmony_ci * The freed space becomes available for allocation by the writer.
53162306a36Sopenharmony_ci *
53262306a36Sopenharmony_ci * Return: The amount of space freed in bytes.
53362306a36Sopenharmony_ci */
53462306a36Sopenharmony_ciunsigned int comedi_buf_read_free(struct comedi_subdevice *s,
53562306a36Sopenharmony_ci				  unsigned int nbytes)
53662306a36Sopenharmony_ci{
53762306a36Sopenharmony_ci	struct comedi_async *async = s->async;
53862306a36Sopenharmony_ci	unsigned int allocated;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	/*
54162306a36Sopenharmony_ci	 * ensure data has been read out of buffer before
54262306a36Sopenharmony_ci	 * the async read count is incremented
54362306a36Sopenharmony_ci	 */
54462306a36Sopenharmony_ci	smp_mb();
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	allocated = comedi_buf_read_n_allocated(async);
54762306a36Sopenharmony_ci	if (nbytes > allocated)
54862306a36Sopenharmony_ci		nbytes = allocated;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	async->buf_read_count += nbytes;
55162306a36Sopenharmony_ci	async->buf_read_ptr += nbytes;
55262306a36Sopenharmony_ci	async->buf_read_ptr %= async->prealloc_bufsz;
55362306a36Sopenharmony_ci	return nbytes;
55462306a36Sopenharmony_ci}
55562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_buf_read_free);
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_cistatic void comedi_buf_memcpy_to(struct comedi_subdevice *s,
55862306a36Sopenharmony_ci				 const void *data, unsigned int num_bytes)
55962306a36Sopenharmony_ci{
56062306a36Sopenharmony_ci	struct comedi_async *async = s->async;
56162306a36Sopenharmony_ci	unsigned int write_ptr = async->buf_write_ptr;
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	while (num_bytes) {
56462306a36Sopenharmony_ci		unsigned int block_size;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci		if (write_ptr + num_bytes > async->prealloc_bufsz)
56762306a36Sopenharmony_ci			block_size = async->prealloc_bufsz - write_ptr;
56862306a36Sopenharmony_ci		else
56962306a36Sopenharmony_ci			block_size = num_bytes;
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci		memcpy(async->prealloc_buf + write_ptr, data, block_size);
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci		data += block_size;
57462306a36Sopenharmony_ci		num_bytes -= block_size;
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci		write_ptr = 0;
57762306a36Sopenharmony_ci	}
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cistatic void comedi_buf_memcpy_from(struct comedi_subdevice *s,
58162306a36Sopenharmony_ci				   void *dest, unsigned int nbytes)
58262306a36Sopenharmony_ci{
58362306a36Sopenharmony_ci	void *src;
58462306a36Sopenharmony_ci	struct comedi_async *async = s->async;
58562306a36Sopenharmony_ci	unsigned int read_ptr = async->buf_read_ptr;
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	while (nbytes) {
58862306a36Sopenharmony_ci		unsigned int block_size;
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci		src = async->prealloc_buf + read_ptr;
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci		if (nbytes >= async->prealloc_bufsz - read_ptr)
59362306a36Sopenharmony_ci			block_size = async->prealloc_bufsz - read_ptr;
59462306a36Sopenharmony_ci		else
59562306a36Sopenharmony_ci			block_size = nbytes;
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci		memcpy(dest, src, block_size);
59862306a36Sopenharmony_ci		nbytes -= block_size;
59962306a36Sopenharmony_ci		dest += block_size;
60062306a36Sopenharmony_ci		read_ptr = 0;
60162306a36Sopenharmony_ci	}
60262306a36Sopenharmony_ci}
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci/**
60562306a36Sopenharmony_ci * comedi_buf_write_samples() - Write sample data to COMEDI buffer
60662306a36Sopenharmony_ci * @s: COMEDI subdevice.
60762306a36Sopenharmony_ci * @data: Pointer to source samples.
60862306a36Sopenharmony_ci * @nsamples: Number of samples to write.
60962306a36Sopenharmony_ci *
61062306a36Sopenharmony_ci * Write up to @nsamples samples to the COMEDI acquisition data buffer
61162306a36Sopenharmony_ci * associated with the subdevice, mark it as written and update the
61262306a36Sopenharmony_ci * acquisition scan progress.  If there is not enough room for the specified
61362306a36Sopenharmony_ci * number of samples, the number of samples written is limited to the number
61462306a36Sopenharmony_ci * that will fit and the %COMEDI_CB_OVERFLOW event flag is set to cause the
61562306a36Sopenharmony_ci * acquisition to terminate with an overrun error.  Set the %COMEDI_CB_BLOCK
61662306a36Sopenharmony_ci * event flag if any samples are written to cause waiting tasks to be woken
61762306a36Sopenharmony_ci * when the event flags are processed.
61862306a36Sopenharmony_ci *
61962306a36Sopenharmony_ci * Return: The amount of data written in bytes.
62062306a36Sopenharmony_ci */
62162306a36Sopenharmony_ciunsigned int comedi_buf_write_samples(struct comedi_subdevice *s,
62262306a36Sopenharmony_ci				      const void *data, unsigned int nsamples)
62362306a36Sopenharmony_ci{
62462306a36Sopenharmony_ci	unsigned int max_samples;
62562306a36Sopenharmony_ci	unsigned int nbytes;
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	/*
62862306a36Sopenharmony_ci	 * Make sure there is enough room in the buffer for all the samples.
62962306a36Sopenharmony_ci	 * If not, clamp the nsamples to the number that will fit, flag the
63062306a36Sopenharmony_ci	 * buffer overrun and add the samples that fit.
63162306a36Sopenharmony_ci	 */
63262306a36Sopenharmony_ci	max_samples = comedi_bytes_to_samples(s, comedi_buf_write_n_unalloc(s));
63362306a36Sopenharmony_ci	if (nsamples > max_samples) {
63462306a36Sopenharmony_ci		dev_warn(s->device->class_dev, "buffer overrun\n");
63562306a36Sopenharmony_ci		s->async->events |= COMEDI_CB_OVERFLOW;
63662306a36Sopenharmony_ci		nsamples = max_samples;
63762306a36Sopenharmony_ci	}
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	if (nsamples == 0)
64062306a36Sopenharmony_ci		return 0;
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	nbytes = comedi_buf_write_alloc(s,
64362306a36Sopenharmony_ci					comedi_samples_to_bytes(s, nsamples));
64462306a36Sopenharmony_ci	comedi_buf_memcpy_to(s, data, nbytes);
64562306a36Sopenharmony_ci	comedi_buf_write_free(s, nbytes);
64662306a36Sopenharmony_ci	comedi_inc_scan_progress(s, nbytes);
64762306a36Sopenharmony_ci	s->async->events |= COMEDI_CB_BLOCK;
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci	return nbytes;
65062306a36Sopenharmony_ci}
65162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_buf_write_samples);
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci/**
65462306a36Sopenharmony_ci * comedi_buf_read_samples() - Read sample data from COMEDI buffer
65562306a36Sopenharmony_ci * @s: COMEDI subdevice.
65662306a36Sopenharmony_ci * @data: Pointer to destination.
65762306a36Sopenharmony_ci * @nsamples: Maximum number of samples to read.
65862306a36Sopenharmony_ci *
65962306a36Sopenharmony_ci * Read up to @nsamples samples from the COMEDI acquisition data buffer
66062306a36Sopenharmony_ci * associated with the subdevice, mark it as read and update the acquisition
66162306a36Sopenharmony_ci * scan progress.  Limit the number of samples read to the number available.
66262306a36Sopenharmony_ci * Set the %COMEDI_CB_BLOCK event flag if any samples are read to cause waiting
66362306a36Sopenharmony_ci * tasks to be woken when the event flags are processed.
66462306a36Sopenharmony_ci *
66562306a36Sopenharmony_ci * Return: The amount of data read in bytes.
66662306a36Sopenharmony_ci */
66762306a36Sopenharmony_ciunsigned int comedi_buf_read_samples(struct comedi_subdevice *s,
66862306a36Sopenharmony_ci				     void *data, unsigned int nsamples)
66962306a36Sopenharmony_ci{
67062306a36Sopenharmony_ci	unsigned int max_samples;
67162306a36Sopenharmony_ci	unsigned int nbytes;
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	/* clamp nsamples to the number of full samples available */
67462306a36Sopenharmony_ci	max_samples = comedi_bytes_to_samples(s,
67562306a36Sopenharmony_ci					      comedi_buf_read_n_available(s));
67662306a36Sopenharmony_ci	if (nsamples > max_samples)
67762306a36Sopenharmony_ci		nsamples = max_samples;
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	if (nsamples == 0)
68062306a36Sopenharmony_ci		return 0;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	nbytes = comedi_buf_read_alloc(s,
68362306a36Sopenharmony_ci				       comedi_samples_to_bytes(s, nsamples));
68462306a36Sopenharmony_ci	comedi_buf_memcpy_from(s, data, nbytes);
68562306a36Sopenharmony_ci	comedi_buf_read_free(s, nbytes);
68662306a36Sopenharmony_ci	comedi_inc_scan_progress(s, nbytes);
68762306a36Sopenharmony_ci	s->async->events |= COMEDI_CB_BLOCK;
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	return nbytes;
69062306a36Sopenharmony_ci}
69162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_buf_read_samples);
692