1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Copyright (C) 2015-2018 Broadcom */ |
3 | |
4 | /** |
5 | * DOC: V3D GEM BO management support |
6 | * |
7 | * Compared to VC4 (V3D 2.x), V3D 3.3 introduces an MMU between the |
8 | * GPU and the bus, allowing us to use shmem objects for our storage |
9 | * instead of CMA. |
10 | * |
11 | * Physically contiguous objects may still be imported to V3D, but the |
12 | * driver doesn't allocate physically contiguous objects on its own. |
13 | * Display engines requiring physically contiguous allocations should |
14 | * look into Mesa's "renderonly" support (as used by the Mesa pl111 |
15 | * driver) for an example of how to integrate with V3D. |
16 | * |
17 | * Long term, we should support evicting pages from the MMU when under |
18 | * memory pressure (thus the v3d_bo_get_pages() refcounting), but |
19 | * that's not a high priority since our systems tend to not have swap. |
20 | */ |
21 | |
22 | #include <linux/dma-buf.h> |
23 | #include <linux/pfn_t.h> |
24 | |
25 | #include "v3d_drv.h" |
26 | #include "uapi/drm/v3d_drm.h" |
27 | |
28 | /* Called DRM core on the last userspace/kernel unreference of the |
29 | * BO. |
30 | */ |
31 | void v3d_free_object(struct drm_gem_object *obj) |
32 | { |
33 | struct v3d_dev *v3d = to_v3d_dev(dev: obj->dev); |
34 | struct v3d_bo *bo = to_v3d_bo(bo: obj); |
35 | |
36 | v3d_mmu_remove_ptes(bo); |
37 | |
38 | mutex_lock(&v3d->bo_lock); |
39 | v3d->bo_stats.num_allocated--; |
40 | v3d->bo_stats.pages_allocated -= obj->size >> PAGE_SHIFT; |
41 | mutex_unlock(lock: &v3d->bo_lock); |
42 | |
43 | spin_lock(lock: &v3d->mm_lock); |
44 | drm_mm_remove_node(node: &bo->node); |
45 | spin_unlock(lock: &v3d->mm_lock); |
46 | |
47 | /* GPU execution may have dirtied any pages in the BO. */ |
48 | bo->base.pages_mark_dirty_on_put = true; |
49 | |
50 | drm_gem_shmem_free(shmem: &bo->base); |
51 | } |
52 | |
53 | static const struct drm_gem_object_funcs v3d_gem_funcs = { |
54 | .free = v3d_free_object, |
55 | .print_info = drm_gem_shmem_object_print_info, |
56 | .pin = drm_gem_shmem_object_pin, |
57 | .unpin = drm_gem_shmem_object_unpin, |
58 | .get_sg_table = drm_gem_shmem_object_get_sg_table, |
59 | .vmap = drm_gem_shmem_object_vmap, |
60 | .vunmap = drm_gem_shmem_object_vunmap, |
61 | .mmap = drm_gem_shmem_object_mmap, |
62 | .vm_ops = &drm_gem_shmem_vm_ops, |
63 | }; |
64 | |
65 | /* gem_create_object function for allocating a BO struct and doing |
66 | * early setup. |
67 | */ |
68 | struct drm_gem_object *v3d_create_object(struct drm_device *dev, size_t size) |
69 | { |
70 | struct v3d_bo *bo; |
71 | struct drm_gem_object *obj; |
72 | |
73 | if (size == 0) |
74 | return ERR_PTR(error: -EINVAL); |
75 | |
76 | bo = kzalloc(size: sizeof(*bo), GFP_KERNEL); |
77 | if (!bo) |
78 | return ERR_PTR(error: -ENOMEM); |
79 | obj = &bo->base.base; |
80 | |
81 | obj->funcs = &v3d_gem_funcs; |
82 | bo->base.map_wc = true; |
83 | INIT_LIST_HEAD(list: &bo->unref_head); |
84 | |
85 | return &bo->base.base; |
86 | } |
87 | |
88 | static int |
89 | v3d_bo_create_finish(struct drm_gem_object *obj) |
90 | { |
91 | struct v3d_dev *v3d = to_v3d_dev(dev: obj->dev); |
92 | struct v3d_bo *bo = to_v3d_bo(bo: obj); |
93 | struct sg_table *sgt; |
94 | int ret; |
95 | |
96 | /* So far we pin the BO in the MMU for its lifetime, so use |
97 | * shmem's helper for getting a lifetime sgt. |
98 | */ |
99 | sgt = drm_gem_shmem_get_pages_sgt(shmem: &bo->base); |
100 | if (IS_ERR(ptr: sgt)) |
101 | return PTR_ERR(ptr: sgt); |
102 | |
103 | spin_lock(lock: &v3d->mm_lock); |
104 | /* Allocate the object's space in the GPU's page tables. |
105 | * Inserting PTEs will happen later, but the offset is for the |
106 | * lifetime of the BO. |
107 | */ |
108 | ret = drm_mm_insert_node_generic(mm: &v3d->mm, node: &bo->node, |
109 | size: obj->size >> PAGE_SHIFT, |
110 | GMP_GRANULARITY >> PAGE_SHIFT, color: 0, mode: 0); |
111 | spin_unlock(lock: &v3d->mm_lock); |
112 | if (ret) |
113 | return ret; |
114 | |
115 | /* Track stats for /debug/dri/n/bo_stats. */ |
116 | mutex_lock(&v3d->bo_lock); |
117 | v3d->bo_stats.num_allocated++; |
118 | v3d->bo_stats.pages_allocated += obj->size >> PAGE_SHIFT; |
119 | mutex_unlock(lock: &v3d->bo_lock); |
120 | |
121 | v3d_mmu_insert_ptes(bo); |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | struct v3d_bo *v3d_bo_create(struct drm_device *dev, struct drm_file *file_priv, |
127 | size_t unaligned_size) |
128 | { |
129 | struct drm_gem_shmem_object *shmem_obj; |
130 | struct v3d_bo *bo; |
131 | int ret; |
132 | |
133 | shmem_obj = drm_gem_shmem_create(dev, size: unaligned_size); |
134 | if (IS_ERR(ptr: shmem_obj)) |
135 | return ERR_CAST(ptr: shmem_obj); |
136 | bo = to_v3d_bo(bo: &shmem_obj->base); |
137 | |
138 | ret = v3d_bo_create_finish(obj: &shmem_obj->base); |
139 | if (ret) |
140 | goto free_obj; |
141 | |
142 | return bo; |
143 | |
144 | free_obj: |
145 | drm_gem_shmem_free(shmem: shmem_obj); |
146 | return ERR_PTR(error: ret); |
147 | } |
148 | |
149 | struct drm_gem_object * |
150 | v3d_prime_import_sg_table(struct drm_device *dev, |
151 | struct dma_buf_attachment *attach, |
152 | struct sg_table *sgt) |
153 | { |
154 | struct drm_gem_object *obj; |
155 | int ret; |
156 | |
157 | obj = drm_gem_shmem_prime_import_sg_table(dev, attach, sgt); |
158 | if (IS_ERR(ptr: obj)) |
159 | return obj; |
160 | |
161 | ret = v3d_bo_create_finish(obj); |
162 | if (ret) { |
163 | drm_gem_shmem_free(shmem: &to_v3d_bo(bo: obj)->base); |
164 | return ERR_PTR(error: ret); |
165 | } |
166 | |
167 | return obj; |
168 | } |
169 | |
170 | int v3d_create_bo_ioctl(struct drm_device *dev, void *data, |
171 | struct drm_file *file_priv) |
172 | { |
173 | struct drm_v3d_create_bo *args = data; |
174 | struct v3d_bo *bo = NULL; |
175 | int ret; |
176 | |
177 | if (args->flags != 0) { |
178 | DRM_INFO("unknown create_bo flags: %d\n" , args->flags); |
179 | return -EINVAL; |
180 | } |
181 | |
182 | bo = v3d_bo_create(dev, file_priv, PAGE_ALIGN(args->size)); |
183 | if (IS_ERR(ptr: bo)) |
184 | return PTR_ERR(ptr: bo); |
185 | |
186 | args->offset = bo->node.start << PAGE_SHIFT; |
187 | |
188 | ret = drm_gem_handle_create(file_priv, obj: &bo->base.base, handlep: &args->handle); |
189 | drm_gem_object_put(obj: &bo->base.base); |
190 | |
191 | return ret; |
192 | } |
193 | |
194 | int v3d_mmap_bo_ioctl(struct drm_device *dev, void *data, |
195 | struct drm_file *file_priv) |
196 | { |
197 | struct drm_v3d_mmap_bo *args = data; |
198 | struct drm_gem_object *gem_obj; |
199 | |
200 | if (args->flags != 0) { |
201 | DRM_INFO("unknown mmap_bo flags: %d\n" , args->flags); |
202 | return -EINVAL; |
203 | } |
204 | |
205 | gem_obj = drm_gem_object_lookup(filp: file_priv, handle: args->handle); |
206 | if (!gem_obj) { |
207 | DRM_DEBUG("Failed to look up GEM BO %d\n" , args->handle); |
208 | return -ENOENT; |
209 | } |
210 | |
211 | args->offset = drm_vma_node_offset_addr(node: &gem_obj->vma_node); |
212 | drm_gem_object_put(obj: gem_obj); |
213 | |
214 | return 0; |
215 | } |
216 | |
217 | int v3d_get_bo_offset_ioctl(struct drm_device *dev, void *data, |
218 | struct drm_file *file_priv) |
219 | { |
220 | struct drm_v3d_get_bo_offset *args = data; |
221 | struct drm_gem_object *gem_obj; |
222 | struct v3d_bo *bo; |
223 | |
224 | gem_obj = drm_gem_object_lookup(filp: file_priv, handle: args->handle); |
225 | if (!gem_obj) { |
226 | DRM_DEBUG("Failed to look up GEM BO %d\n" , args->handle); |
227 | return -ENOENT; |
228 | } |
229 | bo = to_v3d_bo(bo: gem_obj); |
230 | |
231 | args->offset = bo->node.start << PAGE_SHIFT; |
232 | |
233 | drm_gem_object_put(obj: gem_obj); |
234 | return 0; |
235 | } |
236 | |