summaryrefslogblamecommitdiffstats
path: root/lib/iov_iter.c
blob: 75232ad0a5e7ead00e5d8396ed34763d84a0685c (plain) (tree)
1
2
3
4
5
6
7


                          

                          
                         
 



























                                                         






















                                                         
























                                                                 
                                                                 




                                                                 



                                                                 






                                                                 
                                                                 










                                                                 









                                                                 














                                                                 
                                                                                     




































































                                                                        



                                   





                                   
 
                                                                                       




































































                                                                        



                                   





                                   
 










                                                                         
                                                 




                                                                    


                                          

























                                                                             




                                                                       
                                              
                                       





                                             




                             
 













                                                                                    






                                                                      
                                                                 
 
                          





                                       



                                                                           

                                                                              
         
 
                     
 
                            
 
                                                                   
 










                                                                           

                                                                            


                     
 
                              
 




















                                                                            


                                                                        





                                                                       






                                                                          
                                              




                                                                         



                                                                         

                                                      







                                                    

                                                               


                     


                             


                                                                       




                                                                       

                                                                           


                             




                                                      
                                                










                                                          
                                                                      

                                                                      


                                         
                                                     
                                                                       



                                         
                       





                             












                                                                          

                                                          







                                                                  

                                                            

                   



                                              
                                                                          

                                 
























                                                                                      

                               


                 


                                  







                                                                         



                                                        

































                                                                                  

                               


                 


                                        























































































                                                                                 

                                                           















                                                                





                                                                


                      
 
                               














                                                                             
























































                                                                             
#include <linux/export.h>
#include <linux/uio.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <net/checksum.h>

#define iterate_iovec(i, n, __v, __p, skip, STEP) {	\
	size_t left;					\
	size_t wanted = n;				\
	__p = i->iov;					\
	__v.iov_len = min(n, __p->iov_len - skip);	\
	if (likely(__v.iov_len)) {			\
		__v.iov_base = __p->iov_base + skip;	\
		left = (STEP);				\
		__v.iov_len -= left;			\
		skip += __v.iov_len;			\
		n -= __v.iov_len;			\
	} else {					\
		left = 0;				\
	}						\
	while (unlikely(!left && n)) {			\
		__p++;					\
		__v.iov_len = min(n, __p->iov_len);	\
		if (unlikely(!__v.iov_len))		\
			continue;			\
		__v.iov_base = __p->iov_base;		\
		left = (STEP);				\
		__v.iov_len -= left;			\
		skip = __v.iov_len;			\
		n -= __v.iov_len;			\
	}						\
	n = wanted - n;					\
}

#define iterate_kvec(i, n, __v, __p, skip, STEP) {	\
	size_t wanted = n;				\
	__p = i->kvec;					\
	__v.iov_len = min(n, __p->iov_len - skip);	\
	if (likely(__v.iov_len)) {			\
		__v.iov_base = __p->iov_base + skip;	\
		(void)(STEP);				\
		skip += __v.iov_len;			\
		n -= __v.iov_len;			\
	}						\
	while (unlikely(n)) {				\
		__p++;					\
		__v.iov_len = min(n, __p->iov_len);	\
		if (unlikely(!__v.iov_len))		\
			continue;			\
		__v.iov_base = __p->iov_base;		\
		(void)(STEP);				\
		skip = __v.iov_len;			\
		n -= __v.iov_len;			\
	}						\
	n = wanted;					\
}

#define iterate_bvec(i, n, __v, __p, skip, STEP) {	\
	size_t wanted = n;				\
	__p = i->bvec;					\
	__v.bv_len = min_t(size_t, n, __p->bv_len - skip);	\
	if (likely(__v.bv_len)) {			\
		__v.bv_page = __p->bv_page;		\
		__v.bv_offset = __p->bv_offset + skip; 	\
		(void)(STEP);				\
		skip += __v.bv_len;			\
		n -= __v.bv_len;			\
	}						\
	while (unlikely(n)) {				\
		__p++;					\
		__v.bv_len = min_t(size_t, n, __p->bv_len);	\
		if (unlikely(!__v.bv_len))		\
			continue;			\
		__v.bv_page = __p->bv_page;		\
		__v.bv_offset = __p->bv_offset;		\
		(void)(STEP);				\
		skip = __v.bv_len;			\
		n -= __v.bv_len;			\
	}						\
	n = wanted;					\
}

#define iterate_all_kinds(i, n, v, I, B, K) {			\
	size_t skip = i->iov_offset;				\
	if (unlikely(i->type & ITER_BVEC)) {			\
		const struct bio_vec *bvec;			\
		struct bio_vec v;				\
		iterate_bvec(i, n, v, bvec, skip, (B))		\
	} else if (unlikely(i->type & ITER_KVEC)) {		\
		const struct kvec *kvec;			\
		struct kvec v;					\
		iterate_kvec(i, n, v, kvec, skip, (K))		\
	} else {						\
		const struct iovec *iov;			\
		struct iovec v;					\
		iterate_iovec(i, n, v, iov, skip, (I))		\
	}							\
}

#define iterate_and_advance(i, n, v, I, B, K) {			\
	size_t skip = i->iov_offset;				\
	if (unlikely(i->type & ITER_BVEC)) {			\
		const struct bio_vec *bvec;			\
		struct bio_vec v;				\
		iterate_bvec(i, n, v, bvec, skip, (B))		\
		if (skip == bvec->bv_len) {			\
			bvec++;					\
			skip = 0;				\
		}						\
		i->nr_segs -= bvec - i->bvec;			\
		i->bvec = bvec;					\
	} else if (unlikely(i->type & ITER_KVEC)) {		\
		const struct kvec *kvec;			\
		struct kvec v;					\
		iterate_kvec(i, n, v, kvec, skip, (K))		\
		if (skip == kvec->iov_len) {			\
			kvec++;					\
			skip = 0;				\
		}						\
		i->nr_segs -= kvec - i->kvec;			\
		i->kvec = kvec;					\
	} else {						\
		const struct iovec *iov;			\
		struct iovec v;					\
		iterate_iovec(i, n, v, iov, skip, (I))		\
		if (skip == iov->iov_len) {			\
			iov++;					\
			skip = 0;				\
		}						\
		i->nr_segs -= iov - i->iov;			\
		i->iov = iov;					\
	}							\
	i->count -= n;						\
	i->iov_offset = skip;					\
}

static size_t copy_page_to_iter_iovec(struct page *page, size_t offset, size_t bytes,
			 struct iov_iter *i)
{
	size_t skip, copy, left, wanted;
	const struct iovec *iov;
	char __user *buf;
	void *kaddr, *from;

	if (unlikely(bytes > i->count))
		bytes = i->count;

	if (unlikely(!bytes))
		return 0;

	wanted = bytes;
	iov = i->iov;
	skip = i->iov_offset;
	buf = iov->iov_base + skip;
	copy = min(bytes, iov->iov_len - skip);

	if (!fault_in_pages_writeable(buf, copy)) {
		kaddr = kmap_atomic(page);
		from = kaddr + offset;

		/* first chunk, usually the only one */
		left = __copy_to_user_inatomic(buf, from, copy);
		copy -= left;
		skip += copy;
		from += copy;
		bytes -= copy;

		while (unlikely(!left && bytes)) {
			iov++;
			buf = iov->iov_base;
			copy = min(bytes, iov->iov_len);
			left = __copy_to_user_inatomic(buf, from, copy);
			copy -= left;
			skip = copy;
			from += copy;
			bytes -= copy;
		}
		if (likely(!bytes)) {
			kunmap_atomic(kaddr);
			goto done;
		}
		offset = from - kaddr;
		buf += copy;
		kunmap_atomic(kaddr);
		copy = min(bytes, iov->iov_len - skip);
	}
	/* Too bad - revert to non-atomic kmap */
	kaddr = kmap(page);
	from = kaddr + offset;
	left = __copy_to_user(buf, from, copy);
	copy -= left;
	skip += copy;
	from += copy;
	bytes -= copy;
	while (unlikely(!left && bytes)) {
		iov++;
		buf = iov->iov_base;
		copy = min(bytes, iov->iov_len);
		left = __copy_to_user(buf, from, copy);
		copy -= left;
		skip = copy;
		from += copy;
		bytes -= copy;
	}
	kunmap(page);
done:
	if (skip == iov->iov_len) {
		iov++;
		skip = 0;
	}
	i->count -= wanted - bytes;
	i->nr_segs -= iov - i->iov;
	i->iov = iov;
	i->iov_offset = skip;
	return wanted - bytes;
}

