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