738 lines · 19.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0+/* * comedi_buf.c * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net> */ #include <linux/vmalloc.h>#include <linux/slab.h>#include <linux/comedi/comedidev.h>#include "comedi_internal.h" #ifdef PAGE_KERNEL_NOCACHE#define COMEDI_PAGE_PROTECTION		PAGE_KERNEL_NOCACHE#else#define COMEDI_PAGE_PROTECTION		PAGE_KERNEL#endif static void comedi_buf_map_kref_release(struct kref *kref){	struct comedi_buf_map *bm =		container_of(kref, struct comedi_buf_map, refcount);	struct comedi_buf_page *buf;	unsigned int i; 	if (bm->page_list) {		if (bm->dma_dir != DMA_NONE) {			for (i = 0; i < bm->n_pages; i++) {				buf = &bm->page_list[i];				dma_free_coherent(bm->dma_hw_dev, PAGE_SIZE,						  buf->virt_addr,						  buf->dma_addr);			}		} else {			for (i = 0; i < bm->n_pages; i++) {				buf = &bm->page_list[i];				ClearPageReserved(virt_to_page(buf->virt_addr));				free_page((unsigned long)buf->virt_addr);			}		}		vfree(bm->page_list);	}	if (bm->dma_dir != DMA_NONE)		put_device(bm->dma_hw_dev);	kfree(bm);} static void __comedi_buf_free(struct comedi_device *dev,			      struct comedi_subdevice *s){	struct comedi_async *async = s->async;	struct comedi_buf_map *bm;	unsigned long flags; 	async->prealloc_bufsz = 0;	spin_lock_irqsave(&s->spin_lock, flags);	bm = async->buf_map;	async->buf_map = NULL;	spin_unlock_irqrestore(&s->spin_lock, flags);	comedi_buf_map_put(bm);} static struct comedi_buf_map *comedi_buf_map_alloc(struct comedi_device *dev, enum dma_data_direction dma_dir,		     unsigned int n_pages){	struct comedi_buf_map *bm;	struct comedi_buf_page *buf;	unsigned int i; 	bm = kzalloc_obj(*bm);	if (!bm)		return NULL; 	kref_init(&bm->refcount);	bm->dma_dir = dma_dir;	if (bm->dma_dir != DMA_NONE) {		/* Need ref to hardware device to free buffer later. */		bm->dma_hw_dev = get_device(dev->hw_dev);	} 	bm->page_list = vzalloc(sizeof(*buf) * n_pages);	if (!bm->page_list)		goto err; 	if (bm->dma_dir != DMA_NONE) {		for (i = 0; i < n_pages; i++) {			buf = &bm->page_list[i];			buf->virt_addr =			    dma_alloc_coherent(bm->dma_hw_dev, PAGE_SIZE,					       &buf->dma_addr, GFP_KERNEL);			if (!buf->virt_addr)				break;		}	} else {		for (i = 0; i < n_pages; i++) {			buf = &bm->page_list[i];			buf->virt_addr = (void *)get_zeroed_page(GFP_KERNEL);			if (!buf->virt_addr)				break; 			SetPageReserved(virt_to_page(buf->virt_addr));		}	}	bm->n_pages = i;	if (i < n_pages)		goto err; 	return bm; err:	comedi_buf_map_put(bm);	return NULL;} static void __comedi_buf_alloc(struct comedi_device *dev,			       struct comedi_subdevice *s,			       unsigned int n_pages){	struct comedi_async *async = s->async;	struct comedi_buf_map *bm;	unsigned long flags; 	if (!IS_ENABLED(CONFIG_HAS_DMA) && s->async_dma_dir != DMA_NONE) {		dev_err(dev->class_dev,			"dma buffer allocation not supported\n");		return;	} 	bm = comedi_buf_map_alloc(dev, s->async_dma_dir, n_pages);	if (!bm)		return; 	spin_lock_irqsave(&s->spin_lock, flags);	async->buf_map = bm;	spin_unlock_irqrestore(&s->spin_lock, flags);	async->prealloc_bufsz = n_pages << PAGE_SHIFT;} void comedi_buf_map_get(struct comedi_buf_map *bm){	if (bm)		kref_get(&bm->refcount);} int comedi_buf_map_put(struct comedi_buf_map *bm){	if (bm)		return kref_put(&bm->refcount, comedi_buf_map_kref_release);	return 1;} /* helper for "access" vm operation */int comedi_buf_map_access(struct comedi_buf_map *bm, unsigned long offset,			  void *buf, int len, int write){	unsigned int pgoff = offset_in_page(offset);	unsigned long pg = offset >> PAGE_SHIFT;	int done = 0; 	while (done < len && pg < bm->n_pages) {		int l = min_t(int, len - done, PAGE_SIZE - pgoff);		void *b = bm->page_list[pg].virt_addr + pgoff; 		if (write)			memcpy(b, buf, l);		else			memcpy(buf, b, l);		buf += l;		done += l;		pg++;		pgoff = 0;	}	return done;} /* returns s->async->buf_map and increments its kref refcount */struct comedi_buf_map *comedi_buf_map_from_subdev_get(struct comedi_subdevice *s){	struct comedi_async *async = s->async;	struct comedi_buf_map *bm = NULL;	unsigned long flags; 	if (!async)		return NULL; 	spin_lock_irqsave(&s->spin_lock, flags);	bm = async->buf_map;	/* only want it if buffer pages allocated */	if (bm && bm->n_pages)		comedi_buf_map_get(bm);	else		bm = NULL;	spin_unlock_irqrestore(&s->spin_lock, flags); 	return bm;} bool comedi_buf_is_mmapped(struct comedi_subdevice *s){	struct comedi_buf_map *bm = s->async->buf_map; 	return bm && (kref_read(&bm->refcount) > 1);} int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s,		     unsigned long new_size){	struct comedi_async *async = s->async; 	lockdep_assert_held(&dev->mutex); 	/* Round up new_size to multiple of PAGE_SIZE */	new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK; 	/* if no change is required, do nothing */	if (async->prealloc_bufsz == new_size)		return 0; 	/* deallocate old buffer */	__comedi_buf_free(dev, s); 	/* allocate new buffer */	if (new_size) {		unsigned int n_pages = new_size >> PAGE_SHIFT; 		__comedi_buf_alloc(dev, s, n_pages);		if (!async->prealloc_bufsz)			return -ENOMEM;	} 	return 0;} void comedi_buf_reset(struct comedi_subdevice *s){	struct comedi_async *async = s->async; 	async->buf_write_alloc_count = 0;	async->buf_write_count = 0;	async->buf_read_alloc_count = 0;	async->buf_read_count = 0; 	async->buf_write_ptr = 0;	async->buf_read_ptr = 0; 	async->cur_chan = 0;	async->scans_done = 0;	async->scan_progress = 0;	async->munge_chan = 0;	async->munge_count = 0;	async->munge_ptr = 0; 	async->events = 0;} static unsigned int comedi_buf_write_n_unalloc(struct comedi_subdevice *s){	struct comedi_async *async = s->async;	unsigned int free_end = async->buf_read_count + async->prealloc_bufsz; 	return free_end - async->buf_write_alloc_count;} unsigned int comedi_buf_write_n_available(struct comedi_subdevice *s){	struct comedi_async *async = s->async;	unsigned int free_end = async->buf_read_count + async->prealloc_bufsz; 	return free_end - async->buf_write_count;} unsigned int _comedi_buf_write_alloc(struct comedi_subdevice *s,				     unsigned int nbytes){	struct comedi_async *async = s->async;	unsigned int unalloc = comedi_buf_write_n_unalloc(s); 	if (nbytes > unalloc)		nbytes = unalloc; 	async->buf_write_alloc_count += nbytes; 	/*	 * ensure the async buffer 'counts' are read and updated	 * before we write data to the write-alloc'ed buffer space	 */	smp_mb(); 	return nbytes;} /** * comedi_buf_write_alloc() - Reserve buffer space for writing * @s: COMEDI subdevice. * @nbytes: Maximum space to reserve in bytes. * * Reserve up to @nbytes bytes of space to be written in the COMEDI acquisition * data buffer associated with the subdevice.  The amount reserved is limited * by the space available. * * Return: The amount of space reserved in bytes. */unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s,				    unsigned int nbytes){	if (comedi_get_is_subdevice_running(s)) {		nbytes = _comedi_buf_write_alloc(s, nbytes);		comedi_put_is_subdevice_running(s);	} else {		nbytes = 0;	}	return nbytes;}EXPORT_SYMBOL_GPL(comedi_buf_write_alloc); /* * munging is applied to data by core as it passes between user * and kernel space */static unsigned int comedi_buf_munge(struct comedi_subdevice *s,				     unsigned int num_bytes){	struct comedi_async *async = s->async;	struct comedi_buf_page *buf_page_list = async->buf_map->page_list;	unsigned int count = 0;	const unsigned int num_sample_bytes = comedi_bytes_per_sample(s); 	if (!s->munge || (async->cmd.flags & CMDF_RAWDATA) || async->cmd.chanlist_len == 0) {		async->munge_count += num_bytes;		return num_bytes;	} 	/* don't munge partial samples */	num_bytes -= num_bytes % num_sample_bytes;	while (count < num_bytes) {		/*		 * Do not munge beyond page boundary.		 * Note: prealloc_bufsz is a multiple of PAGE_SIZE.		 */		unsigned int page = async->munge_ptr >> PAGE_SHIFT;		unsigned int offset = offset_in_page(async->munge_ptr);		unsigned int block_size =			     min(num_bytes - count, PAGE_SIZE - offset); 		s->munge(s->device, s, buf_page_list[page].virt_addr + offset,			 block_size, async->munge_chan); 		/*		 * ensure data is munged in buffer before the		 * async buffer munge_count is incremented		 */		smp_wmb(); 		async->munge_chan += block_size / num_sample_bytes;		async->munge_chan %= async->cmd.chanlist_len;		async->munge_count += block_size;		async->munge_ptr += block_size;		if (async->munge_ptr == async->prealloc_bufsz)			async->munge_ptr = 0;		count += block_size;	} 	return count;} unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s){	struct comedi_async *async = s->async; 	return async->buf_write_alloc_count - async->buf_write_count;} unsigned int _comedi_buf_write_free(struct comedi_subdevice *s,				    unsigned int nbytes){	struct comedi_async *async = s->async;	unsigned int allocated = comedi_buf_write_n_allocated(s); 	if (nbytes > allocated)		nbytes = allocated; 	async->buf_write_count += nbytes;	async->buf_write_ptr += nbytes;	comedi_buf_munge(s, async->buf_write_count - async->munge_count);	if (async->buf_write_ptr >= async->prealloc_bufsz)		async->buf_write_ptr %= async->prealloc_bufsz; 	return nbytes;} /** * comedi_buf_write_free() - Free buffer space after it is written * @s: COMEDI subdevice. * @nbytes: Maximum space to free in bytes. * * Free up to @nbytes bytes of space previously reserved for writing in the * COMEDI acquisition data buffer associated with the subdevice.  The amount of * space freed is limited to the amount that was reserved.  The freed space is * assumed to have been filled with sample data by the writer. * * If the samples in the freed space need to be "munged", do so here.  The * freed space becomes available for allocation by the reader. * * Return: The amount of space freed in bytes. */unsigned int comedi_buf_write_free(struct comedi_subdevice *s,				   unsigned int nbytes){	if (comedi_get_is_subdevice_running(s)) {		nbytes = _comedi_buf_write_free(s, nbytes);		comedi_put_is_subdevice_running(s);	} else {		nbytes = 0;	}	return nbytes;}EXPORT_SYMBOL_GPL(comedi_buf_write_free); unsigned int _comedi_buf_read_n_available(struct comedi_subdevice *s){	struct comedi_async *async = s->async;	unsigned int num_bytes; 	if (!async)		return 0; 	num_bytes = async->munge_count - async->buf_read_count; 	/*	 * ensure the async buffer 'counts' are read before we	 * attempt to read data from the buffer	 */	smp_rmb(); 	return num_bytes;} /** * comedi_buf_read_n_available() - Determine amount of readable buffer space * @s: COMEDI subdevice. * * Determine the amount of readable buffer space in the COMEDI acquisition data * buffer associated with the subdevice.  The readable buffer space is that * which has been freed by the writer and "munged" to the sample data format * expected by COMEDI if necessary. * * Return: The amount of readable buffer space. */unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s){	unsigned int num_bytes; 	if (comedi_get_is_subdevice_running(s)) {		num_bytes = _comedi_buf_read_n_available(s);		comedi_put_is_subdevice_running(s);	} else {		num_bytes = 0;	}	return num_bytes;}EXPORT_SYMBOL_GPL(comedi_buf_read_n_available); unsigned int _comedi_buf_read_alloc(struct comedi_subdevice *s,				    unsigned int nbytes){	struct comedi_async *async = s->async;	unsigned int available; 	available = async->munge_count - async->buf_read_alloc_count;	if (nbytes > available)		nbytes = available; 	async->buf_read_alloc_count += nbytes; 	/*	 * ensure the async buffer 'counts' are read before we	 * attempt to read data from the read-alloc'ed buffer space	 */	smp_rmb(); 	return nbytes;} /** * comedi_buf_read_alloc() - Reserve buffer space for reading * @s: COMEDI subdevice. * @nbytes: Maximum space to reserve in bytes. * * Reserve up to @nbytes bytes of previously written and "munged" buffer space * for reading in the COMEDI acquisition data buffer associated with the * subdevice.  The amount reserved is limited to the space available.  The * reader can read from the reserved space and then free it.  A reader is also * allowed to read from the space before reserving it as long as it determines * the amount of readable data available, but the space needs to be marked as * reserved before it can be freed. *

Lines 1–500 of 738.