static size_t copy_page_from_iter_iovec(struct page *page, size_t offset, size_t bytes,
			 struct iov_iter *i)
{
	size_t skip, copy, left, wanted;
	const struct iovec *iov;
	char __user *buf;
	void *kaddr, *to;

	if (unlikely(bytes > i->count))
		bytes = i->count;

	if (unlikely(!bytes))
		return 0;

	wanted = bytes;
	iov = i->iov;
	skip = i->iov_offset;
	buf = iov->iov_base + skip;
	copy = min(bytes, iov->iov_len - skip);

	if (!fault_in_pages_readable(buf, copy)) {
		kaddr = kmap_atomic(page);
		to = kaddr + offset;

		/* first chunk, usually the only one */
		left = __copy_from_user_inatomic(to, buf, copy);
		copy -= left;
		skip += copy;
		to += copy;
		bytes -= copy;

		while (unlikely(!left && bytes)) {
			iov++;
			buf = iov->iov_base;
			copy = min(bytes, iov->iov_len);
			left = __copy_from_user_inatomic(to, buf, copy);
			copy -= left;
			skip = copy;
			to += copy;
			bytes -= copy;
		}
		if (likely(!bytes)) {
			kunmap_atomic(kaddr);
			goto done;
		}
		offset = to - kaddr;
		buf += copy;
		kunmap_atomic(kaddr);
		copy = min(bytes, iov->iov_len - skip);
	}
	/* Too bad - revert to non-atomic kmap */
	kaddr = kmap(page);
	to = kaddr + offset;
	left = __copy_from_user(to, buf, copy);
	copy -= left;
	skip += copy;
	to += copy;
	bytes -= copy;
	while (unlikely(!left && bytes)) {
		iov++;
		buf = iov->iov_base;
		copy = min(bytes, iov->iov_len);
		left = __copy_from_user(to, buf, copy);
		copy -= left;
		skip = copy;
		to += copy;
		bytes -= copy;
	}
	kunmap(page);
done:
	if (skip == iov->iov_len) {
		iov++;
		skip = 0;
	}
	i->count -= wanted - bytes;
	i->nr_segs -= iov - i->iov;
	i->iov = iov;
	i->iov_offset = skip;
	return wanted - bytes;
}

/*
 * Fault in the first iovec of the given iov_iter, to a maximum length
 * of bytes. Returns 0 on success, or non-zero if the memory could not be
 * accessed (ie. because it is an invalid address).
 *
 * writev-intensive code may want this to prefault several iovecs -- that
 * would be possible (callers must not rely on the fact that _only_ the
 * first iovec will be faulted with the current implementation).
 */
int iov_iter_fault_in_readable(struct iov_iter *i, size_t bytes)
{
	if (!(i->type & (ITER_BVEC|ITER_KVEC))) {
		char __user *buf = i->iov->iov_base + i->iov_offset;
		bytes = min(bytes, i->iov->iov_len - i->iov_offset);
		return fault_in_pages_readable(buf, bytes);
	}
	return 0;
}
EXPORT_SYMBOL(iov_iter_fault_in_readable);

/*
 * Fault in one or more iovecs of the given iov_iter, to a maximum length of
 * bytes.  For each iovec, fault in each page that constitutes the iovec.
 *
 * Return 0 on success, or non-zero if the memory could not be accessed (i.e.
 * because it is an invalid address).
 */
int iov_iter_fault_in_multipages_readable(struct iov_iter *i, size_t bytes)
{
	size_t skip = i->iov_offset;
	const struct iovec *iov;
	int err;
	struct iovec v;

	if (!(i->type & (ITER_BVEC|ITER_KVEC))) {
		iterate_iovec(i, bytes, v, iov, skip, ({
			err = fault_in_multipages_readable(v.iov_base,
					v.iov_len);
			if (unlikely(err))
			return err;
		0;}))
	}
	return 0;
}
EXPORT_SYMBOL(iov_iter_fault_in_multipages_readable);

void iov_iter_init(struct iov_iter *i, int direction,
			const struct iovec *iov, unsigned long nr_segs,
			size_t count)
{
	/* It will get better.  Eventually... */
	if (segment_eq(get_fs(), KERNEL_DS)) {
		direction |= ITER_KVEC;
		i->type = direction;
		i->kvec = (struct kvec *)iov;
	} else {
		i->type = direction;
		i->iov = iov;
	}
	i->nr_segs = nr_segs;
	i->iov_offset = 0;
	i->count = count;
}
EXPORT_SYMBOL(iov_iter_init);

static void memcpy_from_page(char *to, struct page *page, size_t offset, size_t len)
{
	char *from = kmap_atomic(page);
	memcpy(to, from + offset, len);
	kunmap_atomic(from);
}

static void memcpy_to_page(struct page *page, size_t offset, char *from, size_t len)
{
	char *to = kmap_atomic(page);
	memcpy(to + offset, from, len);
	kunmap_atomic(to);
}

static void memzero_page(struct page *page, size_t offset, size_t len)
{
	char *addr = kmap_atomic(page);
	memset(addr + offset, 0, len);
	kunmap_atomic(addr);
}

size_t copy_to_iter(void *addr, size_t bytes, struct iov_iter *i)
{
	char *from = addr;
	if (unlikely(bytes > i->count))
		bytes = i->count;

	if (unlikely(!bytes))
		return 0;

	iterate_and_advance(i, bytes, v,
		__copy_to_user(v.iov_base, (from += v.iov_len) - v.iov_len,
			       v.iov_len),
		memcpy_to_page(v.bv_page, v.bv_offset,
			       (from += v.bv_len) - v.bv_len, v.bv_len),
		memcpy(v.iov_base, (from += v.iov_len) - v.iov_len, v.iov_len)
	)

	return bytes;
}
EXPORT_SYMBOL(copy_to_iter);

size_t copy_from_iter(void *addr, size_t bytes, struct iov_iter *i)
{
	char *to = addr;
	if (unlikely(bytes > i->count))
		bytes = i->count;

	if (unlikely(!bytes))
		return 0;

	iterate_and_advance(i, bytes, v,
		__copy_from_user((to += v.iov_len) - v.iov_len, v.iov_base,
				 v.iov_len),
		memcpy_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
				 v.bv_offset, v.bv_len),
		memcpy((to += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
	)

	return bytes;
}
EXPORT_SYMBOL(copy_from_iter);

size_t copy_from_iter_nocache(void *addr, size_t bytes, struct iov_iter *i)
{
	char *to = addr;
	if (unlikely(bytes > i->count))
		bytes = i->count;

	if (unlikely(!bytes))
		return 0;

	iterate_and_advance(i, bytes, v,
		__copy_from_user_nocache((to += v.iov_len) - v.iov_len,
					 v.iov_base, v.iov_len),
		memcpy_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
				 v.bv_offset, v.bv_len),
		memcpy((to += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
	)

	return bytes;
}
EXPORT_SYMBOL(copy_from_iter_nocache);

size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
			 struct iov_iter *i)
{
	if (i->type & (ITER_BVEC|ITER_KVEC)) {
		void *kaddr = kmap_atomic(page);
		size_t wanted = copy_to_iter(kaddr + offset, bytes, i);
		kunmap_atomic(kaddr);
		return wanted;
	} else
		return copy_page_to_iter_iovec(page, offset, bytes, i);
}
EXPORT_SYMBOL(copy_page_to_iter);

size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
			 struct iov_iter *i)
{
	if (i->type & (ITER_BVEC|ITER_KVEC)) {
		void *kaddr = kmap_atomic(page);
		size_t wanted = copy_from_iter(kaddr + offset, bytes, i);
		kunmap_atomic(kaddr);
		return wanted;
	} else
		return copy_page_from_iter_iovec(page, offset, bytes, i);
}
EXPORT_SYMBOL(copy_page_from_iter);

