162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci#include <linux/kernel.h> 362306a36Sopenharmony_ci#include <linux/errno.h> 462306a36Sopenharmony_ci#include <linux/err.h> 562306a36Sopenharmony_ci#include <linux/mm.h> 662306a36Sopenharmony_ci#include <linux/slab.h> 762306a36Sopenharmony_ci#include <linux/vmalloc.h> 862306a36Sopenharmony_ci#include <linux/pagemap.h> 962306a36Sopenharmony_ci#include <linux/sched.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <media/frame_vector.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci/** 1462306a36Sopenharmony_ci * get_vaddr_frames() - map virtual addresses to pfns 1562306a36Sopenharmony_ci * @start: starting user address 1662306a36Sopenharmony_ci * @nr_frames: number of pages / pfns from start to map 1762306a36Sopenharmony_ci * @write: the mapped address has write permission 1862306a36Sopenharmony_ci * @vec: structure which receives pages / pfns of the addresses mapped. 1962306a36Sopenharmony_ci * It should have space for at least nr_frames entries. 2062306a36Sopenharmony_ci * 2162306a36Sopenharmony_ci * This function maps virtual addresses from @start and fills @vec structure 2262306a36Sopenharmony_ci * with page frame numbers or page pointers to corresponding pages (choice 2362306a36Sopenharmony_ci * depends on the type of the vma underlying the virtual address). If @start 2462306a36Sopenharmony_ci * belongs to a normal vma, the function grabs reference to each of the pages 2562306a36Sopenharmony_ci * to pin them in memory. If @start belongs to VM_IO | VM_PFNMAP vma, we don't 2662306a36Sopenharmony_ci * touch page structures and the caller must make sure pfns aren't reused for 2762306a36Sopenharmony_ci * anything else while he is using them. 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * The function returns number of pages mapped which may be less than 3062306a36Sopenharmony_ci * @nr_frames. In particular we stop mapping if there are more vmas of 3162306a36Sopenharmony_ci * different type underlying the specified range of virtual addresses. 3262306a36Sopenharmony_ci * When the function isn't able to map a single page, it returns error. 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * Note that get_vaddr_frames() cannot follow VM_IO mappings. It used 3562306a36Sopenharmony_ci * to be able to do that, but that could (racily) return non-refcounted 3662306a36Sopenharmony_ci * pfns. 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * This function takes care of grabbing mmap_lock as necessary. 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_ciint get_vaddr_frames(unsigned long start, unsigned int nr_frames, bool write, 4162306a36Sopenharmony_ci struct frame_vector *vec) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci int ret; 4462306a36Sopenharmony_ci unsigned int gup_flags = FOLL_LONGTERM; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci if (nr_frames == 0) 4762306a36Sopenharmony_ci return 0; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci if (WARN_ON_ONCE(nr_frames > vec->nr_allocated)) 5062306a36Sopenharmony_ci nr_frames = vec->nr_allocated; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci start = untagged_addr(start); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci if (write) 5562306a36Sopenharmony_ci gup_flags |= FOLL_WRITE; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci ret = pin_user_pages_fast(start, nr_frames, gup_flags, 5862306a36Sopenharmony_ci (struct page **)(vec->ptrs)); 5962306a36Sopenharmony_ci vec->got_ref = true; 6062306a36Sopenharmony_ci vec->is_pfns = false; 6162306a36Sopenharmony_ci vec->nr_frames = ret; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (likely(ret > 0)) 6462306a36Sopenharmony_ci return ret; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci vec->nr_frames = 0; 6762306a36Sopenharmony_ci return ret ? ret : -EFAULT; 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ciEXPORT_SYMBOL(get_vaddr_frames); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci/** 7262306a36Sopenharmony_ci * put_vaddr_frames() - drop references to pages if get_vaddr_frames() acquired 7362306a36Sopenharmony_ci * them 7462306a36Sopenharmony_ci * @vec: frame vector to put 7562306a36Sopenharmony_ci * 7662306a36Sopenharmony_ci * Drop references to pages if get_vaddr_frames() acquired them. We also 7762306a36Sopenharmony_ci * invalidate the frame vector so that it is prepared for the next call into 7862306a36Sopenharmony_ci * get_vaddr_frames(). 7962306a36Sopenharmony_ci */ 8062306a36Sopenharmony_civoid put_vaddr_frames(struct frame_vector *vec) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci struct page **pages; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if (!vec->got_ref) 8562306a36Sopenharmony_ci goto out; 8662306a36Sopenharmony_ci pages = frame_vector_pages(vec); 8762306a36Sopenharmony_ci /* 8862306a36Sopenharmony_ci * frame_vector_pages() might needed to do a conversion when 8962306a36Sopenharmony_ci * get_vaddr_frames() got pages but vec was later converted to pfns. 9062306a36Sopenharmony_ci * But it shouldn't really fail to convert pfns back... 9162306a36Sopenharmony_ci */ 9262306a36Sopenharmony_ci if (WARN_ON(IS_ERR(pages))) 9362306a36Sopenharmony_ci goto out; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci unpin_user_pages(pages, vec->nr_frames); 9662306a36Sopenharmony_ci vec->got_ref = false; 9762306a36Sopenharmony_ciout: 9862306a36Sopenharmony_ci vec->nr_frames = 0; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ciEXPORT_SYMBOL(put_vaddr_frames); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci/** 10362306a36Sopenharmony_ci * frame_vector_to_pages - convert frame vector to contain page pointers 10462306a36Sopenharmony_ci * @vec: frame vector to convert 10562306a36Sopenharmony_ci * 10662306a36Sopenharmony_ci * Convert @vec to contain array of page pointers. If the conversion is 10762306a36Sopenharmony_ci * successful, return 0. Otherwise return an error. Note that we do not grab 10862306a36Sopenharmony_ci * page references for the page structures. 10962306a36Sopenharmony_ci */ 11062306a36Sopenharmony_ciint frame_vector_to_pages(struct frame_vector *vec) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci int i; 11362306a36Sopenharmony_ci unsigned long *nums; 11462306a36Sopenharmony_ci struct page **pages; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (!vec->is_pfns) 11762306a36Sopenharmony_ci return 0; 11862306a36Sopenharmony_ci nums = frame_vector_pfns(vec); 11962306a36Sopenharmony_ci for (i = 0; i < vec->nr_frames; i++) 12062306a36Sopenharmony_ci if (!pfn_valid(nums[i])) 12162306a36Sopenharmony_ci return -EINVAL; 12262306a36Sopenharmony_ci pages = (struct page **)nums; 12362306a36Sopenharmony_ci for (i = 0; i < vec->nr_frames; i++) 12462306a36Sopenharmony_ci pages[i] = pfn_to_page(nums[i]); 12562306a36Sopenharmony_ci vec->is_pfns = false; 12662306a36Sopenharmony_ci return 0; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ciEXPORT_SYMBOL(frame_vector_to_pages); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci/** 13162306a36Sopenharmony_ci * frame_vector_to_pfns - convert frame vector to contain pfns 13262306a36Sopenharmony_ci * @vec: frame vector to convert 13362306a36Sopenharmony_ci * 13462306a36Sopenharmony_ci * Convert @vec to contain array of pfns. 13562306a36Sopenharmony_ci */ 13662306a36Sopenharmony_civoid frame_vector_to_pfns(struct frame_vector *vec) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci int i; 13962306a36Sopenharmony_ci unsigned long *nums; 14062306a36Sopenharmony_ci struct page **pages; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (vec->is_pfns) 14362306a36Sopenharmony_ci return; 14462306a36Sopenharmony_ci pages = (struct page **)(vec->ptrs); 14562306a36Sopenharmony_ci nums = (unsigned long *)pages; 14662306a36Sopenharmony_ci for (i = 0; i < vec->nr_frames; i++) 14762306a36Sopenharmony_ci nums[i] = page_to_pfn(pages[i]); 14862306a36Sopenharmony_ci vec->is_pfns = true; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ciEXPORT_SYMBOL(frame_vector_to_pfns); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci/** 15362306a36Sopenharmony_ci * frame_vector_create() - allocate & initialize structure for pinned pfns 15462306a36Sopenharmony_ci * @nr_frames: number of pfns slots we should reserve 15562306a36Sopenharmony_ci * 15662306a36Sopenharmony_ci * Allocate and initialize struct pinned_pfns to be able to hold @nr_pfns 15762306a36Sopenharmony_ci * pfns. 15862306a36Sopenharmony_ci */ 15962306a36Sopenharmony_cistruct frame_vector *frame_vector_create(unsigned int nr_frames) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct frame_vector *vec; 16262306a36Sopenharmony_ci int size = sizeof(struct frame_vector) + sizeof(void *) * nr_frames; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci if (WARN_ON_ONCE(nr_frames == 0)) 16562306a36Sopenharmony_ci return NULL; 16662306a36Sopenharmony_ci /* 16762306a36Sopenharmony_ci * This is absurdly high. It's here just to avoid strange effects when 16862306a36Sopenharmony_ci * arithmetics overflows. 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_ci if (WARN_ON_ONCE(nr_frames > INT_MAX / sizeof(void *) / 2)) 17162306a36Sopenharmony_ci return NULL; 17262306a36Sopenharmony_ci /* 17362306a36Sopenharmony_ci * Avoid higher order allocations, use vmalloc instead. It should 17462306a36Sopenharmony_ci * be rare anyway. 17562306a36Sopenharmony_ci */ 17662306a36Sopenharmony_ci vec = kvmalloc(size, GFP_KERNEL); 17762306a36Sopenharmony_ci if (!vec) 17862306a36Sopenharmony_ci return NULL; 17962306a36Sopenharmony_ci vec->nr_allocated = nr_frames; 18062306a36Sopenharmony_ci vec->nr_frames = 0; 18162306a36Sopenharmony_ci return vec; 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ciEXPORT_SYMBOL(frame_vector_create); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci/** 18662306a36Sopenharmony_ci * frame_vector_destroy() - free memory allocated to carry frame vector 18762306a36Sopenharmony_ci * @vec: Frame vector to free 18862306a36Sopenharmony_ci * 18962306a36Sopenharmony_ci * Free structure allocated by frame_vector_create() to carry frames. 19062306a36Sopenharmony_ci */ 19162306a36Sopenharmony_civoid frame_vector_destroy(struct frame_vector *vec) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci /* Make sure put_vaddr_frames() got called properly... */ 19462306a36Sopenharmony_ci VM_BUG_ON(vec->nr_frames > 0); 19562306a36Sopenharmony_ci kvfree(vec); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ciEXPORT_SYMBOL(frame_vector_destroy); 198