18c2ecf20Sopenharmony_ci#include <linux/kernel.h>
28c2ecf20Sopenharmony_ci#include <linux/mm.h>
38c2ecf20Sopenharmony_ci#include <linux/slab.h>
48c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
58c2ecf20Sopenharmony_ci#include <linux/ktime.h>
68c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#define GUP_FAST_BENCHMARK	_IOWR('g', 1, struct gup_benchmark)
98c2ecf20Sopenharmony_ci#define GUP_BENCHMARK		_IOWR('g', 2, struct gup_benchmark)
108c2ecf20Sopenharmony_ci#define PIN_FAST_BENCHMARK	_IOWR('g', 3, struct gup_benchmark)
118c2ecf20Sopenharmony_ci#define PIN_BENCHMARK		_IOWR('g', 4, struct gup_benchmark)
128c2ecf20Sopenharmony_ci#define PIN_LONGTERM_BENCHMARK	_IOWR('g', 5, struct gup_benchmark)
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cistruct gup_benchmark {
158c2ecf20Sopenharmony_ci	__u64 get_delta_usec;
168c2ecf20Sopenharmony_ci	__u64 put_delta_usec;
178c2ecf20Sopenharmony_ci	__u64 addr;
188c2ecf20Sopenharmony_ci	__u64 size;
198c2ecf20Sopenharmony_ci	__u32 nr_pages_per_call;
208c2ecf20Sopenharmony_ci	__u32 flags;
218c2ecf20Sopenharmony_ci	__u64 expansion[10];	/* For future use */
228c2ecf20Sopenharmony_ci};
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistatic void put_back_pages(unsigned int cmd, struct page **pages,
258c2ecf20Sopenharmony_ci			   unsigned long nr_pages)
268c2ecf20Sopenharmony_ci{
278c2ecf20Sopenharmony_ci	unsigned long i;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	switch (cmd) {
308c2ecf20Sopenharmony_ci	case GUP_FAST_BENCHMARK:
318c2ecf20Sopenharmony_ci	case GUP_BENCHMARK:
328c2ecf20Sopenharmony_ci		for (i = 0; i < nr_pages; i++)
338c2ecf20Sopenharmony_ci			put_page(pages[i]);
348c2ecf20Sopenharmony_ci		break;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	case PIN_FAST_BENCHMARK:
378c2ecf20Sopenharmony_ci	case PIN_BENCHMARK:
388c2ecf20Sopenharmony_ci	case PIN_LONGTERM_BENCHMARK:
398c2ecf20Sopenharmony_ci		unpin_user_pages(pages, nr_pages);
408c2ecf20Sopenharmony_ci		break;
418c2ecf20Sopenharmony_ci	}
428c2ecf20Sopenharmony_ci}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic void verify_dma_pinned(unsigned int cmd, struct page **pages,
458c2ecf20Sopenharmony_ci			      unsigned long nr_pages)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	unsigned long i;
488c2ecf20Sopenharmony_ci	struct page *page;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	switch (cmd) {
518c2ecf20Sopenharmony_ci	case PIN_FAST_BENCHMARK:
528c2ecf20Sopenharmony_ci	case PIN_BENCHMARK:
538c2ecf20Sopenharmony_ci	case PIN_LONGTERM_BENCHMARK:
548c2ecf20Sopenharmony_ci		for (i = 0; i < nr_pages; i++) {
558c2ecf20Sopenharmony_ci			page = pages[i];
568c2ecf20Sopenharmony_ci			if (WARN(!page_maybe_dma_pinned(page),
578c2ecf20Sopenharmony_ci				 "pages[%lu] is NOT dma-pinned\n", i)) {
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci				dump_page(page, "gup_benchmark failure");
608c2ecf20Sopenharmony_ci				break;
618c2ecf20Sopenharmony_ci			}
628c2ecf20Sopenharmony_ci		}
638c2ecf20Sopenharmony_ci		break;
648c2ecf20Sopenharmony_ci	}
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic int __gup_benchmark_ioctl(unsigned int cmd,
688c2ecf20Sopenharmony_ci		struct gup_benchmark *gup)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	ktime_t start_time, end_time;
718c2ecf20Sopenharmony_ci	unsigned long i, nr_pages, addr, next;
728c2ecf20Sopenharmony_ci	int nr;
738c2ecf20Sopenharmony_ci	struct page **pages;
748c2ecf20Sopenharmony_ci	int ret = 0;
758c2ecf20Sopenharmony_ci	bool needs_mmap_lock =
768c2ecf20Sopenharmony_ci		cmd != GUP_FAST_BENCHMARK && cmd != PIN_FAST_BENCHMARK;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	if (gup->size > ULONG_MAX)
798c2ecf20Sopenharmony_ci		return -EINVAL;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	nr_pages = gup->size / PAGE_SIZE;
828c2ecf20Sopenharmony_ci	pages = kvcalloc(nr_pages, sizeof(void *), GFP_KERNEL);
838c2ecf20Sopenharmony_ci	if (!pages)
848c2ecf20Sopenharmony_ci		return -ENOMEM;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	if (needs_mmap_lock && mmap_read_lock_killable(current->mm)) {
878c2ecf20Sopenharmony_ci		ret = -EINTR;
888c2ecf20Sopenharmony_ci		goto free_pages;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	i = 0;
928c2ecf20Sopenharmony_ci	nr = gup->nr_pages_per_call;
938c2ecf20Sopenharmony_ci	start_time = ktime_get();
948c2ecf20Sopenharmony_ci	for (addr = gup->addr; addr < gup->addr + gup->size; addr = next) {
958c2ecf20Sopenharmony_ci		if (nr != gup->nr_pages_per_call)
968c2ecf20Sopenharmony_ci			break;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci		next = addr + nr * PAGE_SIZE;
998c2ecf20Sopenharmony_ci		if (next > gup->addr + gup->size) {
1008c2ecf20Sopenharmony_ci			next = gup->addr + gup->size;
1018c2ecf20Sopenharmony_ci			nr = (next - addr) / PAGE_SIZE;
1028c2ecf20Sopenharmony_ci		}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci		/* Filter out most gup flags: only allow a tiny subset here: */
1058c2ecf20Sopenharmony_ci		gup->flags &= FOLL_WRITE;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci		switch (cmd) {
1088c2ecf20Sopenharmony_ci		case GUP_FAST_BENCHMARK:
1098c2ecf20Sopenharmony_ci			nr = get_user_pages_fast(addr, nr, gup->flags,
1108c2ecf20Sopenharmony_ci						 pages + i);
1118c2ecf20Sopenharmony_ci			break;
1128c2ecf20Sopenharmony_ci		case GUP_BENCHMARK:
1138c2ecf20Sopenharmony_ci			nr = get_user_pages(addr, nr, gup->flags, pages + i,
1148c2ecf20Sopenharmony_ci					    NULL);
1158c2ecf20Sopenharmony_ci			break;
1168c2ecf20Sopenharmony_ci		case PIN_FAST_BENCHMARK:
1178c2ecf20Sopenharmony_ci			nr = pin_user_pages_fast(addr, nr, gup->flags,
1188c2ecf20Sopenharmony_ci						 pages + i);
1198c2ecf20Sopenharmony_ci			break;
1208c2ecf20Sopenharmony_ci		case PIN_BENCHMARK:
1218c2ecf20Sopenharmony_ci			nr = pin_user_pages(addr, nr, gup->flags, pages + i,
1228c2ecf20Sopenharmony_ci					    NULL);
1238c2ecf20Sopenharmony_ci			break;
1248c2ecf20Sopenharmony_ci		case PIN_LONGTERM_BENCHMARK:
1258c2ecf20Sopenharmony_ci			nr = pin_user_pages(addr, nr,
1268c2ecf20Sopenharmony_ci					    gup->flags | FOLL_LONGTERM,
1278c2ecf20Sopenharmony_ci					    pages + i, NULL);
1288c2ecf20Sopenharmony_ci			break;
1298c2ecf20Sopenharmony_ci		default:
1308c2ecf20Sopenharmony_ci			ret = -EINVAL;
1318c2ecf20Sopenharmony_ci			goto unlock;
1328c2ecf20Sopenharmony_ci		}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci		if (nr <= 0)
1358c2ecf20Sopenharmony_ci			break;
1368c2ecf20Sopenharmony_ci		i += nr;
1378c2ecf20Sopenharmony_ci	}
1388c2ecf20Sopenharmony_ci	end_time = ktime_get();
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	/* Shifting the meaning of nr_pages: now it is actual number pinned: */
1418c2ecf20Sopenharmony_ci	nr_pages = i;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	gup->get_delta_usec = ktime_us_delta(end_time, start_time);
1448c2ecf20Sopenharmony_ci	gup->size = addr - gup->addr;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	/*
1478c2ecf20Sopenharmony_ci	 * Take an un-benchmark-timed moment to verify DMA pinned
1488c2ecf20Sopenharmony_ci	 * state: print a warning if any non-dma-pinned pages are found:
1498c2ecf20Sopenharmony_ci	 */
1508c2ecf20Sopenharmony_ci	verify_dma_pinned(cmd, pages, nr_pages);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	start_time = ktime_get();
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	put_back_pages(cmd, pages, nr_pages);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	end_time = ktime_get();
1578c2ecf20Sopenharmony_ci	gup->put_delta_usec = ktime_us_delta(end_time, start_time);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ciunlock:
1608c2ecf20Sopenharmony_ci	if (needs_mmap_lock)
1618c2ecf20Sopenharmony_ci		mmap_read_unlock(current->mm);
1628c2ecf20Sopenharmony_cifree_pages:
1638c2ecf20Sopenharmony_ci	kvfree(pages);
1648c2ecf20Sopenharmony_ci	return ret;
1658c2ecf20Sopenharmony_ci}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_cistatic long gup_benchmark_ioctl(struct file *filep, unsigned int cmd,
1688c2ecf20Sopenharmony_ci		unsigned long arg)
1698c2ecf20Sopenharmony_ci{
1708c2ecf20Sopenharmony_ci	struct gup_benchmark gup;
1718c2ecf20Sopenharmony_ci	int ret;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	switch (cmd) {
1748c2ecf20Sopenharmony_ci	case GUP_FAST_BENCHMARK:
1758c2ecf20Sopenharmony_ci	case GUP_BENCHMARK:
1768c2ecf20Sopenharmony_ci	case PIN_FAST_BENCHMARK:
1778c2ecf20Sopenharmony_ci	case PIN_BENCHMARK:
1788c2ecf20Sopenharmony_ci	case PIN_LONGTERM_BENCHMARK:
1798c2ecf20Sopenharmony_ci		break;
1808c2ecf20Sopenharmony_ci	default:
1818c2ecf20Sopenharmony_ci		return -EINVAL;
1828c2ecf20Sopenharmony_ci	}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	if (copy_from_user(&gup, (void __user *)arg, sizeof(gup)))
1858c2ecf20Sopenharmony_ci		return -EFAULT;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	ret = __gup_benchmark_ioctl(cmd, &gup);
1888c2ecf20Sopenharmony_ci	if (ret)
1898c2ecf20Sopenharmony_ci		return ret;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	if (copy_to_user((void __user *)arg, &gup, sizeof(gup)))
1928c2ecf20Sopenharmony_ci		return -EFAULT;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	return 0;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic const struct file_operations gup_benchmark_fops = {
1988c2ecf20Sopenharmony_ci	.open = nonseekable_open,
1998c2ecf20Sopenharmony_ci	.unlocked_ioctl = gup_benchmark_ioctl,
2008c2ecf20Sopenharmony_ci};
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_cistatic int gup_benchmark_init(void)
2038c2ecf20Sopenharmony_ci{
2048c2ecf20Sopenharmony_ci	debugfs_create_file_unsafe("gup_benchmark", 0600, NULL, NULL,
2058c2ecf20Sopenharmony_ci				   &gup_benchmark_fops);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	return 0;
2088c2ecf20Sopenharmony_ci}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cilate_initcall(gup_benchmark_init);
211