size_t iov_iter_zero(size_t bytes, struct iov_iter *i)
{
	if (unlikely(bytes > i->count))
		bytes = i->count;

	if (unlikely(!bytes))
		return 0;

	iterate_and_advance(i, bytes, v,
		__clear_user(v.iov_base, v.iov_len),
		memzero_page(v.bv_page, v.bv_offset, v.bv_len),
		memset(v.iov_base, 0, v.iov_len)
	)

	return bytes;
}
EXPORT_SYMBOL(iov_iter_zero);

size_t iov_iter_copy_from_user_atomic(struct page *page,
		struct iov_iter *i, unsigned long offset, size_t bytes)
{
	char *kaddr = kmap_atomic(page), *p = kaddr + offset;
	iterate_all_kinds(i, bytes, v,
		__copy_from_user_inatomic((p += v.iov_len) - v.iov_len,
					  v.iov_base, v.iov_len),
		memcpy_from_page((p += v.bv_len) - v.bv_len, v.bv_page,
				 v.bv_offset, v.bv_len),
		memcpy((p += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
	)
	kunmap_atomic(kaddr);
	return bytes;
}
EXPORT_SYMBOL(iov_iter_copy_from_user_atomic);

void iov_iter_advance(struct iov_iter *i, size_t size)
{
	iterate_and_advance(i, size, v, 0, 0, 0)
}
EXPORT_SYMBOL(iov_iter_advance);

/*
 * Return the count of just the current iov_iter segment.
 */
size_t iov_iter_single_seg_count(const struct iov_iter *i)
{
	if (i->nr_segs == 1)
		return i->count;
	else if (i->type & ITER_BVEC)
		return min(i->count, i->bvec->bv_len - i->iov_offset);
	else
		return min(i->count, i->iov->iov_len - i->iov_offset);
}
EXPORT_SYMBOL(iov_iter_single_seg_count);

void iov_iter_kvec(struct iov_iter *i, int direction,
			const struct kvec *kvec, unsigned long nr_segs,
			size_t count)
{
	BUG_ON(!(direction & ITER_KVEC));
	i->type = direction;
	i->kvec = kvec;
	i->nr_segs = nr_segs;
	i->iov_offset = 0;
	i->count = count;
}
EXPORT_SYMBOL(iov_iter_kvec);

void iov_iter_bvec(struct iov_iter *i, int direction,
			const struct bio_vec *bvec, unsigned long nr_segs,
			size_t count)
{
	BUG_ON(!(direction & ITER_BVEC));
	i->type = direction;
	i->bvec = bvec;
	i->nr_segs = nr_segs;
	i->iov_offset = 0;
	i->count = count;
}
EXPORT_SYMBOL(iov_iter_bvec);

unsigned long iov_iter_alignment(const struct iov_iter *i)
{
	unsigned long res = 0;
	size_t size = i->count;

	if (!size)
		return 0;

	iterate_all_kinds(i, size, v,
		(res |= (unsigned long)v.iov_base | v.iov_len, 0),
		res |= v.bv_offset | v.bv_len,
		res |= (unsigned long)v.iov_base | v.iov_len
	)
	return res;
}
EXPORT_SYMBOL(iov_iter_alignment);

ssize_t iov_iter_get_pages(struct iov_iter *i,
		   struct page **pages, size_t maxsize, unsigned maxpages,
		   size_t *start)
{
	if (maxsize > i->count)
		maxsize = i->count;

	if (!maxsize)
		return 0;

	iterate_all_kinds(i, maxsize, v, ({
		unsigned long addr = (unsigned long)v.iov_base;
		size_t len = v.iov_len + (*start = addr & (PAGE_SIZE - 1));
		int n;
		int res;

		if (len > maxpages * PAGE_SIZE)
			len = maxpages * PAGE_SIZE;
		addr &= ~(PAGE_SIZE - 1);
		n = DIV_ROUND_UP(len, PAGE_SIZE);
		res = get_user_pages_fast(addr, n, (i->type & WRITE) != WRITE, pages);
		if (unlikely(res < 0))
			return res;
		return (res == n ? len : res * PAGE_SIZE) - *start;
	0;}),({
		/* can't be more than PAGE_SIZE */
		*start = v.bv_offset;
		get_page(*pages = v.bv_page);
		return v.bv_len;
	}),({
		return -EFAULT;
	})
	)
	return 0;
}
EXPORT_SYMBOL(iov_iter_get_pages);

static struct page **get_pages_array(size_t n)
{
	struct page **p = kmalloc(n * sizeof(struct page *), GFP_KERNEL);
	if (!p)
		p = vmalloc(n * sizeof(struct page *));
	return p;
}

ssize_t iov_iter_get_pages_alloc(struct iov_iter *i,
		   struct page ***pages, size_t maxsize,
		   size_t *start)
{
	struct page **p;

	if (maxsize > i->count)
		maxsize = i->count;

	if (!maxsize)
		return 0;

	iterate_all_kinds(i, maxsize, v, ({
		unsigned long addr = (unsigned long)v.iov_base;
		size_t len = v.iov_len + (*start = addr & (PAGE_SIZE - 1));
		int n;
		int res;

		addr &= ~(PAGE_SIZE - 1);
		n = DIV_ROUND_UP(len, PAGE_SIZE);
		p = get_pages_array(n);
		if (!p)
			return -ENOMEM;
		res = get_user_pages_fast(addr, n, (i->type & WRITE) != WRITE, p);
		if (unlikely(res < 0)) {
			kvfree(p);
			return res;
		}
		*pages = p;
		return (res == n ? len : res * PAGE_SIZE) - *start;
	0;}),({
		/* can't be more than PAGE_SIZE */
		*start = v.bv_offset;
		*pages = p = get_pages_array(1);
		if (!p)
			return -ENOMEM;
		get_page(*p = v.bv_page);
		return v.bv_len;
	}),({
		return -EFAULT;
	})
	)
	return 0;
}
EXPORT_SYMBOL(iov_iter_get_pages_alloc);

size_t csum_and_copy_from_iter(void *addr, size_t bytes, __wsum *csum,
			       struct iov_iter *i)
{
	char *to = addr;
	__wsum sum, next;
	size_t off = 0;
	if (unlikely(bytes > i->count))
		bytes = i->count;

	if (unlikely(!bytes))
		return 0;

	sum = *csum;
	iterate_and_advance(i, bytes, v, ({
		int err = 0;
		next = csum_and_copy_from_user(v.iov_base, 
					       (to += v.iov_len) - v.iov_len,
					       v.iov_len, 0, &err);
		if (!err) {
			sum = csum_block_add(sum, next, off);
			off += v.iov_len;
		}
		err ? v.iov_len : 0;
	}), ({
		char *p = kmap_atomic(v.bv_page);
		next = csum_partial_copy_nocheck(p + v.bv_offset,
						 (to += v.bv_len) - v.bv_len,
						 v.bv_len, 0);
		kunmap_atomic(p);
		sum = csum_block_add(sum, next, off);
		off += v.bv_len;
	}),({
		next = csum_partial_copy_nocheck(v.iov_base,
						 (to += v.iov_len) - v.iov_len,
						 v.iov_len, 0);
		sum = csum_block_add(sum, next, off);
		off += v.iov_len;
	})
	)
	*csum = sum;
	return bytes;
}
EXPORT_SYMBOL(csum_and_copy_from_iter);

size_t csum_and_copy_to_iter(void *addr, size_t bytes, __wsum *csum,
			     struct iov_iter *i)
{
	char *from = addr;
	__wsum sum, next;
	size_t off = 0;
	if (unlikely(bytes > i->count))
		bytes = i->count;

	if (unlikely(!bytes))
		return 0;

	sum = *csum;
	iterate_and_advance(i, bytes, v, ({
		int err = 0;
		next = csum_and_copy_to_user((from += v.iov_len) - v.iov_len,
					     v.iov_base, 
					     v.iov_len, 0, &err);
		if (!err) {
			sum = csum_block_add(sum, next, off);
			off += v.iov_len;
		}
		err ? v.iov_len : 0;
	}), ({
		char *p = kmap_atomic(v.bv_page);
		next = csum_partial_copy_nocheck((from += v.bv_len) - v.bv_len,
						 p + v.bv_offset,
						 v.bv_len, 0);
		kunmap_atomic(p);
		sum = csum_block_add(sum, next, off);
		off += v.bv_len;
	}),({
		next = csum_partial_copy_nocheck((from += v.iov_len) - v.iov_len,
						 v.iov_base,
						 v.iov_len, 0);
		sum = csum_block_add(sum, next, off);
		off += v.iov_len;
	})
	)
	*csum = sum;
	return bytes;
}
EXPORT_SYMBOL(csum_and_copy_to_iter);

int iov_iter_npages(const struct iov_iter *i, int maxpages)
{
	size_t size = i->count;
	int npages = 0;

	if (!size)
		return 0;

	iterate_all_kinds(i, size, v, ({
		unsigned long p = (unsigned long)v.iov_base;
		npages += DIV_ROUND_UP(p + v.iov_len, PAGE_SIZE)
			- p / PAGE_SIZE;
		if (npages >= maxpages)
			return maxpages;
	0;}),({
		npages++;
		if (npages >= maxpages)
			return maxpages;
	}),({
		unsigned long p = (unsigned long)v.iov_base;
		npages += DIV_ROUND_UP(p + v.iov_len, PAGE_SIZE)
			- p / PAGE_SIZE;
		if (npages >= maxpages)
			return maxpages;
	})
	)
	return npages;
}
EXPORT_SYMBOL(iov_iter_npages);

const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags)
{
	*new = *old;
	if (new->type & ITER_BVEC)
		return new->bvec = kmemdup(new->bvec,
				    new->nr_segs * sizeof(struct bio_vec),
				    flags);
	else
		/* iovec and kvec have identical layout */
		return new->iov = kmemdup(new->iov,
				   new->nr_segs * sizeof(struct iovec),
				   flags);
}
EXPORT_SYMBOL(dup_iter);

int import_iovec(int type, const struct iovec __user * uvector,
		 unsigned nr_segs, unsigned fast_segs,
		 struct iovec **iov, struct iov_iter *i)
{
	ssize_t n;
	struct iovec *p;
	n = rw_copy_check_uvector(type, uvector, nr_segs, fast_segs,
				  *iov, &p);
	if (n < 0) {
		if (p != *iov)
			kfree(p);
		*iov = NULL;
		return n;
	}
	iov_iter_init(i, type, p, nr_segs, n);
	*iov = p == *iov ? NULL : p;
	return 0;
}
EXPORT_SYMBOL(import_iovec);

#ifdef CONFIG_COMPAT
#include <linux/compat.h>

int compat_import_iovec(int type, const struct compat_iovec __user * uvector,
		 unsigned nr_segs, unsigned fast_segs,
		 struct iovec **iov, struct iov_iter *i)
{
	ssize_t n;
	struct iovec *p;
	n = compat_rw_copy_check_uvector(type, uvector, nr_segs, fast_segs,
				  *iov, &p);
	if (n < 0) {
		if (p != *iov)
			kfree(p);
		*iov = NULL;
		return n;
	}
	iov_iter_init(i, type, p, nr_segs, n);
	*iov = p == *iov ? NULL : p;
	return 0;
}
#endif

int import_single_range(int rw, void __user *buf, size_t len,
		 struct iovec *iov, struct iov_iter *i)
{
	if (len > MAX_RW_COUNT)
		len = MAX_RW_COUNT;
	if (unlikely(!access_ok(!rw, buf, len)))
		return -EFAULT;

