cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

comedi_buf.c (18206B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * comedi_buf.c
      4 *
      5 * COMEDI - Linux Control and Measurement Device Interface
      6 * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
      7 * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
      8 */
      9
     10#include <linux/vmalloc.h>
     11#include <linux/slab.h>
     12#include <linux/comedi/comedidev.h>
     13#include "comedi_internal.h"
     14
     15#ifdef PAGE_KERNEL_NOCACHE
     16#define COMEDI_PAGE_PROTECTION		PAGE_KERNEL_NOCACHE
     17#else
     18#define COMEDI_PAGE_PROTECTION		PAGE_KERNEL
     19#endif
     20
     21static void comedi_buf_map_kref_release(struct kref *kref)
     22{
     23	struct comedi_buf_map *bm =
     24		container_of(kref, struct comedi_buf_map, refcount);
     25	struct comedi_buf_page *buf;
     26	unsigned int i;
     27
     28	if (bm->page_list) {
     29		if (bm->dma_dir != DMA_NONE) {
     30			/*
     31			 * DMA buffer was allocated as a single block.
     32			 * Address is in page_list[0].
     33			 */
     34			buf = &bm->page_list[0];
     35			dma_free_coherent(bm->dma_hw_dev,
     36					  PAGE_SIZE * bm->n_pages,
     37					  buf->virt_addr, buf->dma_addr);
     38		} else {
     39			for (i = 0; i < bm->n_pages; i++) {
     40				buf = &bm->page_list[i];
     41				ClearPageReserved(virt_to_page(buf->virt_addr));
     42				free_page((unsigned long)buf->virt_addr);
     43			}
     44		}
     45		vfree(bm->page_list);
     46	}
     47	if (bm->dma_dir != DMA_NONE)
     48		put_device(bm->dma_hw_dev);
     49	kfree(bm);
     50}
     51
     52static void __comedi_buf_free(struct comedi_device *dev,
     53			      struct comedi_subdevice *s)
     54{
     55	struct comedi_async *async = s->async;
     56	struct comedi_buf_map *bm;
     57	unsigned long flags;
     58
     59	if (async->prealloc_buf) {
     60		if (s->async_dma_dir == DMA_NONE)
     61			vunmap(async->prealloc_buf);
     62		async->prealloc_buf = NULL;
     63		async->prealloc_bufsz = 0;
     64	}
     65
     66	spin_lock_irqsave(&s->spin_lock, flags);
     67	bm = async->buf_map;
     68	async->buf_map = NULL;
     69	spin_unlock_irqrestore(&s->spin_lock, flags);
     70	comedi_buf_map_put(bm);
     71}
     72
     73static struct comedi_buf_map *
     74comedi_buf_map_alloc(struct comedi_device *dev, enum dma_data_direction dma_dir,
     75		     unsigned int n_pages)
     76{
     77	struct comedi_buf_map *bm;
     78	struct comedi_buf_page *buf;
     79	unsigned int i;
     80
     81	bm = kzalloc(sizeof(*bm), GFP_KERNEL);
     82	if (!bm)
     83		return NULL;
     84
     85	kref_init(&bm->refcount);
     86	bm->dma_dir = dma_dir;
     87	if (bm->dma_dir != DMA_NONE) {
     88		/* Need ref to hardware device to free buffer later. */
     89		bm->dma_hw_dev = get_device(dev->hw_dev);
     90	}
     91
     92	bm->page_list = vzalloc(sizeof(*buf) * n_pages);
     93	if (!bm->page_list)
     94		goto err;
     95
     96	if (bm->dma_dir != DMA_NONE) {
     97		void *virt_addr;
     98		dma_addr_t dma_addr;
     99
    100		/*
    101		 * Currently, the DMA buffer needs to be allocated as a
    102		 * single block so that it can be mmap()'ed.
    103		 */
    104		virt_addr = dma_alloc_coherent(bm->dma_hw_dev,
    105					       PAGE_SIZE * n_pages, &dma_addr,
    106					       GFP_KERNEL);
    107		if (!virt_addr)
    108			goto err;
    109
    110		for (i = 0; i < n_pages; i++) {
    111			buf = &bm->page_list[i];
    112			buf->virt_addr = virt_addr + (i << PAGE_SHIFT);
    113			buf->dma_addr = dma_addr + (i << PAGE_SHIFT);
    114		}
    115
    116		bm->n_pages = i;
    117	} else {
    118		for (i = 0; i < n_pages; i++) {
    119			buf = &bm->page_list[i];
    120			buf->virt_addr = (void *)get_zeroed_page(GFP_KERNEL);
    121			if (!buf->virt_addr)
    122				break;
    123
    124			SetPageReserved(virt_to_page(buf->virt_addr));
    125		}
    126
    127		bm->n_pages = i;
    128		if (i < n_pages)
    129			goto err;
    130	}
    131
    132	return bm;
    133
    134err:
    135	comedi_buf_map_put(bm);
    136	return NULL;
    137}
    138
    139static void __comedi_buf_alloc(struct comedi_device *dev,
    140			       struct comedi_subdevice *s,
    141			       unsigned int n_pages)
    142{
    143	struct comedi_async *async = s->async;
    144	struct page **pages = NULL;
    145	struct comedi_buf_map *bm;
    146	struct comedi_buf_page *buf;
    147	unsigned long flags;
    148	unsigned int i;
    149
    150	if (!IS_ENABLED(CONFIG_HAS_DMA) && s->async_dma_dir != DMA_NONE) {
    151		dev_err(dev->class_dev,
    152			"dma buffer allocation not supported\n");
    153		return;
    154	}
    155
    156	bm = comedi_buf_map_alloc(dev, s->async_dma_dir, n_pages);
    157	if (!bm)
    158		return;
    159
    160	spin_lock_irqsave(&s->spin_lock, flags);
    161	async->buf_map = bm;
    162	spin_unlock_irqrestore(&s->spin_lock, flags);
    163
    164	if (bm->dma_dir != DMA_NONE) {
    165		/*
    166		 * DMA buffer was allocated as a single block.
    167		 * Address is in page_list[0].
    168		 */
    169		buf = &bm->page_list[0];
    170		async->prealloc_buf = buf->virt_addr;
    171	} else {
    172		pages = vmalloc(sizeof(struct page *) * n_pages);
    173		if (!pages)
    174			return;
    175
    176		for (i = 0; i < n_pages; i++) {
    177			buf = &bm->page_list[i];
    178			pages[i] = virt_to_page(buf->virt_addr);
    179		}
    180
    181		/* vmap the pages to prealloc_buf */
    182		async->prealloc_buf = vmap(pages, n_pages, VM_MAP,
    183					   COMEDI_PAGE_PROTECTION);
    184
    185		vfree(pages);
    186	}
    187}
    188
    189void comedi_buf_map_get(struct comedi_buf_map *bm)
    190{
    191	if (bm)
    192		kref_get(&bm->refcount);
    193}
    194
    195int comedi_buf_map_put(struct comedi_buf_map *bm)
    196{
    197	if (bm)
    198		return kref_put(&bm->refcount, comedi_buf_map_kref_release);
    199	return 1;
    200}
    201
    202/* helper for "access" vm operation */
    203int comedi_buf_map_access(struct comedi_buf_map *bm, unsigned long offset,
    204			  void *buf, int len, int write)
    205{
    206	unsigned int pgoff = offset_in_page(offset);
    207	unsigned long pg = offset >> PAGE_SHIFT;
    208	int done = 0;
    209
    210	while (done < len && pg < bm->n_pages) {
    211		int l = min_t(int, len - done, PAGE_SIZE - pgoff);
    212		void *b = bm->page_list[pg].virt_addr + pgoff;
    213
    214		if (write)
    215			memcpy(b, buf, l);
    216		else
    217			memcpy(buf, b, l);
    218		buf += l;
    219		done += l;
    220		pg++;
    221		pgoff = 0;
    222	}
    223	return done;
    224}
    225
    226/* returns s->async->buf_map and increments its kref refcount */
    227struct comedi_buf_map *
    228comedi_buf_map_from_subdev_get(struct comedi_subdevice *s)
    229{
    230	struct comedi_async *async = s->async;
    231	struct comedi_buf_map *bm = NULL;
    232	unsigned long flags;
    233
    234	if (!async)
    235		return NULL;
    236
    237	spin_lock_irqsave(&s->spin_lock, flags);
    238	bm = async->buf_map;
    239	/* only want it if buffer pages allocated */
    240	if (bm && bm->n_pages)
    241		comedi_buf_map_get(bm);
    242	else
    243		bm = NULL;
    244	spin_unlock_irqrestore(&s->spin_lock, flags);
    245
    246	return bm;
    247}
    248
    249bool comedi_buf_is_mmapped(struct comedi_subdevice *s)
    250{
    251	struct comedi_buf_map *bm = s->async->buf_map;
    252
    253	return bm && (kref_read(&bm->refcount) > 1);
    254}
    255
    256int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s,
    257		     unsigned long new_size)
    258{
    259	struct comedi_async *async = s->async;
    260
    261	lockdep_assert_held(&dev->mutex);
    262
    263	/* Round up new_size to multiple of PAGE_SIZE */
    264	new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK;
    265
    266	/* if no change is required, do nothing */
    267	if (async->prealloc_buf && async->prealloc_bufsz == new_size)
    268		return 0;
    269
    270	/* deallocate old buffer */
    271	__comedi_buf_free(dev, s);
    272
    273	/* allocate new buffer */
    274	if (new_size) {
    275		unsigned int n_pages = new_size >> PAGE_SHIFT;
    276
    277		__comedi_buf_alloc(dev, s, n_pages);
    278
    279		if (!async->prealloc_buf) {
    280			/* allocation failed */
    281			__comedi_buf_free(dev, s);
    282			return -ENOMEM;
    283		}
    284	}
    285	async->prealloc_bufsz = new_size;
    286
    287	return 0;
    288}
    289
    290void comedi_buf_reset(struct comedi_subdevice *s)
    291{
    292	struct comedi_async *async = s->async;
    293
    294	async->buf_write_alloc_count = 0;
    295	async->buf_write_count = 0;
    296	async->buf_read_alloc_count = 0;
    297	async->buf_read_count = 0;
    298
    299	async->buf_write_ptr = 0;
    300	async->buf_read_ptr = 0;
    301
    302	async->cur_chan = 0;
    303	async->scans_done = 0;
    304	async->scan_progress = 0;
    305	async->munge_chan = 0;
    306	async->munge_count = 0;
    307	async->munge_ptr = 0;
    308
    309	async->events = 0;
    310}
    311
    312static unsigned int comedi_buf_write_n_unalloc(struct comedi_subdevice *s)
    313{
    314	struct comedi_async *async = s->async;
    315	unsigned int free_end = async->buf_read_count + async->prealloc_bufsz;
    316
    317	return free_end - async->buf_write_alloc_count;
    318}
    319
    320unsigned int comedi_buf_write_n_available(struct comedi_subdevice *s)
    321{
    322	struct comedi_async *async = s->async;
    323	unsigned int free_end = async->buf_read_count + async->prealloc_bufsz;
    324
    325	return free_end - async->buf_write_count;
    326}
    327
    328/**
    329 * comedi_buf_write_alloc() - Reserve buffer space for writing
    330 * @s: COMEDI subdevice.
    331 * @nbytes: Maximum space to reserve in bytes.
    332 *
    333 * Reserve up to @nbytes bytes of space to be written in the COMEDI acquisition
    334 * data buffer associated with the subdevice.  The amount reserved is limited
    335 * by the space available.
    336 *
    337 * Return: The amount of space reserved in bytes.
    338 */
    339unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s,
    340				    unsigned int nbytes)
    341{
    342	struct comedi_async *async = s->async;
    343	unsigned int unalloc = comedi_buf_write_n_unalloc(s);
    344
    345	if (nbytes > unalloc)
    346		nbytes = unalloc;
    347
    348	async->buf_write_alloc_count += nbytes;
    349
    350	/*
    351	 * ensure the async buffer 'counts' are read and updated
    352	 * before we write data to the write-alloc'ed buffer space
    353	 */
    354	smp_mb();
    355
    356	return nbytes;
    357}
    358EXPORT_SYMBOL_GPL(comedi_buf_write_alloc);
    359
    360/*
    361 * munging is applied to data by core as it passes between user
    362 * and kernel space
    363 */
    364static unsigned int comedi_buf_munge(struct comedi_subdevice *s,
    365				     unsigned int num_bytes)
    366{
    367	struct comedi_async *async = s->async;
    368	unsigned int count = 0;
    369	const unsigned int num_sample_bytes = comedi_bytes_per_sample(s);
    370
    371	if (!s->munge || (async->cmd.flags & CMDF_RAWDATA)) {
    372		async->munge_count += num_bytes;
    373		return num_bytes;
    374	}
    375
    376	/* don't munge partial samples */
    377	num_bytes -= num_bytes % num_sample_bytes;
    378	while (count < num_bytes) {
    379		int block_size = num_bytes - count;
    380		unsigned int buf_end;
    381
    382		buf_end = async->prealloc_bufsz - async->munge_ptr;
    383		if (block_size > buf_end)
    384			block_size = buf_end;
    385
    386		s->munge(s->device, s,
    387			 async->prealloc_buf + async->munge_ptr,
    388			 block_size, async->munge_chan);
    389
    390		/*
    391		 * ensure data is munged in buffer before the
    392		 * async buffer munge_count is incremented
    393		 */
    394		smp_wmb();
    395
    396		async->munge_chan += block_size / num_sample_bytes;
    397		async->munge_chan %= async->cmd.chanlist_len;
    398		async->munge_count += block_size;
    399		async->munge_ptr += block_size;
    400		async->munge_ptr %= async->prealloc_bufsz;
    401		count += block_size;
    402	}
    403
    404	return count;
    405}
    406
    407unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s)
    408{
    409	struct comedi_async *async = s->async;
    410
    411	return async->buf_write_alloc_count - async->buf_write_count;
    412}
    413
    414/**
    415 * comedi_buf_write_free() - Free buffer space after it is written
    416 * @s: COMEDI subdevice.
    417 * @nbytes: Maximum space to free in bytes.
    418 *
    419 * Free up to @nbytes bytes of space previously reserved for writing in the
    420 * COMEDI acquisition data buffer associated with the subdevice.  The amount of
    421 * space freed is limited to the amount that was reserved.  The freed space is
    422 * assumed to have been filled with sample data by the writer.
    423 *
    424 * If the samples in the freed space need to be "munged", do so here.  The
    425 * freed space becomes available for allocation by the reader.
    426 *
    427 * Return: The amount of space freed in bytes.
    428 */
    429unsigned int comedi_buf_write_free(struct comedi_subdevice *s,
    430				   unsigned int nbytes)
    431{
    432	struct comedi_async *async = s->async;
    433	unsigned int allocated = comedi_buf_write_n_allocated(s);
    434
    435	if (nbytes > allocated)
    436		nbytes = allocated;
    437
    438	async->buf_write_count += nbytes;
    439	async->buf_write_ptr += nbytes;
    440	comedi_buf_munge(s, async->buf_write_count - async->munge_count);
    441	if (async->buf_write_ptr >= async->prealloc_bufsz)
    442		async->buf_write_ptr %= async->prealloc_bufsz;
    443
    444	return nbytes;
    445}
    446EXPORT_SYMBOL_GPL(comedi_buf_write_free);
    447
    448/**
    449 * comedi_buf_read_n_available() - Determine amount of readable buffer space
    450 * @s: COMEDI subdevice.
    451 *
    452 * Determine the amount of readable buffer space in the COMEDI acquisition data
    453 * buffer associated with the subdevice.  The readable buffer space is that
    454 * which has been freed by the writer and "munged" to the sample data format
    455 * expected by COMEDI if necessary.
    456 *
    457 * Return: The amount of readable buffer space.
    458 */
    459unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s)
    460{
    461	struct comedi_async *async = s->async;
    462	unsigned int num_bytes;
    463
    464	if (!async)
    465		return 0;
    466
    467	num_bytes = async->munge_count - async->buf_read_count;
    468
    469	/*
    470	 * ensure the async buffer 'counts' are read before we
    471	 * attempt to read data from the buffer
    472	 */
    473	smp_rmb();
    474
    475	return num_bytes;
    476}
    477EXPORT_SYMBOL_GPL(comedi_buf_read_n_available);
    478
    479/**
    480 * comedi_buf_read_alloc() - Reserve buffer space for reading
    481 * @s: COMEDI subdevice.
    482 * @nbytes: Maximum space to reserve in bytes.
    483 *
    484 * Reserve up to @nbytes bytes of previously written and "munged" buffer space
    485 * for reading in the COMEDI acquisition data buffer associated with the
    486 * subdevice.  The amount reserved is limited to the space available.  The
    487 * reader can read from the reserved space and then free it.  A reader is also
    488 * allowed to read from the space before reserving it as long as it determines
    489 * the amount of readable data available, but the space needs to be marked as
    490 * reserved before it can be freed.
    491 *
    492 * Return: The amount of space reserved in bytes.
    493 */
    494unsigned int comedi_buf_read_alloc(struct comedi_subdevice *s,
    495				   unsigned int nbytes)
    496{
    497	struct comedi_async *async = s->async;
    498	unsigned int available;
    499
    500	available = async->munge_count - async->buf_read_alloc_count;
    501	if (nbytes > available)
    502		nbytes = available;
    503
    504	async->buf_read_alloc_count += nbytes;
    505
    506	/*
    507	 * ensure the async buffer 'counts' are read before we
    508	 * attempt to read data from the read-alloc'ed buffer space
    509	 */
    510	smp_rmb();
    511
    512	return nbytes;
    513}
    514EXPORT_SYMBOL_GPL(comedi_buf_read_alloc);
    515
    516static unsigned int comedi_buf_read_n_allocated(struct comedi_async *async)
    517{
    518	return async->buf_read_alloc_count - async->buf_read_count;
    519}
    520
    521/**
    522 * comedi_buf_read_free() - Free buffer space after it has been read
    523 * @s: COMEDI subdevice.
    524 * @nbytes: Maximum space to free in bytes.
    525 *
    526 * Free up to @nbytes bytes of buffer space previously reserved for reading in
    527 * the COMEDI acquisition data buffer associated with the subdevice.  The
    528 * amount of space freed is limited to the amount that was reserved.
    529 *
    530 * The freed space becomes available for allocation by the writer.
    531 *
    532 * Return: The amount of space freed in bytes.
    533 */
    534unsigned int comedi_buf_read_free(struct comedi_subdevice *s,
    535				  unsigned int nbytes)
    536{
    537	struct comedi_async *async = s->async;
    538	unsigned int allocated;
    539
    540	/*
    541	 * ensure data has been read out of buffer before
    542	 * the async read count is incremented
    543	 */
    544	smp_mb();
    545
    546	allocated = comedi_buf_read_n_allocated(async);
    547	if (nbytes > allocated)
    548		nbytes = allocated;
    549
    550	async->buf_read_count += nbytes;
    551	async->buf_read_ptr += nbytes;
    552	async->buf_read_ptr %= async->prealloc_bufsz;
    553	return nbytes;
    554}
    555EXPORT_SYMBOL_GPL(comedi_buf_read_free);
    556
    557static void comedi_buf_memcpy_to(struct comedi_subdevice *s,
    558				 const void *data, unsigned int num_bytes)
    559{
    560	struct comedi_async *async = s->async;
    561	unsigned int write_ptr = async->buf_write_ptr;
    562
    563	while (num_bytes) {
    564		unsigned int block_size;
    565
    566		if (write_ptr + num_bytes > async->prealloc_bufsz)
    567			block_size = async->prealloc_bufsz - write_ptr;
    568		else
    569			block_size = num_bytes;
    570
    571		memcpy(async->prealloc_buf + write_ptr, data, block_size);
    572
    573		data += block_size;
    574		num_bytes -= block_size;
    575
    576		write_ptr = 0;
    577	}
    578}
    579
    580static void comedi_buf_memcpy_from(struct comedi_subdevice *s,
    581				   void *dest, unsigned int nbytes)
    582{
    583	void *src;
    584	struct comedi_async *async = s->async;
    585	unsigned int read_ptr = async->buf_read_ptr;
    586
    587	while (nbytes) {
    588		unsigned int block_size;
    589
    590		src = async->prealloc_buf + read_ptr;
    591
    592		if (nbytes >= async->prealloc_bufsz - read_ptr)
    593			block_size = async->prealloc_bufsz - read_ptr;
    594		else
    595			block_size = nbytes;
    596
    597		memcpy(dest, src, block_size);
    598		nbytes -= block_size;
    599		dest += block_size;
    600		read_ptr = 0;
    601	}
    602}
    603
    604/**
    605 * comedi_buf_write_samples() - Write sample data to COMEDI buffer
    606 * @s: COMEDI subdevice.
    607 * @data: Pointer to source samples.
    608 * @nsamples: Number of samples to write.
    609 *
    610 * Write up to @nsamples samples to the COMEDI acquisition data buffer
    611 * associated with the subdevice, mark it as written and update the
    612 * acquisition scan progress.  If there is not enough room for the specified
    613 * number of samples, the number of samples written is limited to the number
    614 * that will fit and the %COMEDI_CB_OVERFLOW event flag is set to cause the
    615 * acquisition to terminate with an overrun error.  Set the %COMEDI_CB_BLOCK
    616 * event flag if any samples are written to cause waiting tasks to be woken
    617 * when the event flags are processed.
    618 *
    619 * Return: The amount of data written in bytes.
    620 */
    621unsigned int comedi_buf_write_samples(struct comedi_subdevice *s,
    622				      const void *data, unsigned int nsamples)
    623{
    624	unsigned int max_samples;
    625	unsigned int nbytes;
    626
    627	/*
    628	 * Make sure there is enough room in the buffer for all the samples.
    629	 * If not, clamp the nsamples to the number that will fit, flag the
    630	 * buffer overrun and add the samples that fit.
    631	 */
    632	max_samples = comedi_bytes_to_samples(s, comedi_buf_write_n_unalloc(s));
    633	if (nsamples > max_samples) {
    634		dev_warn(s->device->class_dev, "buffer overrun\n");
    635		s->async->events |= COMEDI_CB_OVERFLOW;
    636		nsamples = max_samples;
    637	}
    638
    639	if (nsamples == 0)
    640		return 0;
    641
    642	nbytes = comedi_buf_write_alloc(s,
    643					comedi_samples_to_bytes(s, nsamples));
    644	comedi_buf_memcpy_to(s, data, nbytes);
    645	comedi_buf_write_free(s, nbytes);
    646	comedi_inc_scan_progress(s, nbytes);
    647	s->async->events |= COMEDI_CB_BLOCK;
    648
    649	return nbytes;
    650}
    651EXPORT_SYMBOL_GPL(comedi_buf_write_samples);
    652
    653/**
    654 * comedi_buf_read_samples() - Read sample data from COMEDI buffer
    655 * @s: COMEDI subdevice.
    656 * @data: Pointer to destination.
    657 * @nsamples: Maximum number of samples to read.
    658 *
    659 * Read up to @nsamples samples from the COMEDI acquisition data buffer
    660 * associated with the subdevice, mark it as read and update the acquisition
    661 * scan progress.  Limit the number of samples read to the number available.
    662 * Set the %COMEDI_CB_BLOCK event flag if any samples are read to cause waiting
    663 * tasks to be woken when the event flags are processed.
    664 *
    665 * Return: The amount of data read in bytes.
    666 */
    667unsigned int comedi_buf_read_samples(struct comedi_subdevice *s,
    668				     void *data, unsigned int nsamples)
    669{
    670	unsigned int max_samples;
    671	unsigned int nbytes;
    672
    673	/* clamp nsamples to the number of full samples available */
    674	max_samples = comedi_bytes_to_samples(s,
    675					      comedi_buf_read_n_available(s));
    676	if (nsamples > max_samples)
    677		nsamples = max_samples;
    678
    679	if (nsamples == 0)
    680		return 0;
    681
    682	nbytes = comedi_buf_read_alloc(s,
    683				       comedi_samples_to_bytes(s, nsamples));
    684	comedi_buf_memcpy_from(s, data, nbytes);
    685	comedi_buf_read_free(s, nbytes);
    686	comedi_inc_scan_progress(s, nbytes);
    687	s->async->events |= COMEDI_CB_BLOCK;
    688
    689	return nbytes;
    690}
    691EXPORT_SYMBOL_GPL(comedi_buf_read_samples);