|
48 | 48 | #include "drm_internal.h"
|
49 | 49 | #include "drm_legacy.h"
|
50 | 50 |
|
| 51 | +#if defined(CONFIG_MMU) && defined(CONFIG_TRANSPARENT_HUGEPAGE) |
| 52 | +#include <uapi/asm/mman.h> |
| 53 | +#include <drm/drm_vma_manager.h> |
| 54 | +#endif |
| 55 | + |
51 | 56 | /* from BKL pushdown */
|
52 | 57 | DEFINE_MUTEX(drm_global_mutex);
|
53 | 58 |
|
@@ -872,3 +877,138 @@ struct file *mock_drm_getfile(struct drm_minor *minor, unsigned int flags)
|
872 | 877 | return file;
|
873 | 878 | }
|
874 | 879 | EXPORT_SYMBOL_FOR_TESTS_ONLY(mock_drm_getfile);
|
| 880 | + |
| 881 | +#ifdef CONFIG_MMU |
| 882 | +#ifdef CONFIG_TRANSPARENT_HUGEPAGE |
| 883 | +/* |
| 884 | + * drm_addr_inflate() attempts to construct an aligned area by inflating |
| 885 | + * the area size and skipping the unaligned start of the area. |
| 886 | + * adapted from shmem_get_unmapped_area() |
| 887 | + */ |
| 888 | +static unsigned long drm_addr_inflate(unsigned long addr, |
| 889 | + unsigned long len, |
| 890 | + unsigned long pgoff, |
| 891 | + unsigned long flags, |
| 892 | + unsigned long huge_size) |
| 893 | +{ |
| 894 | + unsigned long offset, inflated_len; |
| 895 | + unsigned long inflated_addr; |
| 896 | + unsigned long inflated_offset; |
| 897 | + |
| 898 | + offset = (pgoff << PAGE_SHIFT) & (huge_size - 1); |
| 899 | + if (offset && offset + len < 2 * huge_size) |
| 900 | + return addr; |
| 901 | + if ((addr & (huge_size - 1)) == offset) |
| 902 | + return addr; |
| 903 | + |
| 904 | + inflated_len = len + huge_size - PAGE_SIZE; |
| 905 | + if (inflated_len > TASK_SIZE) |
| 906 | + return addr; |
| 907 | + if (inflated_len < len) |
| 908 | + return addr; |
| 909 | + |
| 910 | + inflated_addr = current->mm->get_unmapped_area(NULL, 0, inflated_len, |
| 911 | + 0, flags); |
| 912 | + if (IS_ERR_VALUE(inflated_addr)) |
| 913 | + return addr; |
| 914 | + if (inflated_addr & ~PAGE_MASK) |
| 915 | + return addr; |
| 916 | + |
| 917 | + inflated_offset = inflated_addr & (huge_size - 1); |
| 918 | + inflated_addr += offset - inflated_offset; |
| 919 | + if (inflated_offset > offset) |
| 920 | + inflated_addr += huge_size; |
| 921 | + |
| 922 | + if (inflated_addr > TASK_SIZE - len) |
| 923 | + return addr; |
| 924 | + |
| 925 | + return inflated_addr; |
| 926 | +} |
| 927 | + |
| 928 | +/** |
| 929 | + * drm_get_unmapped_area() - Get an unused user-space virtual memory area |
| 930 | + * suitable for huge page table entries. |
| 931 | + * @file: The struct file representing the address space being mmap()'d. |
| 932 | + * @uaddr: Start address suggested by user-space. |
| 933 | + * @len: Length of the area. |
| 934 | + * @pgoff: The page offset into the address space. |
| 935 | + * @flags: mmap flags |
| 936 | + * @mgr: The address space manager used by the drm driver. This argument can |
| 937 | + * probably be removed at some point when all drivers use the same |
| 938 | + * address space manager. |
| 939 | + * |
| 940 | + * This function attempts to find an unused user-space virtual memory area |
| 941 | + * that can accommodate the size we want to map, and that is properly |
| 942 | + * aligned to facilitate huge page table entries matching actual |
| 943 | + * huge pages or huge page aligned memory in buffer objects. Buffer objects |
| 944 | + * are assumed to start at huge page boundary pfns (io memory) or be |
| 945 | + * populated by huge pages aligned to the start of the buffer object |
| 946 | + * (system- or coherent memory). Adapted from shmem_get_unmapped_area. |
| 947 | + * |
| 948 | + * Return: aligned user-space address. |
| 949 | + */ |
| 950 | +unsigned long drm_get_unmapped_area(struct file *file, |
| 951 | + unsigned long uaddr, unsigned long len, |
| 952 | + unsigned long pgoff, unsigned long flags, |
| 953 | + struct drm_vma_offset_manager *mgr) |
| 954 | +{ |
| 955 | + unsigned long addr; |
| 956 | + unsigned long inflated_addr; |
| 957 | + struct drm_vma_offset_node *node; |
| 958 | + |
| 959 | + if (len > TASK_SIZE) |
| 960 | + return -ENOMEM; |
| 961 | + |
| 962 | + /* |
| 963 | + * @pgoff is the file page-offset the huge page boundaries of |
| 964 | + * which typically aligns to physical address huge page boundaries. |
| 965 | + * That's not true for DRM, however, where physical address huge |
| 966 | + * page boundaries instead are aligned with the offset from |
| 967 | + * buffer object start. So adjust @pgoff to be the offset from |
| 968 | + * buffer object start. |
| 969 | + */ |
| 970 | + drm_vma_offset_lock_lookup(mgr); |
| 971 | + node = drm_vma_offset_lookup_locked(mgr, pgoff, 1); |
| 972 | + if (node) |
| 973 | + pgoff -= node->vm_node.start; |
| 974 | + drm_vma_offset_unlock_lookup(mgr); |
| 975 | + |
| 976 | + addr = current->mm->get_unmapped_area(file, uaddr, len, pgoff, flags); |
| 977 | + if (IS_ERR_VALUE(addr)) |
| 978 | + return addr; |
| 979 | + if (addr & ~PAGE_MASK) |
| 980 | + return addr; |
| 981 | + if (addr > TASK_SIZE - len) |
| 982 | + return addr; |
| 983 | + |
| 984 | + if (len < HPAGE_PMD_SIZE) |
| 985 | + return addr; |
| 986 | + if (flags & MAP_FIXED) |
| 987 | + return addr; |
| 988 | + /* |
| 989 | + * Our priority is to support MAP_SHARED mapped hugely; |
| 990 | + * and support MAP_PRIVATE mapped hugely too, until it is COWed. |
| 991 | + * But if caller specified an address hint, respect that as before. |
| 992 | + */ |
| 993 | + if (uaddr) |
| 994 | + return addr; |
| 995 | + |
| 996 | + inflated_addr = drm_addr_inflate(addr, len, pgoff, flags, |
| 997 | + HPAGE_PMD_SIZE); |
| 998 | + |
| 999 | + if (IS_ENABLED(CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD) && |
| 1000 | + len >= HPAGE_PUD_SIZE) |
| 1001 | + inflated_addr = drm_addr_inflate(inflated_addr, len, pgoff, |
| 1002 | + flags, HPAGE_PUD_SIZE); |
| 1003 | + return inflated_addr; |
| 1004 | +} |
| 1005 | +#else /* CONFIG_TRANSPARENT_HUGEPAGE */ |
| 1006 | +unsigned long drm_get_unmapped_area(struct file *file, |
| 1007 | + unsigned long uaddr, unsigned long len, |
| 1008 | + unsigned long pgoff, unsigned long flags, |
| 1009 | + struct drm_vma_offset_manager *mgr) |
| 1010 | +{ |
| 1011 | + return current->mm->get_unmapped_area(file, uaddr, len, pgoff, flags); |
| 1012 | +} |
| 1013 | +#endif /* CONFIG_TRANSPARENT_HUGEPAGE */ |
| 1014 | +#endif /* CONFIG_MMU */ |
0 commit comments