1 | /* $NetBSD: i915_pci.c,v 1.15 2014/07/24 22:13:23 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: i915_pci.c,v 1.15 2014/07/24 22:13:23 riastradh Exp $" ); |
34 | |
35 | #include <sys/types.h> |
36 | #include <sys/queue.h> |
37 | #include <sys/systm.h> |
38 | #include <sys/queue.h> |
39 | #include <sys/workqueue.h> |
40 | |
41 | #include <drm/drmP.h> |
42 | |
43 | #include "i915_drv.h" |
44 | #include "i915_pci.h" |
45 | |
46 | SIMPLEQ_HEAD(i915drmkms_task_head, i915drmkms_task); |
47 | |
48 | struct i915drmkms_softc { |
49 | device_t sc_dev; |
50 | enum { |
51 | I915DRMKMS_TASK_ATTACH, |
52 | I915DRMKMS_TASK_WORKQUEUE, |
53 | } sc_task_state; |
54 | union { |
55 | struct workqueue *workqueue; |
56 | struct i915drmkms_task_head attach; |
57 | } sc_task_u; |
58 | struct drm_device *sc_drm_dev; |
59 | struct pci_dev sc_pci_dev; |
60 | }; |
61 | |
62 | static const struct intel_device_info * |
63 | i915drmkms_pci_lookup(const struct pci_attach_args *); |
64 | |
65 | static int i915drmkms_match(device_t, cfdata_t, void *); |
66 | static void i915drmkms_attach(device_t, device_t, void *); |
67 | static int i915drmkms_detach(device_t, int); |
68 | |
69 | static bool i915drmkms_suspend(device_t, const pmf_qual_t *); |
70 | static bool i915drmkms_resume(device_t, const pmf_qual_t *); |
71 | |
72 | static void i915drmkms_task_work(struct work *, void *); |
73 | |
74 | CFATTACH_DECL_NEW(i915drmkms, sizeof(struct i915drmkms_softc), |
75 | i915drmkms_match, i915drmkms_attach, i915drmkms_detach, NULL); |
76 | |
77 | /* XXX Kludge to get these from i915_drv.c. */ |
78 | extern struct drm_driver *const i915_drm_driver; |
79 | extern const struct pci_device_id *const i915_device_ids; |
80 | extern const size_t i915_n_device_ids; |
81 | |
82 | static const struct intel_device_info * |
83 | i915drmkms_pci_lookup(const struct pci_attach_args *pa) |
84 | { |
85 | size_t i; |
86 | |
87 | /* Attach only at function 0 to work around Intel lossage. */ |
88 | if (pa->pa_function != 0) |
89 | return NULL; |
90 | |
91 | /* We're interested only in Intel products. */ |
92 | if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_INTEL) |
93 | return NULL; |
94 | |
95 | /* We're interested only in Intel display devices. */ |
96 | if (PCI_CLASS(pa->pa_class) != PCI_CLASS_DISPLAY) |
97 | return NULL; |
98 | |
99 | for (i = 0; i < i915_n_device_ids; i++) |
100 | if (PCI_PRODUCT(pa->pa_id) == i915_device_ids[i].device) |
101 | break; |
102 | |
103 | /* Did we find it? */ |
104 | if (i == i915_n_device_ids) |
105 | return NULL; |
106 | |
107 | const struct intel_device_info *const info = |
108 | (const void *)(uintptr_t)i915_device_ids[i].driver_data; |
109 | |
110 | if (IS_PRELIMINARY_HW(info)) { |
111 | printf("i915drmkms: preliminary hardware support disabled\n" ); |
112 | return NULL; |
113 | } |
114 | |
115 | return info; |
116 | } |
117 | |
118 | static int |
119 | i915drmkms_match(device_t parent, cfdata_t match, void *aux) |
120 | { |
121 | extern int i915drmkms_guarantee_initialized(void); |
122 | const struct pci_attach_args *const pa = aux; |
123 | int error; |
124 | |
125 | error = i915drmkms_guarantee_initialized(); |
126 | if (error) { |
127 | aprint_error("i915drmkms: failed to initialize: %d\n" , error); |
128 | return 0; |
129 | } |
130 | |
131 | if (i915drmkms_pci_lookup(pa) == NULL) |
132 | return 0; |
133 | |
134 | return 6; /* XXX Beat genfb_pci... */ |
135 | } |
136 | |
137 | static void |
138 | i915drmkms_attach(device_t parent, device_t self, void *aux) |
139 | { |
140 | struct i915drmkms_softc *const sc = device_private(self); |
141 | const struct pci_attach_args *const pa = aux; |
142 | const struct intel_device_info *const info = i915drmkms_pci_lookup(pa); |
143 | const unsigned long cookie = |
144 | (unsigned long)(uintptr_t)(const void *)info; |
145 | int error; |
146 | |
147 | KASSERT(info != NULL); |
148 | |
149 | sc->sc_dev = self; |
150 | |
151 | pci_aprint_devinfo(pa, NULL); |
152 | |
153 | if (!pmf_device_register(self, &i915drmkms_suspend, |
154 | &i915drmkms_resume)) |
155 | aprint_error_dev(self, "unable to establish power handler\n" ); |
156 | |
157 | sc->sc_task_state = I915DRMKMS_TASK_ATTACH; |
158 | SIMPLEQ_INIT(&sc->sc_task_u.attach); |
159 | |
160 | /* XXX errno Linux->NetBSD */ |
161 | error = -drm_pci_attach(self, pa, &sc->sc_pci_dev, i915_drm_driver, |
162 | cookie, &sc->sc_drm_dev); |
163 | if (error) { |
164 | aprint_error_dev(self, "unable to attach drm: %d\n" , error); |
165 | return; |
166 | } |
167 | |
168 | while (!SIMPLEQ_EMPTY(&sc->sc_task_u.attach)) { |
169 | struct i915drmkms_task *const task = |
170 | SIMPLEQ_FIRST(&sc->sc_task_u.attach); |
171 | |
172 | SIMPLEQ_REMOVE_HEAD(&sc->sc_task_u.attach, ift_u.queue); |
173 | (*task->ift_fn)(task); |
174 | } |
175 | |
176 | sc->sc_task_state = I915DRMKMS_TASK_WORKQUEUE; |
177 | error = workqueue_create(&sc->sc_task_u.workqueue, "intelfb" , |
178 | &i915drmkms_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE); |
179 | if (error) { |
180 | aprint_error_dev(self, "unable to create workqueue: %d\n" , |
181 | error); |
182 | sc->sc_task_u.workqueue = NULL; |
183 | return; |
184 | } |
185 | } |
186 | |
187 | static int |
188 | i915drmkms_detach(device_t self, int flags) |
189 | { |
190 | struct i915drmkms_softc *const sc = device_private(self); |
191 | int error; |
192 | |
193 | /* XXX Check for in-use before tearing it all down... */ |
194 | error = config_detach_children(self, flags); |
195 | if (error) |
196 | return error; |
197 | |
198 | if (sc->sc_task_state == I915DRMKMS_TASK_ATTACH) |
199 | goto out; |
200 | if (sc->sc_task_u.workqueue != NULL) { |
201 | workqueue_destroy(sc->sc_task_u.workqueue); |
202 | sc->sc_task_u.workqueue = NULL; |
203 | } |
204 | |
205 | if (sc->sc_drm_dev == NULL) |
206 | goto out; |
207 | /* XXX errno Linux->NetBSD */ |
208 | error = -drm_pci_detach(sc->sc_drm_dev, flags); |
209 | if (error) |
210 | /* XXX Kinda too late to fail now... */ |
211 | return error; |
212 | sc->sc_drm_dev = NULL; |
213 | |
214 | out: pmf_device_deregister(self); |
215 | return 0; |
216 | } |
217 | |
218 | static bool |
219 | i915drmkms_suspend(device_t self, const pmf_qual_t *qual) |
220 | { |
221 | struct i915drmkms_softc *const sc = device_private(self); |
222 | struct drm_device *const dev = sc->sc_drm_dev; |
223 | int ret; |
224 | |
225 | if (dev == NULL) |
226 | return true; |
227 | |
228 | ret = i915_drm_freeze(dev); |
229 | if (ret) |
230 | return false; |
231 | |
232 | return true; |
233 | } |
234 | |
235 | static bool |
236 | i915drmkms_resume(device_t self, const pmf_qual_t *qual) |
237 | { |
238 | struct i915drmkms_softc *const sc = device_private(self); |
239 | struct drm_device *const dev = sc->sc_drm_dev; |
240 | int ret; |
241 | |
242 | if (dev == NULL) |
243 | return true; |
244 | |
245 | ret = i915_drm_thaw_early(dev); |
246 | if (ret) |
247 | return false; |
248 | ret = i915_drm_thaw(dev); |
249 | if (ret) |
250 | return false; |
251 | |
252 | return true; |
253 | } |
254 | |
255 | static void |
256 | i915drmkms_task_work(struct work *work, void *cookie __unused) |
257 | { |
258 | struct i915drmkms_task *const task = container_of(work, |
259 | struct i915drmkms_task, ift_u.work); |
260 | |
261 | (*task->ift_fn)(task); |
262 | } |
263 | |
264 | int |
265 | i915drmkms_task_schedule(device_t self, struct i915drmkms_task *task) |
266 | { |
267 | struct i915drmkms_softc *const sc = device_private(self); |
268 | |
269 | switch (sc->sc_task_state) { |
270 | case I915DRMKMS_TASK_ATTACH: |
271 | SIMPLEQ_INSERT_TAIL(&sc->sc_task_u.attach, task, ift_u.queue); |
272 | return 0; |
273 | case I915DRMKMS_TASK_WORKQUEUE: |
274 | if (sc->sc_task_u.workqueue == NULL) { |
275 | aprint_error_dev(self, "unable to schedule task\n" ); |
276 | return EIO; |
277 | } |
278 | workqueue_enqueue(sc->sc_task_u.workqueue, &task->ift_u.work, |
279 | NULL); |
280 | return 0; |
281 | default: |
282 | panic("i915drmkms in invalid task state: %d\n" , |
283 | (int)sc->sc_task_state); |
284 | } |
285 | } |
286 | |