Commit | Line | Data |
---|---|---|
ebb945a9 BS |
1 | /* |
2 | * Copyright 2012 Red Hat Inc. | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
20 | * OTHER DEALINGS IN THE SOFTWARE. | |
21 | * | |
22 | * Authors: Ben Skeggs | |
23 | */ | |
24 | ||
25 | #include <core/object.h> | |
26 | #include <core/client.h> | |
27 | #include <core/device.h> | |
28 | #include <core/class.h> | |
29 | ||
30 | #include <subdev/fb.h> | |
31 | #include <subdev/vm.h> | |
32 | #include <subdev/instmem.h> | |
33 | ||
34 | #include <engine/software.h> | |
35 | ||
36 | #include "nouveau_drm.h" | |
37 | #include "nouveau_dma.h" | |
38 | #include "nouveau_bo.h" | |
39 | #include "nouveau_chan.h" | |
40 | #include "nouveau_fence.h" | |
41 | #include "nouveau_abi16.h" | |
42 | ||
43 | MODULE_PARM_DESC(vram_pushbuf, "Create DMA push buffers in VRAM"); | |
44 | static int nouveau_vram_pushbuf; | |
45 | module_param_named(vram_pushbuf, nouveau_vram_pushbuf, int, 0400); | |
46 | ||
47 | int | |
48 | nouveau_channel_idle(struct nouveau_channel *chan) | |
49 | { | |
da07e52c | 50 | struct nouveau_cli *cli = chan->cli; |
ebb945a9 BS |
51 | struct nouveau_fence *fence = NULL; |
52 | int ret; | |
53 | ||
264ce192 | 54 | ret = nouveau_fence_new(chan, false, &fence); |
ebb945a9 BS |
55 | if (!ret) { |
56 | ret = nouveau_fence_wait(fence, false, false); | |
57 | nouveau_fence_unref(&fence); | |
58 | } | |
59 | ||
60 | if (ret) | |
93260d3c MS |
61 | NV_ERROR(cli, "failed to idle channel 0x%08x [%s]\n", |
62 | chan->handle, cli->base.name); | |
ebb945a9 BS |
63 | return ret; |
64 | } | |
65 | ||
66 | void | |
67 | nouveau_channel_del(struct nouveau_channel **pchan) | |
68 | { | |
69 | struct nouveau_channel *chan = *pchan; | |
70 | if (chan) { | |
71 | struct nouveau_object *client = nv_object(chan->cli); | |
72 | if (chan->fence) { | |
73 | nouveau_channel_idle(chan); | |
74 | nouveau_fence(chan->drm)->context_del(chan); | |
75 | } | |
76 | nouveau_object_del(client, NVDRM_DEVICE, chan->handle); | |
77 | nouveau_object_del(client, NVDRM_DEVICE, chan->push.handle); | |
78 | nouveau_bo_vma_del(chan->push.buffer, &chan->push.vma); | |
79 | nouveau_bo_unmap(chan->push.buffer); | |
124ea297 MS |
80 | if (chan->push.buffer && chan->push.buffer->pin_refcnt) |
81 | nouveau_bo_unpin(chan->push.buffer); | |
ebb945a9 BS |
82 | nouveau_bo_ref(NULL, &chan->push.buffer); |
83 | kfree(chan); | |
84 | } | |
85 | *pchan = NULL; | |
86 | } | |
87 | ||
88 | static int | |
89 | nouveau_channel_prep(struct nouveau_drm *drm, struct nouveau_cli *cli, | |
90 | u32 parent, u32 handle, u32 size, | |
91 | struct nouveau_channel **pchan) | |
92 | { | |
93 | struct nouveau_device *device = nv_device(drm->device); | |
94 | struct nouveau_instmem *imem = nouveau_instmem(device); | |
95 | struct nouveau_vmmgr *vmm = nouveau_vmmgr(device); | |
96 | struct nouveau_fb *pfb = nouveau_fb(device); | |
97 | struct nouveau_client *client = &cli->base; | |
98 | struct nv_dma_class args = {}; | |
99 | struct nouveau_channel *chan; | |
100 | struct nouveau_object *push; | |
101 | u32 target; | |
102 | int ret; | |
103 | ||
104 | chan = *pchan = kzalloc(sizeof(*chan), GFP_KERNEL); | |
105 | if (!chan) | |
106 | return -ENOMEM; | |
107 | ||
108 | chan->cli = cli; | |
109 | chan->drm = drm; | |
110 | chan->handle = handle; | |
111 | ||
112 | /* allocate memory for dma push buffer */ | |
113 | target = TTM_PL_FLAG_TT; | |
114 | if (nouveau_vram_pushbuf) | |
115 | target = TTM_PL_FLAG_VRAM; | |
116 | ||
117 | ret = nouveau_bo_new(drm->dev, size, 0, target, 0, 0, NULL, | |
118 | &chan->push.buffer); | |
119 | if (ret == 0) { | |
120 | ret = nouveau_bo_pin(chan->push.buffer, target); | |
121 | if (ret == 0) | |
122 | ret = nouveau_bo_map(chan->push.buffer); | |
123 | } | |
124 | ||
125 | if (ret) { | |
126 | nouveau_channel_del(pchan); | |
127 | return ret; | |
128 | } | |
129 | ||
130 | /* create dma object covering the *entire* memory space that the | |
131 | * pushbuf lives in, this is because the GEM code requires that | |
132 | * we be able to call out to other (indirect) push buffers | |
133 | */ | |
134 | chan->push.vma.offset = chan->push.buffer->bo.offset; | |
135 | chan->push.handle = NVDRM_PUSH | (handle & 0xffff); | |
136 | ||
137 | if (device->card_type >= NV_50) { | |
138 | ret = nouveau_bo_vma_add(chan->push.buffer, client->vm, | |
139 | &chan->push.vma); | |
140 | if (ret) { | |
141 | nouveau_channel_del(pchan); | |
142 | return ret; | |
143 | } | |
144 | ||
145 | args.flags = NV_DMA_TARGET_VM | NV_DMA_ACCESS_VM; | |
146 | args.start = 0; | |
147 | args.limit = client->vm->vmm->limit - 1; | |
148 | } else | |
149 | if (chan->push.buffer->bo.mem.mem_type == TTM_PL_VRAM) { | |
150 | u64 limit = pfb->ram.size - imem->reserved - 1; | |
151 | if (device->card_type == NV_04) { | |
152 | /* nv04 vram pushbuf hack, retarget to its location in | |
153 | * the framebuffer bar rather than direct vram access.. | |
154 | * nfi why this exists, it came from the -nv ddx. | |
155 | */ | |
156 | args.flags = NV_DMA_TARGET_PCI | NV_DMA_ACCESS_RDWR; | |
157 | args.start = pci_resource_start(device->pdev, 1); | |
158 | args.limit = args.start + limit; | |
159 | } else { | |
160 | args.flags = NV_DMA_TARGET_VRAM | NV_DMA_ACCESS_RDWR; | |
161 | args.start = 0; | |
162 | args.limit = limit; | |
163 | } | |
164 | } else { | |
165 | if (chan->drm->agp.stat == ENABLED) { | |
166 | args.flags = NV_DMA_TARGET_AGP | NV_DMA_ACCESS_RDWR; | |
167 | args.start = chan->drm->agp.base; | |
168 | args.limit = chan->drm->agp.base + | |
169 | chan->drm->agp.size - 1; | |
170 | } else { | |
171 | args.flags = NV_DMA_TARGET_VM | NV_DMA_ACCESS_RDWR; | |
172 | args.start = 0; | |
173 | args.limit = vmm->limit - 1; | |
174 | } | |
175 | } | |
176 | ||
177 | ret = nouveau_object_new(nv_object(chan->cli), parent, | |
178 | chan->push.handle, 0x0002, | |
179 | &args, sizeof(args), &push); | |
180 | if (ret) { | |
181 | nouveau_channel_del(pchan); | |
182 | return ret; | |
183 | } | |
184 | ||
185 | return 0; | |
186 | } | |
187 | ||
5b8a43ae | 188 | static int |
ebb945a9 | 189 | nouveau_channel_ind(struct nouveau_drm *drm, struct nouveau_cli *cli, |
49981046 BS |
190 | u32 parent, u32 handle, u32 engine, |
191 | struct nouveau_channel **pchan) | |
ebb945a9 | 192 | { |
c97f8c92 BS |
193 | static const u16 oclasses[] = { NVE0_CHANNEL_IND_CLASS, |
194 | NVC0_CHANNEL_IND_CLASS, | |
195 | NV84_CHANNEL_IND_CLASS, | |
196 | NV50_CHANNEL_IND_CLASS, | |
197 | 0 }; | |
ebb945a9 | 198 | const u16 *oclass = oclasses; |
dbff2dee | 199 | struct nve0_channel_ind_class args; |
ebb945a9 BS |
200 | struct nouveau_channel *chan; |
201 | int ret; | |
202 | ||
203 | /* allocate dma push buffer */ | |
204 | ret = nouveau_channel_prep(drm, cli, parent, handle, 0x12000, &chan); | |
205 | *pchan = chan; | |
206 | if (ret) | |
207 | return ret; | |
208 | ||
209 | /* create channel object */ | |
210 | args.pushbuf = chan->push.handle; | |
211 | args.ioffset = 0x10000 + chan->push.vma.offset; | |
212 | args.ilength = 0x02000; | |
49981046 | 213 | args.engine = engine; |
ebb945a9 BS |
214 | |
215 | do { | |
216 | ret = nouveau_object_new(nv_object(cli), parent, handle, | |
217 | *oclass++, &args, sizeof(args), | |
218 | &chan->object); | |
219 | if (ret == 0) | |
220 | return ret; | |
221 | } while (*oclass); | |
222 | ||
223 | nouveau_channel_del(pchan); | |
224 | return ret; | |
225 | } | |
226 | ||
227 | static int | |
228 | nouveau_channel_dma(struct nouveau_drm *drm, struct nouveau_cli *cli, | |
229 | u32 parent, u32 handle, struct nouveau_channel **pchan) | |
230 | { | |
c97f8c92 BS |
231 | static const u16 oclasses[] = { NV40_CHANNEL_DMA_CLASS, |
232 | NV17_CHANNEL_DMA_CLASS, | |
233 | NV10_CHANNEL_DMA_CLASS, | |
234 | NV03_CHANNEL_DMA_CLASS, | |
235 | 0 }; | |
ebb945a9 | 236 | const u16 *oclass = oclasses; |
a7c6e75e | 237 | struct nv03_channel_dma_class args; |
ebb945a9 BS |
238 | struct nouveau_channel *chan; |
239 | int ret; | |
240 | ||
241 | /* allocate dma push buffer */ | |
242 | ret = nouveau_channel_prep(drm, cli, parent, handle, 0x10000, &chan); | |
243 | *pchan = chan; | |
244 | if (ret) | |
245 | return ret; | |
246 | ||
247 | /* create channel object */ | |
248 | args.pushbuf = chan->push.handle; | |
249 | args.offset = chan->push.vma.offset; | |
250 | ||
251 | do { | |
252 | ret = nouveau_object_new(nv_object(cli), parent, handle, | |
253 | *oclass++, &args, sizeof(args), | |
254 | &chan->object); | |
255 | if (ret == 0) | |
256 | return ret; | |
257 | } while (ret && *oclass); | |
258 | ||
259 | nouveau_channel_del(pchan); | |
260 | return ret; | |
261 | } | |
262 | ||
263 | static int | |
264 | nouveau_channel_init(struct nouveau_channel *chan, u32 vram, u32 gart) | |
265 | { | |
266 | struct nouveau_client *client = nv_client(chan->cli); | |
267 | struct nouveau_device *device = nv_device(chan->drm->device); | |
268 | struct nouveau_instmem *imem = nouveau_instmem(device); | |
269 | struct nouveau_vmmgr *vmm = nouveau_vmmgr(device); | |
270 | struct nouveau_fb *pfb = nouveau_fb(device); | |
271 | struct nouveau_software_chan *swch; | |
272 | struct nouveau_object *object; | |
f756944a | 273 | struct nv_dma_class args = {}; |
ebb945a9 BS |
274 | int ret, i; |
275 | ||
ebb945a9 BS |
276 | /* allocate dma objects to cover all allowed vram, and gart */ |
277 | if (device->card_type < NV_C0) { | |
278 | if (device->card_type >= NV_50) { | |
279 | args.flags = NV_DMA_TARGET_VM | NV_DMA_ACCESS_VM; | |
280 | args.start = 0; | |
281 | args.limit = client->vm->vmm->limit - 1; | |
282 | } else { | |
283 | args.flags = NV_DMA_TARGET_VRAM | NV_DMA_ACCESS_RDWR; | |
284 | args.start = 0; | |
285 | args.limit = pfb->ram.size - imem->reserved - 1; | |
286 | } | |
287 | ||
288 | ret = nouveau_object_new(nv_object(client), chan->handle, vram, | |
289 | 0x003d, &args, sizeof(args), &object); | |
290 | if (ret) | |
291 | return ret; | |
292 | ||
293 | if (device->card_type >= NV_50) { | |
294 | args.flags = NV_DMA_TARGET_VM | NV_DMA_ACCESS_VM; | |
295 | args.start = 0; | |
296 | args.limit = client->vm->vmm->limit - 1; | |
297 | } else | |
298 | if (chan->drm->agp.stat == ENABLED) { | |
299 | args.flags = NV_DMA_TARGET_AGP | NV_DMA_ACCESS_RDWR; | |
300 | args.start = chan->drm->agp.base; | |
301 | args.limit = chan->drm->agp.base + | |
302 | chan->drm->agp.size - 1; | |
303 | } else { | |
304 | args.flags = NV_DMA_TARGET_VM | NV_DMA_ACCESS_RDWR; | |
305 | args.start = 0; | |
306 | args.limit = vmm->limit - 1; | |
307 | } | |
308 | ||
309 | ret = nouveau_object_new(nv_object(client), chan->handle, gart, | |
310 | 0x003d, &args, sizeof(args), &object); | |
311 | if (ret) | |
312 | return ret; | |
49981046 BS |
313 | |
314 | chan->vram = vram; | |
315 | chan->gart = gart; | |
ebb945a9 BS |
316 | } |
317 | ||
318 | /* initialise dma tracking parameters */ | |
503b0f1c BS |
319 | switch (nv_hclass(chan->object) & 0x00ff) { |
320 | case 0x006b: | |
ebb945a9 BS |
321 | case 0x006e: |
322 | chan->user_put = 0x40; | |
323 | chan->user_get = 0x44; | |
324 | chan->dma.max = (0x10000 / 4) - 2; | |
325 | break; | |
326 | default: | |
327 | chan->user_put = 0x40; | |
328 | chan->user_get = 0x44; | |
329 | chan->user_get_hi = 0x60; | |
330 | chan->dma.ib_base = 0x10000 / 4; | |
331 | chan->dma.ib_max = (0x02000 / 8) - 1; | |
332 | chan->dma.ib_put = 0; | |
333 | chan->dma.ib_free = chan->dma.ib_max - chan->dma.ib_put; | |
334 | chan->dma.max = chan->dma.ib_base; | |
335 | break; | |
336 | } | |
337 | ||
338 | chan->dma.put = 0; | |
339 | chan->dma.cur = chan->dma.put; | |
340 | chan->dma.free = chan->dma.max - chan->dma.cur; | |
341 | ||
342 | ret = RING_SPACE(chan, NOUVEAU_DMA_SKIPS); | |
343 | if (ret) | |
344 | return ret; | |
345 | ||
346 | for (i = 0; i < NOUVEAU_DMA_SKIPS; i++) | |
347 | OUT_RING(chan, 0x00000000); | |
348 | ||
349 | /* allocate software object class (used for fences on <= nv05, and | |
350 | * to signal flip completion), bind it to a subchannel. | |
351 | */ | |
49469800 | 352 | if ((device->card_type < NV_E0) || gart /* nve0: want_nvsw */) { |
49981046 BS |
353 | ret = nouveau_object_new(nv_object(client), chan->handle, |
354 | NvSw, nouveau_abi16_swclass(chan->drm), | |
355 | NULL, 0, &object); | |
356 | if (ret) | |
357 | return ret; | |
ebb945a9 | 358 | |
49981046 BS |
359 | swch = (void *)object->parent; |
360 | swch->flip = nouveau_flip_complete; | |
361 | swch->flip_data = chan; | |
362 | } | |
ebb945a9 BS |
363 | |
364 | if (device->card_type < NV_C0) { | |
365 | ret = RING_SPACE(chan, 2); | |
366 | if (ret) | |
367 | return ret; | |
368 | ||
369 | BEGIN_NV04(chan, NvSubSw, 0x0000, 1); | |
370 | OUT_RING (chan, NvSw); | |
371 | FIRE_RING (chan); | |
372 | } | |
373 | ||
374 | /* initialise synchronisation */ | |
375 | return nouveau_fence(chan->drm)->context_new(chan); | |
376 | } | |
377 | ||
378 | int | |
379 | nouveau_channel_new(struct nouveau_drm *drm, struct nouveau_cli *cli, | |
49981046 | 380 | u32 parent, u32 handle, u32 arg0, u32 arg1, |
ebb945a9 BS |
381 | struct nouveau_channel **pchan) |
382 | { | |
383 | int ret; | |
384 | ||
49981046 | 385 | ret = nouveau_channel_ind(drm, cli, parent, handle, arg0, pchan); |
ebb945a9 | 386 | if (ret) { |
da07e52c | 387 | NV_DEBUG(cli, "ib channel create, %d\n", ret); |
ebb945a9 BS |
388 | ret = nouveau_channel_dma(drm, cli, parent, handle, pchan); |
389 | if (ret) { | |
da07e52c | 390 | NV_DEBUG(cli, "dma channel create, %d\n", ret); |
ebb945a9 BS |
391 | return ret; |
392 | } | |
393 | } | |
394 | ||
49981046 | 395 | ret = nouveau_channel_init(*pchan, arg0, arg1); |
ebb945a9 | 396 | if (ret) { |
da07e52c | 397 | NV_ERROR(cli, "channel failed to initialise, %d\n", ret); |
ebb945a9 BS |
398 | nouveau_channel_del(pchan); |
399 | return ret; | |
400 | } | |
401 | ||
402 | return 0; | |
403 | } |