1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
|
/* $NetBSD: drm_memory.c,v 1.17 2021/12/19 10:47:13 riastradh Exp $ */
/*-
* Copyright (c) 2013 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Taylor R. Campbell.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: drm_memory.c,v 1.17 2021/12/19 10:47:13 riastradh Exp $");
#if defined(__i386__) || defined(__x86_64__)
# ifdef _KERNEL_OPT
# include "agp.h"
# if NAGP > 0
# include "agp_i810.h"
# else
# define NAGP_I810 0
# endif
# include "genfb.h"
# else
# define NAGP_I810 1
# define NGENFB 0
# endif
#else
# ifdef _KERNEL_OPT
# define NAGP_I810 0
# include "genfb.h"
# else
# define NAGP_I810 0
# define NGENFB 0
# endif
#endif
#include <sys/bus.h>
#if NAGP_I810 > 0
/* XXX include order botch -- shouldn't need to include pcivar.h */
#include <dev/pci/pcivar.h>
#include <dev/pci/agpvar.h>
#endif
#if NGENFB > 0
#include <dev/wsfb/genfbvar.h>
#endif
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_cache.h>
#include <drm/drm_legacy.h>
#include <drm/drm_pci.h>
#include <drm/drm_print.h>
/*
* XXX drm_bus_borrow is a horrible kludge!
*/
static bool
drm_bus_borrow(bus_addr_t base, bus_size_t size, bus_space_handle_t *handlep)
{
#if NAGP_I810 > 0
if (agp_i810_borrow(base, size, handlep))
return true;
#endif
#if NGENFB > 0
if (genfb_borrow(base, handlep))
return true;
#endif
return false;
}
void
drm_legacy_ioremap(struct drm_local_map *map, struct drm_device *dev)
{
const bus_space_tag_t bst = dev->bst;
unsigned int unit;
/*
* Search dev's bus maps for a match.
*/
for (unit = 0; unit < dev->bus_nmaps; unit++) {
struct drm_bus_map *const bm = &dev->bus_maps[unit];
int flags = bm->bm_flags;
/* Reject maps starting after the request. */
if (map->offset < bm->bm_base)
continue;
/* Reject maps smaller than the request. */
if (bm->bm_size < map->size)
continue;
/* Reject maps that the request doesn't fit in. */
if ((bm->bm_size - map->size) <
(map->offset - bm->bm_base))
continue;
/* Ensure we can map the space into virtual memory. */
if (!ISSET(flags, BUS_SPACE_MAP_LINEAR))
continue;
/* Reflect requested flags in the bus_space map. */
if (ISSET(map->flags, _DRM_WRITE_COMBINING))
flags |= BUS_SPACE_MAP_PREFETCHABLE;
/* Map it. */
if (bus_space_map(bst, map->offset, map->size, flags,
&map->lm_data.bus_space.bsh))
break;
map->lm_data.bus_space.bus_map = bm;
goto win;
}
/* Couldn't map it. Try borrowing from someone else. */
if (drm_bus_borrow(map->offset, map->size,
&map->lm_data.bus_space.bsh)) {
map->lm_data.bus_space.bus_map = NULL;
goto win;
}
/* Failure! */
return;
win: map->lm_data.bus_space.bst = bst;
map->handle = bus_space_vaddr(bst, map->lm_data.bus_space.bsh);
}
void
drm_legacy_ioremapfree(struct drm_local_map *map, struct drm_device *dev)
{
if (map->lm_data.bus_space.bus_map != NULL) {
bus_space_unmap(map->lm_data.bus_space.bst,
map->lm_data.bus_space.bsh, map->size);
map->lm_data.bus_space.bus_map = NULL;
map->handle = NULL;
}
}
/*
* Allocate a drm dma handle, allocate memory fit for DMA, and map it.
*
* XXX This is called drm_pci_alloc for hysterical raisins; it is not
* specific to PCI.
*
* XXX For now, we use non-blocking allocations because this is called
* by ioctls with the drm global mutex held.
*
* XXX Error information is lost because this returns NULL on failure,
* not even an error embedded in a pointer.
*/
struct drm_dma_handle *
drm_pci_alloc(struct drm_device *dev, size_t size, size_t align)
{
int nsegs;
int error;
/*
* Allocate a drm_dma_handle record.
*/
struct drm_dma_handle *const dmah = kmem_alloc(sizeof(*dmah),
KM_NOSLEEP);
if (dmah == NULL) {
error = -ENOMEM;
goto out;
}
dmah->dmah_tag = dev->dmat;
/*
* Allocate the requested amount of DMA-safe memory.
*/
/* XXX errno NetBSD->Linux */
error = -bus_dmamem_alloc(dmah->dmah_tag, size, align, 0,
&dmah->dmah_seg, 1, &nsegs, BUS_DMA_NOWAIT);
if (error)
goto fail0;
KASSERT(nsegs == 1);
/*
* Map the DMA-safe memory into kernel virtual address space.
*/
/* XXX errno NetBSD->Linux */
error = -bus_dmamem_map(dmah->dmah_tag, &dmah->dmah_seg, 1, size,
&dmah->vaddr,
(BUS_DMA_NOWAIT | BUS_DMA_COHERENT | BUS_DMA_NOCACHE));
if (error)
goto fail1;
dmah->size = size;
/*
* Create a map for DMA transfers.
*/
/* XXX errno NetBSD->Linux */
error = -bus_dmamap_create(dmah->dmah_tag, size, 1, size, 0,
BUS_DMA_NOWAIT, &dmah->dmah_map);
if (error)
goto fail2;
/*
* Load the kva buffer into the map for DMA transfers.
*/
/* XXX errno NetBSD->Linux */
error = -bus_dmamap_load(dmah->dmah_tag, dmah->dmah_map, dmah->vaddr,
size, NULL, (BUS_DMA_NOWAIT | BUS_DMA_NOCACHE));
if (error)
goto fail3;
/* Record the bus address for convenient reference. */
dmah->busaddr = dmah->dmah_map->dm_segs[0].ds_addr;
/* Zero the DMA buffer. XXX Yikes! Is this necessary? */
memset(dmah->vaddr, 0, size);
/* Success! */
return dmah;
fail3: bus_dmamap_destroy(dmah->dmah_tag, dmah->dmah_map);
fail2: bus_dmamem_unmap(dmah->dmah_tag, dmah->vaddr, dmah->size);
fail1: bus_dmamem_free(dmah->dmah_tag, &dmah->dmah_seg, 1);
fail0: dmah->dmah_tag = NULL; /* XXX paranoia */
kmem_free(dmah, sizeof(*dmah));
out: DRM_DEBUG("drm_pci_alloc failed: %d\n", error);
return NULL;
}
/*
* Release the bus DMA mappings and memory in dmah, and deallocate it.
*/
void
drm_pci_free(struct drm_device *dev, struct drm_dma_handle *dmah)
{
bus_dmamap_unload(dmah->dmah_tag, dmah->dmah_map);
bus_dmamap_destroy(dmah->dmah_tag, dmah->dmah_map);
bus_dmamem_unmap(dmah->dmah_tag, dmah->vaddr, dmah->size);
bus_dmamem_free(dmah->dmah_tag, &dmah->dmah_seg, 1);
dmah->dmah_tag = NULL; /* XXX paranoia */
kmem_free(dmah, sizeof(*dmah));
}
/*
* Make sure the DMA-safe memory allocated for dev lies between
* min_addr and max_addr. Can be used multiple times to restrict the
* bounds further, but never to expand the bounds again.
*
* XXX Caller must guarantee nobody has used the tag yet,
* i.e. allocated any DMA memory.
*/
int
drm_limit_dma_space(struct drm_device *dev, resource_size_t min_addr,
resource_size_t max_addr)
{
int ret;
KASSERT(min_addr <= max_addr);
/*
* Limit it further if we have already limited it, and destroy
* the old subregion DMA tag.
*/
if (dev->dmat_subregion_p) {
min_addr = MAX(min_addr, dev->dmat_subregion_min);
max_addr = MIN(max_addr, dev->dmat_subregion_max);
bus_dmatag_destroy(dev->dmat);
}
/*
* If our limit contains the 32-bit space but for some reason
* we can't use a subregion, either because the bus doesn't
* support >32-bit DMA or because bus_dma(9) on this platform
* lacks bus_dmatag_subregion, just use the 32-bit space.
*/
if (min_addr == 0 && max_addr >= UINT32_C(0xffffffff) &&
dev->bus_dmat == dev->bus_dmat32) {
dma32: dev->dmat = dev->bus_dmat32;
dev->dmat_subregion_p = false;
dev->dmat_subregion_min = 0;
dev->dmat_subregion_max = UINT32_C(0xffffffff);
return 0;
}
/*
* Create a DMA tag for a subregion from the bus's DMA tag. If
* that fails, restore dev->dmat to the whole region so that we
* need not worry about dev->dmat being uninitialized (not that
* the caller should try to allocate DMA-safe memory on failure
* anyway, but...paranoia).
*/
/* XXX errno NetBSD->Linux */
ret = -bus_dmatag_subregion(dev->bus_dmat, min_addr, max_addr,
&dev->dmat, BUS_DMA_WAITOK);
if (ret) {
/*
* bus_dmatag_subregion may fail. If so, and if the
* subregion contains the 32-bit space, just use the
* 32-bit DMA tag.
*/
if (ret == -EOPNOTSUPP && dev->bus_dmat32 &&
min_addr == 0 && max_addr >= UINT32_C(0xffffffff))
goto dma32;
/* XXX Back out? */
dev->dmat = dev->bus_dmat;
dev->dmat_subregion_p = false;
dev->dmat_subregion_min = 0;
dev->dmat_subregion_max = __type_max(bus_addr_t);
return ret;
}
/*
* Remember that we have a subregion tag so that we know to
* destroy it later, and record the bounds in case we need to
* limit them again.
*/
dev->dmat_subregion_p = true;
dev->dmat_subregion_min = min_addr;
dev->dmat_subregion_max = max_addr;
/* Success! */
return 0;
}
bool
drm_need_swiotlb(int dma_bits)
{
return false;
}
|