1 /* $NetBSD: i915_pci_autoconf.c,v 1.14 2022/10/15 15:20:06 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_autoconf.c,v 1.14 2022/10/15 15:20:06 riastradh Exp $"); 34 35 #include <sys/types.h> 36 #include <sys/atomic.h> 37 #include <sys/queue.h> 38 #include <sys/systm.h> 39 #include <sys/queue.h> 40 #include <sys/workqueue.h> 41 42 #include <drm/drm_ioctl.h> 43 #include <drm/drm_pci.h> 44 45 #include "i915_drv.h" 46 #include "i915_pci.h" 47 48 struct drm_device; 49 50 SIMPLEQ_HEAD(i915drmkms_task_head, i915drmkms_task); 51 52 struct i915drmkms_softc { 53 device_t sc_dev; 54 struct pci_attach_args sc_pa; 55 struct lwp *sc_task_thread; 56 struct i915drmkms_task_head sc_tasks; 57 struct workqueue *sc_task_wq; 58 struct drm_device *sc_drm_dev; 59 struct pci_dev sc_pci_dev; 60 }; 61 62 static const struct pci_device_id * 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 void i915drmkms_attach_real(device_t); 68 static int i915drmkms_detach(device_t, int); 69 70 static bool i915drmkms_suspend(device_t, const pmf_qual_t *); 71 static bool i915drmkms_resume(device_t, const pmf_qual_t *); 72 73 static void i915drmkms_task_work(struct work *, void *); 74 75 CFATTACH_DECL_NEW(i915drmkms, sizeof(struct i915drmkms_softc), 76 i915drmkms_match, i915drmkms_attach, i915drmkms_detach, NULL); 77 78 /* XXX Kludge to get these from i915_pci.c. */ 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 pci_device_id * 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 pci_device_id *ent = &i915_device_ids[i]; 108 const struct intel_device_info *const info = 109 (struct intel_device_info *)ent->driver_data; 110 111 if (info->require_force_probe) { 112 printf("i915drmkms: preliminary hardware support disabled\n"); 113 return NULL; 114 } 115 116 return ent; 117 } 118 119 static int 120 i915drmkms_match(device_t parent, cfdata_t match, void *aux) 121 { 122 extern int i915drmkms_guarantee_initialized(void); 123 const struct pci_attach_args *const pa = aux; 124 int error; 125 126 error = i915drmkms_guarantee_initialized(); 127 if (error) { 128 aprint_error("i915drmkms: failed to initialize: %d\n", error); 129 return 0; 130 } 131 132 if (i915drmkms_pci_lookup(pa) == NULL) 133 return 0; 134 135 return 6; /* XXX Beat genfb_pci... */ 136 } 137 138 static void 139 i915drmkms_attach(device_t parent, device_t self, void *aux) 140 { 141 struct i915drmkms_softc *const sc = device_private(self); 142 const struct pci_attach_args *const pa = aux; 143 int error; 144 145 pci_aprint_devinfo(pa, NULL); 146 147 /* Initialize the Linux PCI device descriptor. */ 148 linux_pci_dev_init(&sc->sc_pci_dev, self, parent, pa, 0); 149 150 sc->sc_dev = self; 151 sc->sc_pa = *pa; 152 sc->sc_task_thread = NULL; 153 SIMPLEQ_INIT(&sc->sc_tasks); 154 error = workqueue_create(&sc->sc_task_wq, "intelfb", 155 &i915drmkms_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE); 156 if (error) { 157 aprint_error_dev(self, "unable to create workqueue: %d\n", 158 error); 159 sc->sc_task_wq = NULL; 160 return; 161 } 162 163 /* 164 * Defer the remainder of initialization until we have mounted 165 * the root file system and can load firmware images. 166 */ 167 config_mountroot(self, &i915drmkms_attach_real); 168 } 169 170 static void 171 i915drmkms_attach_real(device_t self) 172 { 173 struct i915drmkms_softc *const sc = device_private(self); 174 struct pci_attach_args *const pa = &sc->sc_pa; 175 const struct pci_device_id *ent = i915drmkms_pci_lookup(pa); 176 const struct intel_device_info *const info __diagused = 177 (struct intel_device_info *)ent->driver_data; 178 int error; 179 180 KASSERT(info != NULL); 181 182 /* 183 * Cause any tasks issued synchronously during attach to be 184 * processed at the end of this function. 185 */ 186 sc->sc_task_thread = curlwp; 187 188 /* Attach the drm driver. */ 189 /* XXX errno Linux->NetBSD */ 190 error = -i915_driver_probe(&sc->sc_pci_dev, ent); 191 if (error) { 192 aprint_error_dev(self, "unable to register drm: %d\n", error); 193 return; 194 } 195 sc->sc_drm_dev = pci_get_drvdata(&sc->sc_pci_dev); 196 197 /* 198 * Now that the drm driver is attached, we can safely suspend 199 * and resume. 200 */ 201 if (!pmf_device_register(self, &i915drmkms_suspend, 202 &i915drmkms_resume)) 203 aprint_error_dev(self, "unable to establish power handler\n"); 204 205 /* 206 * Process asynchronous tasks queued synchronously during 207 * attach. This will be for display detection to attach a 208 * framebuffer, so we have the opportunity for a console device 209 * to attach before autoconf has completed, in time for init(8) 210 * to find that console without panicking. 211 */ 212 while (!SIMPLEQ_EMPTY(&sc->sc_tasks)) { 213 struct i915drmkms_task *const task = 214 SIMPLEQ_FIRST(&sc->sc_tasks); 215 216 SIMPLEQ_REMOVE_HEAD(&sc->sc_tasks, ift_u.queue); 217 (*task->ift_fn)(task); 218 } 219 220 /* Cause any subesquent tasks to be processed by the workqueue. */ 221 atomic_store_relaxed(&sc->sc_task_thread, NULL); 222 } 223 224 static int 225 i915drmkms_detach(device_t self, int flags) 226 { 227 struct i915drmkms_softc *const sc = device_private(self); 228 int error; 229 230 /* XXX Check for in-use before tearing it all down... */ 231 error = config_detach_children(self, flags); 232 if (error) 233 return error; 234 235 KASSERT(sc->sc_task_thread == NULL); 236 KASSERT(SIMPLEQ_EMPTY(&sc->sc_tasks)); 237 238 pmf_device_deregister(self); 239 if (sc->sc_drm_dev) { 240 i915_driver_remove(sc->sc_drm_dev->dev_private); 241 sc->sc_drm_dev = NULL; 242 } 243 if (sc->sc_task_wq) { 244 workqueue_destroy(sc->sc_task_wq); 245 sc->sc_task_wq = NULL; 246 } 247 linux_pci_dev_destroy(&sc->sc_pci_dev); 248 249 return 0; 250 } 251 252 static bool 253 i915drmkms_suspend(device_t self, const pmf_qual_t *qual) 254 { 255 struct i915drmkms_softc *const sc = device_private(self); 256 struct drm_device *const dev = sc->sc_drm_dev; 257 int ret; 258 259 drm_suspend_ioctl(dev); 260 261 ret = i915_drm_prepare(dev); 262 if (ret) 263 return false; 264 ret = i915_drm_suspend(dev); 265 if (ret) 266 return false; 267 ret = i915_drm_suspend_late(dev, false); 268 if (ret) 269 return false; 270 271 return true; 272 } 273 274 static bool 275 i915drmkms_resume(device_t self, const pmf_qual_t *qual) 276 { 277 struct i915drmkms_softc *const sc = device_private(self); 278 struct drm_device *const dev = sc->sc_drm_dev; 279 int ret; 280 281 ret = i915_drm_resume_early(dev); 282 if (ret) 283 goto out; 284 ret = i915_drm_resume(dev); 285 if (ret) 286 goto out; 287 288 out: drm_resume_ioctl(dev); 289 return ret == 0; 290 } 291 292 static void 293 i915drmkms_task_work(struct work *work, void *cookie __unused) 294 { 295 struct i915drmkms_task *const task = container_of(work, 296 struct i915drmkms_task, ift_u.work); 297 298 (*task->ift_fn)(task); 299 } 300 301 void 302 i915drmkms_task_schedule(device_t self, struct i915drmkms_task *task) 303 { 304 struct i915drmkms_softc *const sc = device_private(self); 305 306 if (atomic_load_relaxed(&sc->sc_task_thread) == curlwp) 307 SIMPLEQ_INSERT_TAIL(&sc->sc_tasks, task, ift_u.queue); 308 else 309 workqueue_enqueue(sc->sc_task_wq, &task->ift_u.work, NULL); 310 } 311