	iov->iov_base = buf;
	iov->iov_len = len;
	iov_iter_init(i, rw, iov, 1, len);
	return 0;
}
t;linux/slab.h> #include <linux/of_address.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/of_dma.h> #include <linux/of_platform.h> #include <linux/random.h> #include "dmaengine.h" /* Number of DMA Transfer descriptors allocated per channel */ #define MPC_DMA_DESCRIPTORS 64 /* Macro definitions */ #define MPC_DMA_TCD_OFFSET 0x1000 /* * Maximum channel counts for individual hardware variants * and the maximum channel count over all supported controllers, * used for data structure size */ #define MPC8308_DMACHAN_MAX 16 #define MPC512x_DMACHAN_MAX 64 #define MPC_DMA_CHANNELS 64 /* Arbitration mode of group and channel */ #define MPC_DMA_DMACR_EDCG (1 << 31) #define MPC_DMA_DMACR_ERGA (1 << 3) #define MPC_DMA_DMACR_ERCA (1 << 2) /* Error codes */ #define MPC_DMA_DMAES_VLD (1 << 31) #define MPC_DMA_DMAES_GPE (1 << 15) #define MPC_DMA_DMAES_CPE (1 << 14) #define MPC_DMA_DMAES_ERRCHN(err) \ (((err) >> 8) & 0x3f) #define MPC_DMA_DMAES_SAE (1 << 7) #define MPC_DMA_DMAES_SOE (1 << 6) #define MPC_DMA_DMAES_DAE (1 << 5) #define MPC_DMA_DMAES_DOE (1 << 4) #define MPC_DMA_DMAES_NCE (1 << 3) #define MPC_DMA_DMAES_SGE (1 << 2) #define MPC_DMA_DMAES_SBE (1 << 1) #define MPC_DMA_DMAES_DBE (1 << 0) #define MPC_DMA_DMAGPOR_SNOOP_ENABLE (1 << 6) #define MPC_DMA_TSIZE_1 0x00 #define MPC_DMA_TSIZE_2 0x01 #define MPC_DMA_TSIZE_4 0x02 #define MPC_DMA_TSIZE_16 0x04 #define MPC_DMA_TSIZE_32 0x05 /* MPC5121 DMA engine registers */ struct __attribute__ ((__packed__)) mpc_dma_regs { /* 0x00 */ u32 dmacr; /* DMA control register */ u32 dmaes; /* DMA error status */ /* 0x08 */ u32 dmaerqh; /* DMA enable request high(channels 63~32) */ u32 dmaerql; /* DMA enable request low(channels 31~0) */ u32 dmaeeih; /* DMA enable error interrupt high(ch63~32) */ u32 dmaeeil; /* DMA enable error interrupt low(ch31~0) */ /* 0x18 */ u8 dmaserq; /* DMA set enable request */ u8 dmacerq; /* DMA clear enable request */ u8 dmaseei; /* DMA set enable error interrupt */ u8 dmaceei; /* DMA clear enable error interrupt */ /* 0x1c */ u8 dmacint; /* DMA clear interrupt request */ u8 dmacerr; /* DMA clear error */ u8 dmassrt; /* DMA set start bit */ u8 dmacdne; /* DMA clear DONE status bit */ /* 0x20 */ u32 dmainth; /* DMA interrupt request high(ch63~32) */ u32 dmaintl; /* DMA interrupt request low(ch31~0) */ u32 dmaerrh; /* DMA error high(ch63~32) */ u32 dmaerrl; /* DMA error low(ch31~0) */ /* 0x30 */ u32 dmahrsh; /* DMA hw request status high(ch63~32) */ u32 dmahrsl; /* DMA hardware request status low(ch31~0) */ union { u32 dmaihsa; /* DMA interrupt high select AXE(ch63~32) */ u32 dmagpor; /* (General purpose register on MPC8308) */ }; u32 dmailsa; /* DMA interrupt low select AXE(ch31~0) */ /* 0x40 ~ 0xff */ u32 reserve0[48]; /* Reserved */ /* 0x100 */ u8 dchpri[MPC_DMA_CHANNELS]; /* DMA channels(0~63) priority */ }; struct __attribute__ ((__packed__)) mpc_dma_tcd { /* 0x00 */ u32 saddr; /* Source address */ u32 smod:5; /* Source address modulo */ u32 ssize:3; /* Source data transfer size */ u32 dmod:5; /* Destination address modulo */ u32 dsize:3; /* Destination data transfer size */ u32 soff:16; /* Signed source address offset */ /* 0x08 */ u32 nbytes; /* Inner "minor" byte count */ u32 slast; /* Last source address adjustment */ u32 daddr; /* Destination address */ /* 0x14 */ u32 citer_elink:1; /* Enable channel-to-channel linking on * minor loop complete */ u32 citer_linkch:6; /* Link channel for minor loop complete */ u32 citer:9; /* Current "major" iteration count */ u32 doff:16; /* Signed destination address offset */ /* 0x18 */ u32 dlast_sga; /* Last Destination address adjustment/scatter * gather address */ /* 0x1c */ u32 biter_elink:1; /* Enable channel-to-channel linking on major * loop complete */ u32 biter_linkch:6; u32 biter:9; /* Beginning "major" iteration count */ u32 bwc:2; /* Bandwidth control */ u32 major_linkch:6; /* Link channel number */ u32 done:1; /* Channel done */ u32 active:1; /* Channel active */ u32 major_elink:1; /* Enable channel-to-channel linking on major * loop complete */ u32 e_sg:1; /* Enable scatter/gather processing */ u32 d_req:1; /* Disable request */ u32 int_half:1; /* Enable an interrupt when major counter is * half complete */ u32 int_maj:1; /* Enable an interrupt when major iteration * count completes */ u32 start:1; /* Channel start */ }; struct mpc_dma_desc { struct dma_async_tx_descriptor desc; struct mpc_dma_tcd *tcd; dma_addr_t tcd_paddr; int error; struct list_head node; int will_access_peripheral; }; struct mpc_dma_chan { struct dma_chan chan; struct list_head free; struct list_head prepared; struct list_head queued; struct list_head active; struct list_head completed; struct mpc_dma_tcd *tcd; dma_addr_t tcd_paddr; /* Settings for access to peripheral FIFO */ dma_addr_t src_per_paddr; u32 src_tcd_nunits; u8 swidth; dma_addr_t dst_per_paddr; u32 dst_tcd_nunits; u8 dwidth; /* Lock for this structure */ spinlock_t lock; }; struct mpc_dma { struct dma_device dma; struct tasklet_struct tasklet; struct mpc_dma_chan channels[MPC_DMA_CHANNELS]; struct mpc_dma_regs __iomem *regs; struct mpc_dma_tcd __iomem *tcd; int irq; int irq2; uint error_status; int is_mpc8308; /* Lock for error_status field in this structure */ spinlock_t error_status_lock; }; #define DRV_NAME "mpc512x_dma" /* Convert struct dma_chan to struct mpc_dma_chan */ static inline struct mpc_dma_chan *dma_chan_to_mpc_dma_chan(struct dma_chan *c) { return container_of(c, struct mpc_dma_chan, chan); } /* Convert struct dma_chan to struct mpc_dma */ static inline struct mpc_dma *dma_chan_to_mpc_dma(struct dma_chan *c) { struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(c); return container_of(mchan, struct mpc_dma, channels[c->chan_id]); } /* * Execute all queued DMA descriptors. * * Following requirements must be met while calling mpc_dma_execute(): * a) mchan->lock is acquired, * b) mchan->active list is empty, * c) mchan->queued list contains at least one entry. */ static void mpc_dma_execute(struct mpc_dma_chan *mchan) { struct mpc_dma *mdma = dma_chan_to_mpc_dma(&mchan->chan); struct mpc_dma_desc *first = NULL; struct mpc_dma_desc *prev = NULL; struct mpc_dma_desc *mdesc; int cid = mchan->chan.chan_id; while (!list_empty(&mchan->queued)) { mdesc = list_first_entry(&mchan->queued, struct mpc_dma_desc, node); /* * Grab either several mem-to-mem transfer descriptors * or one peripheral transfer descriptor, * don't mix mem-to-mem and peripheral transfer descriptors * within the same 'active' list. */ if (mdesc->will_access_peripheral) { if (list_empty(&mchan->active)) list_move_tail(&mdesc->node, &mchan->active); break; } else { list_move_tail(&mdesc->node, &mchan->active); } } /* Chain descriptors into one transaction */ list_for_each_entry(mdesc, &mchan->active, node) { if (!first) first = mdesc; if (!prev) { prev = mdesc; continue; } prev->tcd->dlast_sga = mdesc->tcd_paddr; prev->tcd->e_sg = 1; mdesc->tcd->start = 1; prev = mdesc; } prev->tcd->int_maj = 1; /* Send first descriptor in chain into hardware */ memcpy_toio(&mdma->tcd[cid], first->tcd, sizeof(struct mpc_dma_tcd)); if (first != prev) mdma->tcd[cid].e_sg = 1; if (mdma->is_mpc8308) { /* MPC8308, no request lines, software initiated start */ out_8(&mdma->regs->dmassrt, cid); } else if (first->will_access_peripheral) { /* Peripherals involved, start by external request signal */ out_8(&mdma->regs->dmaserq, cid); } else { /* Memory to memory transfer, software initiated start */ out_8(&mdma->regs->dmassrt, cid); } } /* Handle interrupt on one half of DMA controller (32 channels) */ static void mpc_dma_irq_process(struct mpc_dma *mdma, u32 is, u32 es, int off) { struct mpc_dma_chan *mchan; struct mpc_dma_desc *mdesc; u32 status = is | es; int ch; while ((ch = fls(status) - 1) >= 0) { status &= ~(1 << ch); mchan = &mdma->channels[ch + off]; spin_lock(&mchan->lock); out_8(&mdma->regs->dmacint, ch + off); out_8(&mdma->regs->dmacerr, ch + off); /* Check error status */ if (es & (1 << ch)) list_for_each_entry(mdesc, &mchan->active, node) mdesc->error = -EIO; /* Execute queued descriptors */ list_splice_tail_init(&mchan->active, &mchan->completed); if (!list_empty(&mchan->queued)) mpc_dma_execute(mchan); spin_unlock(&mchan->lock); } } /* Interrupt handler */ static irqreturn_t mpc_dma_irq(int irq, void *data) { struct mpc_dma *mdma = data; uint es; /* Save error status register */ es = in_be32(&mdma->regs->dmaes); spin_lock(&mdma->error_status_lock); if ((es & MPC_DMA_DMAES_VLD) && mdma->error_status == 0) mdma->error_status = es; spin_unlock(&mdma->error_status_lock); /* Handle interrupt on each channel */ if (mdma->dma.chancnt > 32) { mpc_dma_irq_process(mdma, in_be32(&mdma->regs->dmainth), in_be32(&mdma->regs->dmaerrh), 32); } mpc_dma_irq_process(mdma, in_be32(&mdma->regs->dmaintl), in_be32(&mdma->regs->dmaerrl), 0); /* Schedule tasklet */ tasklet_schedule(&mdma->tasklet); return IRQ_HANDLED; } /* process completed descriptors */ static void mpc_dma_process_completed(struct mpc_dma *mdma) { dma_cookie_t last_cookie = 0; struct mpc_dma_chan *mchan; struct mpc_dma_desc *mdesc; struct dma_async_tx_descriptor *desc; unsigned long flags; LIST_HEAD(list); int i; for (i = 0; i < mdma->dma.chancnt; i++) { mchan = &mdma->channels[i]; /* Get all completed descriptors */ spin_lock_irqsave(&mchan->lock, flags); if (!list_empty(&mchan->completed)) list_splice_tail_init(&mchan->completed, &list); spin_unlock_irqrestore(&mchan->lock, flags); if (list_empty(&list)) continue; /* Execute callbacks and run dependencies */ list_for_each_entry(mdesc, &list, node) { desc = &mdesc->desc; dmaengine_desc_get_callback_invoke(desc, NULL); last_cookie = desc->cookie; dma_run_dependencies(desc); } /* Free descriptors */ spin_lock_irqsave(&mchan->lock, flags); list_splice_tail_init(&list, &mchan->free); mchan->chan.completed_cookie = last_cookie; spin_unlock_irqrestore(&mchan->lock, flags); } } /* DMA Tasklet */ static void mpc_dma_tasklet(unsigned long data) { struct mpc_dma *mdma = (void *)data; unsigned long flags; uint es; spin_lock_irqsave(&mdma->error_status_lock, flags); es = mdma->error_status; mdma->error_status = 0; spin_unlock_irqrestore(&mdma->error_status_lock, flags); /* Print nice error report */ if (es) { dev_err(mdma->dma.dev, "Hardware reported following error(s) on channel %u:\n", MPC_DMA_DMAES_ERRCHN(es)); if (es & MPC_DMA_DMAES_GPE) dev_err(mdma->dma.dev, "- Group Priority Error\n"); if (es & MPC_DMA_DMAES_CPE) dev_err(mdma->dma.dev, "- Channel Priority Error\n"); if (es & MPC_DMA_DMAES_SAE) dev_err(mdma->dma.dev, "- Source Address Error\n"); if (es & MPC_DMA_DMAES_SOE) dev_err(mdma->dma.dev, "- Source Offset Configuration Error\n"); if (es & MPC_DMA_DMAES_DAE) dev_err(mdma->dma.dev, "- Destination Address Error\n"); if (es & MPC_DMA_DMAES_DOE) dev_err(mdma->dma.dev, "- Destination Offset Configuration Error\n"); if (es & MPC_DMA_DMAES_NCE) dev_err(mdma->dma.dev, "- NBytes/Citter Configuration Error\n"); if (es & MPC_DMA_DMAES_SGE) dev_err(mdma->dma.dev, "- Scatter/Gather Configuration Error\n"); if (es & MPC_DMA_DMAES_SBE) dev_err(mdma->dma.dev, "- Source Bus Error\n"); if (es & MPC_DMA_DMAES_DBE) dev_err(mdma->dma.dev, "- Destination Bus Error\n"); } mpc_dma_process_completed(mdma); } /* Submit descriptor to hardware */ static dma_cookie_t mpc_dma_tx_submit(struct dma_async_tx_descriptor *txd) { struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(txd->chan); struct mpc_dma_desc *mdesc; unsigned long flags; dma_cookie_t cookie; mdesc = container_of(txd, struct mpc_dma_desc, desc); spin_lock_irqsave(&mchan->lock, flags); /* Move descriptor to queue */ list_move_tail(&mdesc->node, &mchan->queued); /* If channel is idle, execute all queued descriptors */ if (list_empty(&mchan->active)) mpc_dma_execute(mchan); /* Update cookie */ cookie = dma_cookie_assign(txd); spin_unlock_irqrestore(&mchan->lock, flags); return cookie; } /* Alloc channel resources */ static int mpc_dma_alloc_chan_resources(struct dma_chan *chan) { struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan); struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan); struct mpc_dma_desc *mdesc; struct mpc_dma_tcd *tcd; dma_addr_t tcd_paddr; unsigned long flags; LIST_HEAD(descs); int i; /* Alloc DMA memory for Transfer Control Descriptors */ tcd = dma_alloc_coherent(mdma->dma.dev, MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd), &tcd_paddr, GFP_KERNEL); if (!tcd) return -ENOMEM; /* Alloc descriptors for this channel */ for (i = 0; i < MPC_DMA_DESCRIPTORS; i++) { mdesc = kzalloc(sizeof(struct mpc_dma_desc), GFP_KERNEL); if (!mdesc) { dev_notice(mdma->dma.dev, "Memory allocation error. Allocated only %u descriptors\n", i); break; } dma_async_tx_descriptor_init(&mdesc->desc, chan); mdesc->desc.flags = DMA_CTRL_ACK; mdesc->desc.tx_submit = mpc_dma_tx_submit; mdesc->tcd = &tcd[i]; mdesc->tcd_paddr = tcd_paddr + (i * sizeof(struct mpc_dma_tcd)); list_add_tail(&mdesc->node, &descs); } /* Return error only if no descriptors were allocated */ if (i == 0) { dma_free_coherent(mdma->dma.dev, MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd), tcd, tcd_paddr); return -ENOMEM; } spin_lock_irqsave(&mchan->lock, flags); mchan->tcd = tcd; mchan->tcd_paddr = tcd_paddr; list_splice_tail_init(&descs, &mchan->free); spin_unlock_irqrestore(&mchan->lock, flags); /* Enable Error Interrupt */ out_8(&mdma->regs->dmaseei, chan->chan_id); return 0; } /* Free channel resources */ static void mpc_dma_free_chan_resources(struct dma_chan *chan) { struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan); struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan); struct mpc_dma_desc *mdesc, *tmp; struct mpc_dma_tcd *tcd; dma_addr_t tcd_paddr; unsigned long flags; LIST_HEAD(descs); spin_lock_irqsave(&mchan->lock, flags); /* Channel must be idle */ BUG_ON(!list_empty(&mchan->prepared)); BUG_ON(!list_empty(&mchan->queued)); BUG_ON(!list_empty(&mchan->active)); BUG_ON(!list_empty(&mchan->completed)); /* Move data */ list_splice_tail_init(&mchan->free, &descs); tcd = mchan->tcd; tcd_paddr = mchan->tcd_paddr; spin_unlock_irqrestore(&mchan->lock, flags); /* Free DMA memory used by descriptors */ dma_free_coherent(mdma->dma.dev, MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd), tcd, tcd_paddr); /* Free descriptors */ list_for_each_entry_safe(mdesc, tmp, &descs, node) kfree(mdesc); /* Disable Error Interrupt */ out_8(&mdma->regs->dmaceei, chan->chan_id); } /* Send all pending descriptor to hardware */ static void mpc_dma_issue_pending(struct dma_chan *chan) { /* * We are posting descriptors to the hardware as soon as * they are ready, so this function does nothing. */ } /* Check request completion status */ static enum dma_status mpc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie, struct dma_tx_state *txstate) { return dma_cookie_status(chan, cookie, txstate); } /* Prepare descriptor for memory to memory copy */ static struct dma_async_tx_descriptor * mpc_dma_prep_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, size_t len, unsigned long flags) { struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan); struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan); struct mpc_dma_desc *mdesc = NULL; struct mpc_dma_tcd *tcd; unsigned long iflags; /* Get free descriptor */ spin_lock_irqsave(&mchan->lock, iflags); if (!list_empty(&mchan->free)) { mdesc = list_first_entry(&mchan->free, struct mpc_dma_desc, node); list_del(&mdesc->node); } spin_unlock_irqrestore(&mchan->lock, iflags); if (!mdesc) { /* try to free completed descriptors */ mpc_dma_process_completed(mdma); return NULL; } mdesc->error = 0; mdesc->will_access_peripheral = 0; tcd = mdesc->tcd; /* Prepare Transfer Control Descriptor for this transaction */ memset(tcd, 0, sizeof(struct mpc_dma_tcd)); if (IS_ALIGNED(src | dst | len, 32)) { tcd->ssize = MPC_DMA_TSIZE_32; tcd->dsize = MPC_DMA_TSIZE_32; tcd->soff = 32; tcd->doff = 32; } else if (!mdma->is_mpc8308 && IS_ALIGNED(src | dst | len, 16)) { /* MPC8308 doesn't support 16 byte transfers */ tcd->ssize = MPC_DMA_TSIZE_16; tcd->dsize = MPC_DMA_TSIZE_16; tcd->soff = 16; tcd->doff = 16; } else if (IS_ALIGNED(src | dst | len, 4)) { tcd->ssize = MPC_DMA_TSIZE_4; tcd->dsize = MPC_DMA_TSIZE_4; tcd->soff = 4; tcd->doff = 4; } else if (IS_ALIGNED(src | dst | len, 2)) { tcd->ssize = MPC_DMA_TSIZE_2; tcd->dsize = MPC_DMA_TSIZE_2; tcd->soff = 2; tcd->doff = 2; } else { tcd->ssize = MPC_DMA_TSIZE_1; tcd->dsize = MPC_DMA_TSIZE_1; tcd->soff = 1; tcd->doff = 1; } tcd->saddr = src; tcd->daddr = dst; tcd->nbytes = len; tcd->biter = 1; tcd->citer = 1; /* Place descriptor in prepared list */ spin_lock_irqsave(&mchan->lock, iflags); list_add_tail(&mdesc->node, &mchan->prepared); spin_unlock_irqrestore(&mchan->lock, iflags); return &mdesc->desc; } inline u8 buswidth_to_dmatsize(u8 buswidth) { u8 res; for (res = 0; buswidth > 1; buswidth /= 2) res++; return res; } static struct dma_async_tx_descriptor * mpc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction direction, unsigned long flags, void *context) { struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan); struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan); struct mpc_dma_desc *mdesc = NULL; dma_addr_t per_paddr; u32 tcd_nunits; struct mpc_dma_tcd *tcd; unsigned long iflags; struct scatterlist *sg; size_t len; int iter, i; /* Currently there is no proper support for scatter/gather */ if (sg_len != 1) return NULL; if (!is_slave_direction(direction)) return NULL; for_each_sg(sgl, sg, sg_len, i) { spin_lock_irqsave(&mchan->lock, iflags); mdesc = list_first_entry(&mchan->free, struct mpc_dma_desc, node); if (!mdesc) { spin_unlock_irqrestore(&mchan->lock, iflags); /* Try to free completed descriptors */ mpc_dma_process_completed(mdma); return NULL; } list_del(&mdesc->node); if (direction == DMA_DEV_TO_MEM) { per_paddr = mchan->src_per_paddr; tcd_nunits = mchan->src_tcd_nunits; } else { per_paddr = mchan->dst_per_paddr; tcd_nunits = mchan->dst_tcd_nunits; } spin_unlock_irqrestore(&mchan->lock, iflags); if (per_paddr == 0 || tcd_nunits == 0) goto err_prep; mdesc->error = 0; mdesc->will_access_peripheral = 1; /* Prepare Transfer Control Descriptor for this transaction */ tcd = mdesc->tcd; memset(tcd, 0, sizeof(struct mpc_dma_tcd)); if (direction == DMA_DEV_TO_MEM) { tcd->saddr = per_paddr; tcd->daddr = sg_dma_address(sg); if (!IS_ALIGNED(sg_dma_address(sg), mchan->dwidth)) goto err_prep; tcd->soff = 0; tcd->doff = mchan->dwidth; } else { tcd->saddr = sg_dma_address(sg); tcd->daddr = per_paddr; if (!IS_ALIGNED(sg_dma_address(sg), mchan->swidth)) goto err_prep; tcd->soff = mchan->swidth; tcd->doff = 0; } tcd->ssize = buswidth_to_dmatsize(mchan->swidth); tcd->dsize = buswidth_to_dmatsize(mchan->dwidth); if (mdma->is_mpc8308) { tcd->nbytes = sg_dma_len(sg); if (!IS_ALIGNED(tcd->nbytes, mchan->swidth)) goto err_prep; /* No major loops for MPC8303 */ tcd->biter = 1; tcd->citer = 1; } else { len = sg_dma_len(sg); tcd->nbytes = tcd_nunits * tcd->ssize; if (!IS_ALIGNED(len, tcd->nbytes)) goto err_prep; iter = len / tcd->nbytes; if (iter >= 1 << 15) { /* len is too big */ goto err_prep; } /* citer_linkch contains the high bits of iter */ tcd->biter = iter & 0x1ff; tcd->biter_linkch = iter >> 9; tcd->citer = tcd->biter; tcd->citer_linkch = tcd->biter_linkch; } tcd->e_sg = 0; tcd->d_req = 1; /* Place descriptor in prepared list */ spin_lock_irqsave(&mchan->lock, iflags); list_add_tail(&mdesc->node, &mchan->prepared); spin_unlock_irqrestore(&mchan->lock, iflags); } return &mdesc->desc; err_prep: /* Put the descriptor back */ spin_lock_irqsave(&mchan->lock, iflags); list_add_tail(&mdesc->node, &mchan->free); spin_unlock_irqrestore(&mchan->lock, iflags); return NULL; } inline bool is_buswidth_valid(u8 buswidth, bool is_mpc8308) { switch (buswidth) { case 16: if (is_mpc8308) return false; case 1: case 2: case 4: case 32: break; default: return false; } return true; } static int mpc_dma_device_config(struct dma_chan *chan, struct dma_slave_config *cfg) { struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan); struct mpc_dma *mdma = dma_chan_to_mpc_dma(&mchan->chan); unsigned long flags; /* * Software constraints: * - only transfers between a peripheral device and memory are * supported * - transfer chunk sizes of 1, 2, 4, 16 (for MPC512x), and 32 bytes * are supported, and, consequently, source addresses and * destination addresses; must be aligned accordingly; furthermore, * for MPC512x SoCs, the transfer size must be aligned on (chunk * size * maxburst) * - during the transfer, the RAM address is incremented by the size * of transfer chunk * - the peripheral port's address is constant during the transfer. */ if (!IS_ALIGNED(cfg->src_addr, cfg->src_addr_width) || !IS_ALIGNED(cfg->dst_addr, cfg->dst_addr_width)) { return -EINVAL; } if (!is_buswidth_valid(cfg->src_addr_width, mdma->is_mpc8308) || !is_buswidth_valid(cfg->dst_addr_width, mdma->is_mpc8308)) return -EINVAL; spin_lock_irqsave(&mchan->lock, flags); mchan->src_per_paddr = cfg->src_addr; mchan->src_tcd_nunits = cfg->src_maxburst; mchan->swidth = cfg->src_addr_width; mchan->dst_per_paddr = cfg->dst_addr; mchan->dst_tcd_nunits = cfg->dst_maxburst; mchan->dwidth = cfg->dst_addr_width; /* Apply defaults */ if (mchan->src_tcd_nunits == 0) mchan->src_tcd_nunits = 1; if (mchan->dst_tcd_nunits == 0) mchan->dst_tcd_nunits = 1; spin_unlock_irqrestore(&mchan->lock, flags); return 0; } static int mpc_dma_device_terminate_all(struct dma_chan *chan) { struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan); struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan); unsigned long flags; /* Disable channel requests */ spin_lock_irqsave(&mchan->lock, flags); out_8(&mdma->regs->dmacerq, chan->chan_id); list_splice_tail_init(&mchan->prepared, &mchan->free); list_splice_tail_init(&mchan->queued, &mchan->free); list_splice_tail_init(&mchan->active, &mchan->free); spin_unlock_irqrestore(&mchan->lock, flags); return 0; } static int mpc_dma_probe(struct platform_device *op) { struct device_node *dn = op->dev.of_node; struct device *dev = &op->dev; struct dma_device *dma; struct mpc_dma *mdma; struct mpc_dma_chan *mchan; struct resource res; ulong regs_start, regs_size; int retval, i; u8 chancnt; mdma = devm_kzalloc(dev, sizeof(struct mpc_dma), GFP_KERNEL); if (!mdma) { retval = -ENOMEM; goto err; } mdma->irq = irq_of_parse_and_map(dn, 0); if (!mdma->irq) { dev_err(dev, "Error mapping IRQ!\n"); retval = -EINVAL; goto err; } if (of_device_is_compatible(dn, "fsl,mpc8308-dma")) { mdma->is_mpc8308 = 1; mdma->irq2 = irq_of_parse_and_map(dn, 1); if (!mdma->irq2) { dev_err(dev, "Error mapping IRQ!\n"); retval = -EINVAL; goto err_dispose1; } } retval = of_address_to_resource(dn, 0, &res); if (retval) { dev_err(dev, "Error parsing memory region!\n"); goto err_dispose2; } regs_start = res.start; regs_size = resource_size(&res); if (!devm_request_mem_region(dev, regs_start, regs_size, DRV_NAME)) { dev_err(dev, "Error requesting memory region!\n"); retval = -EBUSY; goto err_dispose2; } mdma->regs = devm_ioremap(dev, regs_start, regs_size); if (!mdma->regs) { dev_err(dev, "Error mapping memory region!\n"); retval = -ENOMEM; goto err_dispose2; } mdma->tcd = (struct mpc_dma_tcd *)((u8 *)(mdma->regs) + MPC_DMA_TCD_OFFSET); retval = request_irq(mdma->irq, &mpc_dma_irq, 0, DRV_NAME, mdma); if (retval) { dev_err(dev, "Error requesting IRQ!\n"); retval = -EINVAL; goto err_dispose2; } if (mdma->is_mpc8308) { retval = request_irq(mdma->irq2, &mpc_dma_irq, 0, DRV_NAME, mdma); if (retval) { dev_err(dev, "Error requesting IRQ2!\n"); retval = -EINVAL; goto err_free1; } } spin_lock_init(&mdma->error_status_lock); dma = &mdma->dma; dma->dev = dev; dma->device_alloc_chan_resources = mpc_dma_alloc_chan_resources; dma->device_free_chan_resources = mpc_dma_free_chan_resources; dma->device_issue_pending = mpc_dma_issue_pending; dma->device_tx_status = mpc_dma_tx_status; dma->device_prep_dma_memcpy = mpc_dma_prep_memcpy; dma->device_prep_slave_sg = mpc_dma_prep_slave_sg; dma->device_config = mpc_dma_device_config; dma->device_terminate_all = mpc_dma_device_terminate_all; INIT_LIST_HEAD(&dma->channels); dma_cap_set(DMA_MEMCPY, dma->cap_mask); dma_cap_set(DMA_SLAVE, dma->cap_mask); if (mdma->is_mpc8308) chancnt = MPC8308_DMACHAN_MAX; else chancnt = MPC512x_DMACHAN_MAX; for (i = 0; i < chancnt; i++) { mchan = &mdma->channels[i]; mchan->chan.device = dma; dma_cookie_init(&mchan->chan); INIT_LIST_HEAD(&mchan->free); INIT_LIST_HEAD(&mchan->prepared); INIT_LIST_HEAD(&mchan->queued); INIT_LIST_HEAD(&mchan->active); INIT_LIST_HEAD(&mchan->completed); spin_lock_init(&mchan->lock); list_add_tail(&mchan->chan.device_node, &dma->channels); } tasklet_init(&mdma->tasklet, mpc_dma_tasklet, (unsigned long)mdma); /* * Configure DMA Engine: * - Dynamic clock, * - Round-robin group arbitration, * - Round-robin channel arbitration. */ if (mdma->is_mpc8308) { /* MPC8308 has 16 channels and lacks some registers */ out_be32(&mdma->regs->dmacr, MPC_DMA_DMACR_ERCA); /* enable snooping */ out_be32(&mdma->regs->dmagpor, MPC_DMA_DMAGPOR_SNOOP_ENABLE); /* Disable error interrupts */ out_be32(&mdma->regs->dmaeeil, 0); /* Clear interrupts status */ out_be32(&mdma->regs->dmaintl, 0xFFFF); out_be32(&mdma->regs->dmaerrl, 0xFFFF); } else { out_be32(&mdma->regs->dmacr, MPC_DMA_DMACR_EDCG | MPC_DMA_DMACR_ERGA | MPC_DMA_DMACR_ERCA); /* Disable hardware DMA requests */ out_be32(&mdma->regs->dmaerqh, 0); out_be32(&mdma->regs->dmaerql, 0); /* Disable error interrupts */ out_be32(&mdma->regs->dmaeeih, 0); out_be32(&mdma->regs->dmaeeil, 0); /* Clear interrupts status */ out_be32(&mdma->regs->dmainth, 0xFFFFFFFF); out_be32(&mdma->regs->dmaintl, 0xFFFFFFFF); out_be32(&mdma->regs->dmaerrh, 0xFFFFFFFF); out_be32(&mdma->regs->dmaerrl, 0xFFFFFFFF); /* Route interrupts to IPIC */ out_be32(&mdma->regs->dmaihsa, 0); out_be32(&mdma->regs->dmailsa, 0); } /* Register DMA engine */ dev_set_drvdata(dev, mdma); retval = dma_async_device_register(dma); if (retval) goto err_free2; /* Register with OF helpers for DMA lookups (nonfatal) */ if (dev->of_node) { retval = of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id, mdma); if (retval) dev_warn(dev, "Could not register for OF lookup\n"); } return 0; err_free2: if (mdma->is_mpc8308) free_irq(mdma->irq2, mdma); err_free1: free_irq(mdma->irq, mdma); err_dispose2: if (mdma->is_mpc8308) irq_dispose_mapping(mdma->irq2); err_dispose1: irq_dispose_mapping(mdma->irq); err: return retval; } static int mpc_dma_remove(struct platform_device *op) { struct device *dev = &op->dev; struct mpc_dma *mdma = dev_get_drvdata(dev); if (dev->of_node) of_dma_controller_free(dev->of_node); dma_async_device_unregister(&mdma->dma); if (mdma->is_mpc8308) { free_irq(mdma->irq2, mdma); irq_dispose_mapping(mdma->irq2); } free_irq(mdma->irq, mdma); irq_dispose_mapping(mdma->irq); tasklet_kill(&mdma->tasklet); return 0; } static const struct of_device_id mpc_dma_match[] = { { .compatible = "fsl,mpc5121-dma", }, { .compatible = "fsl,mpc8308-dma", }, {}, }; MODULE_DEVICE_TABLE(of, mpc_dma_match); static struct platform_driver mpc_dma_driver = { .probe = mpc_dma_probe, .remove = mpc_dma_remove, .driver = { .name = DRV_NAME, .of_match_table = mpc_dma_match, }, }; module_platform_driver(mpc_dma_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Piotr Ziecik <kosmo@semihalf.com>");