1 | /* $NetBSD: nouveau_pci.c,v 1.8 2016/04/19 06:57:37 mrg Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2015 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: nouveau_pci.c,v 1.8 2016/04/19 06:57:37 mrg Exp $" ); |
34 | |
35 | #include <sys/types.h> |
36 | #include <sys/device.h> |
37 | #include <sys/queue.h> |
38 | #include <sys/workqueue.h> |
39 | #include <sys/module.h> |
40 | |
41 | #include <drm/drmP.h> |
42 | |
43 | #include <engine/device.h> |
44 | |
45 | #include "nouveau_drm.h" |
46 | #include "nouveau_pci.h" |
47 | |
48 | MODULE(MODULE_CLASS_DRIVER, nouveau_pci, "nouveau,drmkms_pci" ); |
49 | |
50 | SIMPLEQ_HEAD(nouveau_pci_task_head, nouveau_pci_task); |
51 | |
52 | struct nouveau_pci_softc { |
53 | device_t sc_dev; |
54 | enum { |
55 | NOUVEAU_TASK_ATTACH, |
56 | NOUVEAU_TASK_WORKQUEUE, |
57 | } sc_task_state; |
58 | union { |
59 | struct workqueue *workqueue; |
60 | struct nouveau_pci_task_head attach; |
61 | } sc_task_u; |
62 | struct drm_device *sc_drm_dev; |
63 | struct pci_dev sc_pci_dev; |
64 | struct nouveau_device *sc_nv_dev; |
65 | }; |
66 | |
67 | static int nouveau_pci_match(device_t, cfdata_t, void *); |
68 | static void nouveau_pci_attach(device_t, device_t, void *); |
69 | static int nouveau_pci_detach(device_t, int); |
70 | |
71 | static bool nouveau_pci_suspend(device_t, const pmf_qual_t *); |
72 | static bool nouveau_pci_resume(device_t, const pmf_qual_t *); |
73 | |
74 | static void nouveau_pci_task_work(struct work *, void *); |
75 | |
76 | CFATTACH_DECL_NEW(nouveau_pci, sizeof(struct nouveau_pci_softc), |
77 | nouveau_pci_match, nouveau_pci_attach, nouveau_pci_detach, NULL); |
78 | |
79 | /* Kludge to get this from nouveau_drm.c. */ |
80 | extern struct drm_driver *const nouveau_drm_driver; |
81 | |
82 | static int |
83 | nouveau_pci_match(device_t parent, cfdata_t match, void *aux) |
84 | { |
85 | const struct pci_attach_args *const pa = aux; |
86 | |
87 | if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_NVIDIA && |
88 | PCI_VENDOR(pa->pa_id) != PCI_VENDOR_NVIDIA_SGS) |
89 | return 0; |
90 | |
91 | if (PCI_CLASS(pa->pa_class) != PCI_CLASS_DISPLAY) |
92 | return 0; |
93 | |
94 | return 6; /* XXX Beat genfb_pci... */ |
95 | } |
96 | |
97 | extern char *nouveau_config; |
98 | extern char *nouveau_debug; |
99 | |
100 | static void |
101 | nouveau_pci_attach(device_t parent, device_t self, void *aux) |
102 | { |
103 | struct nouveau_pci_softc *const sc = device_private(self); |
104 | const struct pci_attach_args *const pa = aux; |
105 | uint64_t devname; |
106 | int error; |
107 | |
108 | pci_aprint_devinfo(pa, NULL); |
109 | |
110 | sc->sc_dev = self; |
111 | |
112 | if (!pmf_device_register(self, &nouveau_pci_suspend, |
113 | &nouveau_pci_resume)) |
114 | aprint_error_dev(self, "unable to establish power handler\n" ); |
115 | |
116 | sc->sc_task_state = NOUVEAU_TASK_ATTACH; |
117 | SIMPLEQ_INIT(&sc->sc_task_u.attach); |
118 | |
119 | devname = (uint64_t)device_unit(device_parent(self)) << 32; |
120 | devname |= pa->pa_bus << 16; |
121 | /* XXX errno Linux->NetBSD */ |
122 | error = -nouveau_device_create(&sc->sc_pci_dev, NOUVEAU_BUS_PCI, |
123 | devname, device_xname(self), nouveau_config, nouveau_debug, |
124 | &sc->sc_nv_dev); |
125 | if (error) { |
126 | aprint_error_dev(self, "unable to create nouveau device: %d\n" , |
127 | error); |
128 | return; |
129 | } |
130 | |
131 | /* XXX errno Linux->NetBSD */ |
132 | error = -drm_pci_attach(self, pa, &sc->sc_pci_dev, nouveau_drm_driver, |
133 | 0, &sc->sc_drm_dev); |
134 | if (error) { |
135 | aprint_error_dev(self, "unable to attach drm: %d\n" , error); |
136 | return; |
137 | } |
138 | |
139 | while (!SIMPLEQ_EMPTY(&sc->sc_task_u.attach)) { |
140 | struct nouveau_pci_task *const task = |
141 | SIMPLEQ_FIRST(&sc->sc_task_u.attach); |
142 | |
143 | SIMPLEQ_REMOVE_HEAD(&sc->sc_task_u.attach, nt_u.queue); |
144 | (*task->nt_fn)(task); |
145 | } |
146 | |
147 | sc->sc_task_state = NOUVEAU_TASK_WORKQUEUE; |
148 | error = workqueue_create(&sc->sc_task_u.workqueue, "nouveau_pci" , |
149 | &nouveau_pci_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE); |
150 | if (error) { |
151 | aprint_error_dev(self, "unable to create workqueue: %d\n" , |
152 | error); |
153 | sc->sc_task_u.workqueue = NULL; |
154 | return; |
155 | } |
156 | } |
157 | |
158 | static int |
159 | nouveau_pci_detach(device_t self, int flags) |
160 | { |
161 | struct nouveau_pci_softc *const sc = device_private(self); |
162 | int error; |
163 | |
164 | /* XXX Check for in-use before tearing it all down... */ |
165 | error = config_detach_children(self, flags); |
166 | if (error) |
167 | return error; |
168 | |
169 | if (sc->sc_task_state == NOUVEAU_TASK_ATTACH) |
170 | goto out0; |
171 | if (sc->sc_task_u.workqueue != NULL) { |
172 | workqueue_destroy(sc->sc_task_u.workqueue); |
173 | sc->sc_task_u.workqueue = NULL; |
174 | } |
175 | |
176 | if (sc->sc_nv_dev == NULL) |
177 | goto out0; |
178 | |
179 | if (sc->sc_drm_dev == NULL) |
180 | goto out1; |
181 | /* XXX errno Linux->NetBSD */ |
182 | error = -drm_pci_detach(sc->sc_drm_dev, flags); |
183 | if (error) |
184 | /* XXX Kinda too late to fail now... */ |
185 | return error; |
186 | sc->sc_drm_dev = NULL; |
187 | |
188 | out1: nouveau_object_ref(NULL, (void *)&sc->sc_nv_dev); |
189 | out0: pmf_device_deregister(self); |
190 | return 0; |
191 | } |
192 | |
193 | /* |
194 | * XXX Synchronize with nouveau_do_suspend in nouveau_drm.c. |
195 | */ |
196 | static bool |
197 | nouveau_pci_suspend(device_t self, const pmf_qual_t *qual __unused) |
198 | { |
199 | struct nouveau_pci_softc *const sc = device_private(self); |
200 | |
201 | return nouveau_pmops_suspend(sc->sc_drm_dev) == 0; |
202 | } |
203 | |
204 | static bool |
205 | nouveau_pci_resume(device_t self, const pmf_qual_t *qual) |
206 | { |
207 | struct nouveau_pci_softc *const sc = device_private(self); |
208 | |
209 | return nouveau_pmops_resume(sc->sc_drm_dev) == 0; |
210 | } |
211 | |
212 | static void |
213 | nouveau_pci_task_work(struct work *work, void *cookie __unused) |
214 | { |
215 | struct nouveau_pci_task *const task = container_of(work, |
216 | struct nouveau_pci_task, nt_u.work); |
217 | |
218 | (*task->nt_fn)(task); |
219 | } |
220 | |
221 | int |
222 | nouveau_pci_task_schedule(device_t self, struct nouveau_pci_task *task) |
223 | { |
224 | struct nouveau_pci_softc *const sc = device_private(self); |
225 | |
226 | switch (sc->sc_task_state) { |
227 | case NOUVEAU_TASK_ATTACH: |
228 | SIMPLEQ_INSERT_TAIL(&sc->sc_task_u.attach, task, nt_u.queue); |
229 | return 0; |
230 | case NOUVEAU_TASK_WORKQUEUE: |
231 | if (sc->sc_task_u.workqueue == NULL) { |
232 | aprint_error_dev(self, "unable to schedule task\n" ); |
233 | return EIO; |
234 | } |
235 | workqueue_enqueue(sc->sc_task_u.workqueue, &task->nt_u.work, |
236 | NULL); |
237 | return 0; |
238 | default: |
239 | panic("nouveau in invalid task state: %d\n" , |
240 | (int)sc->sc_task_state); |
241 | } |
242 | } |
243 | |
244 | static int |
245 | nouveau_pci_modcmd(modcmd_t cmd, void *arg __unused) |
246 | { |
247 | int error; |
248 | |
249 | switch (cmd) { |
250 | case MODULE_CMD_INIT: |
251 | error = drm_pci_init(nouveau_drm_driver, NULL); |
252 | if (error) { |
253 | aprint_error("nouveau_pci: failed to init: %d\n" , |
254 | error); |
255 | return error; |
256 | } |
257 | #if 0 /* XXX nouveau acpi */ |
258 | nouveau_register_dsm_handler(); |
259 | #endif |
260 | break; |
261 | case MODULE_CMD_FINI: |
262 | #if 0 /* XXX nouveau acpi */ |
263 | nouveau_unregister_dsm_handler(); |
264 | #endif |
265 | drm_pci_exit(nouveau_drm_driver, NULL); |
266 | break; |
267 | default: |
268 | return ENOTTY; |
269 | } |
270 | |
271 | return 0; |
272 | } |
273 | |