1/* $NetBSD: drm_scatter.c,v 1.3 2014/07/16 20:56:25 riastradh Exp $ */
2
3/*-
4 * Copyright (c) 2013 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Taylor R. Campbell.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33__KERNEL_RCSID(0, "$NetBSD: drm_scatter.c,v 1.3 2014/07/16 20:56:25 riastradh Exp $");
34
35#include <sys/types.h>
36#include <sys/bus.h>
37#include <sys/errno.h>
38#include <sys/systm.h>
39
40#include <linux/slab.h>
41
42#include <drm/drmP.h>
43
44static int drm_sg_alloc_mem(struct drm_device *, size_t,
45 struct drm_sg_mem **);
46static void drm_sg_free_mem(struct drm_device *, struct drm_sg_mem *);
47
48int
49drm_sg_alloc(struct drm_device *dev, void *data,
50 struct drm_file *file __unused)
51{
52 struct drm_scatter_gather *const request = data;
53 struct drm_sg_mem *sg;
54 int error;
55
56 /*
57 * XXX Should not hold this mutex during drm_sg_alloc. For
58 * now, we'll just use non-blocking allocations.
59 */
60 KASSERT(mutex_is_locked(&drm_global_mutex));
61
62 /*
63 * Sanity-check the inputs.
64 */
65 if (!drm_core_check_feature(dev, DRIVER_SG))
66 return -ENODEV;
67 if (dev->sg != NULL)
68 return -EBUSY;
69 if (request->size > 0xffffffffUL)
70 return -ENOMEM;
71
72 /*
73 * Do the allocation.
74 *
75 * XXX Would it be safe to drop drm_global_mutex here to avoid
76 * holding it during allocation?
77 */
78 error = drm_sg_alloc_mem(dev, request->size, &sg);
79 if (error)
80 return error;
81
82 /* Set it up to be the device's scatter-gather memory. */
83 dev->sg = sg;
84
85 /* Success! */
86 request->handle = sg->handle;
87 return 0;
88}
89
90int
91drm_sg_free(struct drm_device *dev, void *data, struct drm_file *file)
92{
93 struct drm_scatter_gather *const request = data;
94 struct drm_sg_mem *sg;
95
96 KASSERT(mutex_is_locked(&drm_global_mutex));
97
98 /*
99 * Sanity-check the inputs.
100 */
101 if (!drm_core_check_feature(dev, DRIVER_SG))
102 return -ENODEV;
103
104 sg = dev->sg;
105 if (sg == NULL)
106 return -ENXIO;
107 if (sg->handle != request->handle)
108 return -EINVAL;
109
110 /* Remove dev->sg. */
111 dev->sg = NULL;
112
113 /* Free it. */
114 drm_sg_free_mem(dev, sg);
115
116 /* Success! */
117 return 0;
118}
119
120void
121drm_legacy_sg_cleanup(struct drm_device *dev)
122{
123
124 if (!drm_core_check_feature(dev, DRIVER_SG))
125 return;
126 if (dev->sg == NULL)
127 return;
128 if (drm_core_check_feature(dev, DRIVER_MODESET))
129 return;
130
131 drm_sg_free_mem(dev, dev->sg);
132}
133
134static int
135drm_sg_alloc_mem(struct drm_device *dev, size_t size, struct drm_sg_mem **sgp)
136{
137 int nsegs;
138 int error;
139
140 KASSERT(drm_core_check_feature(dev, DRIVER_SG));
141
142 KASSERT(size <= (size_t)0xffffffffUL); /* XXX 32-bit sizes only? */
143 const size_t nbytes = PAGE_ALIGN(size);
144 const size_t npages = nbytes >> PAGE_SHIFT;
145 KASSERT(npages <= (size_t)INT_MAX);
146
147 /*
148 * Allocate a drm_sg_mem record.
149 */
150 struct drm_sg_mem *const sg =
151 kmem_zalloc(offsetof(struct drm_sg_mem, sg_segs[npages]),
152 KM_NOSLEEP);
153 if (sg == NULL)
154 return -ENOMEM;
155 sg->sg_tag = dev->dmat;
156 sg->sg_nsegs_max = (unsigned int)npages;
157
158 /*
159 * Allocate the requested amount of DMA-safe memory.
160 */
161 KASSERT(sg->sg_nsegs_max <= (unsigned int)INT_MAX);
162 /* XXX errno NetBSD->Linux */
163 error = -bus_dmamem_alloc(sg->sg_tag, nbytes, PAGE_SIZE, 0,
164 sg->sg_segs, (int)sg->sg_nsegs_max, &nsegs, BUS_DMA_NOWAIT);
165 if (error)
166 goto fail0;
167 KASSERT(0 <= nsegs);
168 sg->sg_nsegs = (unsigned int)nsegs;
169
170 /*
171 * XXX Old drm passed DRM_BUS_NOWAIT below but BUS_DMA_WAITOK
172 * above. WTF?
173 */
174
175 /*
176 * Map the DMA-safe memory into kernel virtual address space.
177 */
178 /* XXX errno NetBSD->Linux */
179 error = -bus_dmamem_map(sg->sg_tag, sg->sg_segs, nsegs, nbytes,
180 &sg->virtual,
181 (BUS_DMA_NOWAIT | BUS_DMA_COHERENT | BUS_DMA_NOCACHE));
182 if (error)
183 goto fail1;
184 sg->sg_size = nbytes;
185
186 /*
187 * Create a map for DMA transfers.
188 */
189 /* XXX errno NetBSD->Linux */
190 error = -bus_dmamap_create(sg->sg_tag, nbytes, nsegs, nbytes, 0,
191 BUS_DMA_NOWAIT, &sg->sg_map);
192 if (error)
193 goto fail2;
194
195 /*
196 * Load the kva buffer into the map for DMA transfers.
197 */
198 /* XXX errno NetBSD->Linux */
199 error = -bus_dmamap_load(sg->sg_tag, sg->sg_map, sg->virtual, nbytes,
200 NULL, (BUS_DMA_NOWAIT | BUS_DMA_NOCACHE));
201 if (error)
202 goto fail3;
203
204 /*
205 * Use the kernel virtual address as the userland handle.
206 *
207 * XXX This leaks some information about kernel virtual
208 * addresses to userland; should we use an idr instead?
209 */
210 sg->handle = (unsigned long)(uintptr_t)sg->virtual;
211 KASSERT(sg->virtual == (void *)(uintptr_t)sg->handle);
212
213 /* Success! */
214 *sgp = sg;
215 return 0;
216
217fail3: bus_dmamap_destroy(sg->sg_tag, sg->sg_map);
218fail2: bus_dmamem_unmap(sg->sg_tag, sg->virtual, sg->sg_size);
219fail1: KASSERT(sg->sg_nsegs <= (unsigned int)INT_MAX);
220 bus_dmamem_free(sg->sg_tag, sg->sg_segs, (int)sg->sg_nsegs);
221fail0: sg->sg_tag = NULL; /* XXX paranoia */
222 kmem_free(sg, offsetof(struct drm_sg_mem, sg_segs[sg->sg_nsegs_max]));
223 return error;
224}
225
226static void
227drm_sg_free_mem(struct drm_device *dev, struct drm_sg_mem *sg)
228{
229
230 bus_dmamap_unload(sg->sg_tag, sg->sg_map);
231 bus_dmamap_destroy(sg->sg_tag, sg->sg_map);
232 bus_dmamem_unmap(sg->sg_tag, sg->virtual, sg->sg_size);
233 KASSERT(sg->sg_nsegs <= (unsigned int)INT_MAX);
234 bus_dmamem_free(sg->sg_tag, sg->sg_segs, (int)sg->sg_nsegs);
235 sg->sg_tag = NULL; /* XXX paranoia */
236 kmem_free(sg, offsetof(struct drm_sg_mem, sg_segs[sg->sg_nsegs_max]));
237}
238