xref: /dragonfly/sys/dev/acpica/acpi_dock/acpi_dock.c (revision d4ef6694)
1 /*-
2  * Copyright (c) 2005-2006 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD: head/sys/dev/acpica/acpi_dock.c 246128 2013-01-30 18:01:20Z sbz $
27  */
28 
29 #include "opt_acpi.h"
30 #include <sys/param.h>
31 #include <sys/bus.h>
32 #include <sys/kernel.h>
33 #include <sys/module.h>
34 #include <sys/mplock2.h>
35 
36 #include <contrib/dev/acpica/source/include/acpi.h>
37 #include <contrib/dev/acpica/source/include/accommon.h>
38 
39 #include <dev/acpica/acpivar.h>
40 #include <dev/acpica/acpiio.h>
41 
42 /* Hooks for the ACPICA debugging infrastructure */
43 #define _COMPONENT	ACPI_DOCK
44 ACPI_MODULE_NAME("DOCK")
45 
46 /* For Docking status */
47 #define ACPI_DOCK_STATUS_UNKNOWN	-1
48 #define ACPI_DOCK_STATUS_UNDOCKED	0
49 #define ACPI_DOCK_STATUS_DOCKED		1
50 
51 #define ACPI_DOCK_UNLOCK		0 /* Allow device to be ejected */
52 #define ACPI_DOCK_LOCK			1 /* Prevent dev from being removed */
53 
54 #define ACPI_DOCK_ISOLATE		0 /* Isolate from dock connector */
55 #define ACPI_DOCK_CONNECT		1 /* Connect to dock */
56 
57 struct acpi_dock_softc {
58 	int		_sta;
59 	int		_bdn;
60 	int		_uid;
61 	int		status;
62 	struct sysctl_ctx_list	sysctl_ctx;
63 	struct sysctl_oid	*sysctl_tree;
64 };
65 
66 ACPI_SERIAL_DECL(dock, "ACPI Docking Station");
67 
68 /*
69  * Utility functions
70  */
71 
72 static void
73 acpi_dock_get_info(device_t dev)
74 {
75 	struct acpi_dock_softc *sc;
76 	ACPI_HANDLE	h;
77 
78 	sc = device_get_softc(dev);
79 	h = acpi_get_handle(dev);
80 
81 	if (ACPI_FAILURE(acpi_GetInteger(h, "_STA", &sc->_sta)))
82 		sc->_sta = ACPI_DOCK_STATUS_UNKNOWN;
83 	if (ACPI_FAILURE(acpi_GetInteger(h, "_BDN", &sc->_bdn)))
84 		sc->_bdn = ACPI_DOCK_STATUS_UNKNOWN;
85 	if (ACPI_FAILURE(acpi_GetInteger(h, "_UID", &sc->_uid)))
86 		sc->_uid = ACPI_DOCK_STATUS_UNKNOWN;
87 	ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
88 		    "_STA: %04x, _BDN: %04x, _UID: %04x\n", sc->_sta,
89 		    sc->_bdn, sc->_uid);
90 }
91 
92 static int
93 acpi_dock_execute_dck(device_t dev, int dock)
94 {
95 	ACPI_HANDLE	h;
96 	ACPI_OBJECT	argobj;
97 	ACPI_OBJECT_LIST args;
98 	ACPI_BUFFER	buf;
99 	ACPI_OBJECT	retobj;
100 	ACPI_STATUS	status;
101 
102 	h = acpi_get_handle(dev);
103 
104 	argobj.Type = ACPI_TYPE_INTEGER;
105 	argobj.Integer.Value = dock;
106 	args.Count = 1;
107 	args.Pointer = &argobj;
108 	buf.Pointer = &retobj;
109 	buf.Length = sizeof(retobj);
110 	status = AcpiEvaluateObject(h, "_DCK", &args, &buf);
111 
112 	/*
113 	 * When _DCK is called with 0, OSPM will ignore the return value.
114 	 */
115 	if (dock == ACPI_DOCK_ISOLATE)
116 		return (0);
117 
118 	/* If _DCK returned 1, the request succeeded. */
119 	if (ACPI_SUCCESS(status) && retobj.Type == ACPI_TYPE_INTEGER &&
120 	    retobj.Integer.Value == 1)
121 		return (0);
122 
123 	return (-1);
124 }
125 
126 /* Lock devices while docked to prevent surprise removal. */
127 static void
128 acpi_dock_execute_lck(device_t dev, int lock)
129 {
130 	ACPI_HANDLE	h;
131 
132 	h = acpi_get_handle(dev);
133 	acpi_SetInteger(h, "_LCK", lock);
134 }
135 
136 /* Eject a device (i.e., motorized). */
137 static int
138 acpi_dock_execute_ejx(device_t dev, int eject, int state)
139 {
140 	ACPI_HANDLE	h;
141 	ACPI_STATUS	status;
142 	char		ejx[5];
143 
144 	h = acpi_get_handle(dev);
145 	ksnprintf(ejx, sizeof(ejx), "_EJ%d", state);
146 	status = acpi_SetInteger(h, ejx, eject);
147 	if (ACPI_SUCCESS(status))
148 		return (0);
149 
150 	return (-1);
151 }
152 
153 /* Find dependent devices.  When their parent is removed, so are they. */
154 static int
155 acpi_dock_is_ejd_device(ACPI_HANDLE dock_handle, ACPI_HANDLE handle)
156 {
157 	int		ret;
158 	ACPI_STATUS	ret_status;
159 	ACPI_BUFFER	ejd_buffer;
160 	ACPI_OBJECT	*obj;
161 
162 	ret = 0;
163 
164 	ejd_buffer.Pointer = NULL;
165 	ejd_buffer.Length = ACPI_ALLOCATE_BUFFER;
166 	ret_status = AcpiEvaluateObject(handle, "_EJD", NULL, &ejd_buffer);
167 	if (ACPI_FAILURE(ret_status))
168 		goto out;
169 
170 	obj = (ACPI_OBJECT *)ejd_buffer.Pointer;
171 	if (dock_handle == acpi_GetReference(NULL, obj))
172 		ret = 1;
173 
174 out:
175 	if (ejd_buffer.Pointer != NULL)
176 		AcpiOsFree(ejd_buffer.Pointer);
177 
178 	return (ret);
179 }
180 
181 /*
182  * Docking functions
183  */
184 
185 static void
186 acpi_dock_attach_later(void *context)
187 {
188 	device_t	dev;
189 
190 	dev = (device_t)context;
191 
192 	if (!device_is_enabled(dev))
193 		device_enable(dev);
194 
195 	get_mplock();
196 	device_probe_and_attach(dev);
197 	rel_mplock();
198 }
199 
200 static ACPI_STATUS
201 acpi_dock_insert_child(ACPI_HANDLE handle, UINT32 level, void *context,
202     void **status)
203 {
204 	device_t	dock_dev, dev;
205 	ACPI_HANDLE	dock_handle;
206 
207 	dock_dev = (device_t)context;
208 	dock_handle = acpi_get_handle(dock_dev);
209 
210 	if (!acpi_dock_is_ejd_device(dock_handle, handle))
211 		goto out;
212 
213 	ACPI_VPRINT(dock_dev, acpi_device_get_parent_softc(dock_dev),
214 		    "inserting device for %s\n", acpi_name(handle));
215 
216 #if 0
217 	/*
218 	 * If the system boot up w/o Docking, the devices under the dock
219 	 * still un-initialized, also control methods such as _INI, _STA
220 	 * are not executed.
221 	 * Normal devices are initialized at booting by calling
222 	 * AcpiInitializeObjects(), however the devices under the dock
223 	 * need to be initialized here on the scheme of ACPICA.
224 	 */
225 	ACPI_INIT_WALK_INFO	Info;
226 
227 	AcpiNsWalkNamespace(ACPI_TYPE_ANY, handle,
228 	    100, TRUE, AcpiNsInitOneDevice, NULL, &Info, NULL);
229 #endif
230 
231 	dev = acpi_get_device(handle);
232 	if (dev == NULL) {
233 		device_printf(dock_dev, "error: %s has no associated device\n",
234 		    acpi_name(handle));
235 		goto out;
236 	}
237 
238 	AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_dock_attach_later, dev);
239 
240 out:
241 	return (AE_OK);
242 }
243 
244 static void
245 acpi_dock_insert_children(device_t dev)
246 {
247 	ACPI_STATUS	status;
248 	ACPI_HANDLE	sb_handle;
249 
250 	status = AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &sb_handle);
251 	if (ACPI_SUCCESS(status)) {
252 		AcpiWalkNamespace(ACPI_TYPE_DEVICE, sb_handle,
253 		    100, acpi_dock_insert_child, NULL, dev, NULL);
254 	}
255 }
256 
257 static void
258 acpi_dock_insert(device_t dev)
259 {
260 	struct acpi_dock_softc *sc;
261 
262 	ACPI_SERIAL_ASSERT(dock);
263 
264 	sc = device_get_softc(dev);
265 
266 	if (sc->status == ACPI_DOCK_STATUS_UNDOCKED ||
267 	    sc->status == ACPI_DOCK_STATUS_UNKNOWN) {
268 		acpi_dock_execute_lck(dev, ACPI_DOCK_LOCK);
269 		if (acpi_dock_execute_dck(dev, ACPI_DOCK_CONNECT) != 0) {
270 			device_printf(dev, "_DCK failed\n");
271 			return;
272 		}
273 
274 		if (!cold)
275 			acpi_dock_insert_children(dev);
276 		sc->status = ACPI_DOCK_STATUS_DOCKED;
277 	}
278 }
279 
280 /*
281  * Undock
282  */
283 
284 static ACPI_STATUS
285 acpi_dock_eject_child(ACPI_HANDLE handle, UINT32 level, void *context,
286     void **status)
287 {
288 	device_t	dock_dev, dev;
289 	ACPI_HANDLE	dock_handle;
290 
291 	dock_dev = *(device_t *)context;
292 	dock_handle = acpi_get_handle(dock_dev);
293 
294 	if (!acpi_dock_is_ejd_device(dock_handle, handle))
295 		goto out;
296 
297 	ACPI_VPRINT(dock_dev, acpi_device_get_parent_softc(dock_dev),
298 	    "ejecting device for %s\n", acpi_name(handle));
299 
300 	dev = acpi_get_device(handle);
301 	if (dev != NULL && device_is_attached(dev)) {
302 		get_mplock();
303 		device_detach(dev);
304 		rel_mplock();
305 	}
306 
307 	acpi_SetInteger(handle, "_EJ0", 0);
308 out:
309 	return (AE_OK);
310 }
311 
312 static void
313 acpi_dock_eject_children(device_t dev)
314 {
315 	ACPI_HANDLE	sb_handle;
316 	ACPI_STATUS	status;
317 
318 	status = AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &sb_handle);
319 	if (ACPI_SUCCESS(status)) {
320 		AcpiWalkNamespace(ACPI_TYPE_DEVICE, sb_handle,
321 		    100, acpi_dock_eject_child, NULL, &dev, NULL);
322 	}
323 }
324 
325 static void
326 acpi_dock_removal(device_t dev)
327 {
328 	struct acpi_dock_softc *sc;
329 
330 	ACPI_SERIAL_ASSERT(dock);
331 
332 	sc = device_get_softc(dev);
333 	if (sc->status == ACPI_DOCK_STATUS_DOCKED ||
334 	    sc->status == ACPI_DOCK_STATUS_UNKNOWN) {
335 		acpi_dock_eject_children(dev);
336 		if (acpi_dock_execute_dck(dev, ACPI_DOCK_ISOLATE) != 0)
337 			return;
338 
339 		acpi_dock_execute_lck(dev, ACPI_DOCK_UNLOCK);
340 
341 		if (acpi_dock_execute_ejx(dev, 1, 0) != 0) {
342 			device_printf(dev, "_EJ0 failed\n");
343 			return;
344 		}
345 
346 		sc->status = ACPI_DOCK_STATUS_UNDOCKED;
347 	}
348 
349 	acpi_dock_get_info(dev);
350 	if (sc->_sta != 0)
351 		device_printf(dev, "mechanical failure (%#x).\n", sc->_sta);
352 }
353 
354 /*
355  * Device/Bus check
356  */
357 
358 static void
359 acpi_dock_device_check(device_t dev)
360 {
361 	struct acpi_dock_softc *sc;
362 
363 	ACPI_SERIAL_ASSERT(dock);
364 
365 	sc = device_get_softc(dev);
366 	acpi_dock_get_info(dev);
367 
368 	/*
369 	 * If the _STA method indicates 'present' and 'functioning', the
370 	 * system is docked.  If _STA does not exist for this device, it
371 	 * is always present.
372 	 */
373 	if (sc->_sta == ACPI_DOCK_STATUS_UNKNOWN ||
374 	    ACPI_DEVICE_PRESENT(sc->_sta))
375 		acpi_dock_insert(dev);
376 	else if (sc->_sta == 0)
377 		acpi_dock_removal(dev);
378 }
379 
380 /*
381  * Notify Handler
382  */
383 
384 static void
385 acpi_dock_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context)
386 {
387 	device_t	dev;
388 
389 	dev = (device_t) context;
390 	ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
391 		    "got notification %#x\n", notify);
392 
393 	ACPI_SERIAL_BEGIN(dock);
394 	switch (notify) {
395 	case ACPI_NOTIFY_BUS_CHECK:
396 	case ACPI_NOTIFY_DEVICE_CHECK:
397 		acpi_dock_device_check(dev);
398 		break;
399 	case ACPI_NOTIFY_EJECT_REQUEST:
400 		acpi_dock_removal(dev);
401 		break;
402 	default:
403 		device_printf(dev, "unknown notify %#x\n", notify);
404 		break;
405 	}
406 	ACPI_SERIAL_END(dock);
407 }
408 
409 static int
410 acpi_dock_status_sysctl(SYSCTL_HANDLER_ARGS)
411 {
412 	struct acpi_dock_softc *sc;
413 	device_t	dev;
414 	int		status, err;
415 
416 	dev = (device_t)arg1;
417 
418 	sc = device_get_softc(dev);
419 	status = sc->status;
420 
421 	ACPI_SERIAL_BEGIN(dock);
422 	err = sysctl_handle_int(oidp, &status, 0, req);
423 	if (err != 0 || req->newptr == NULL)
424 		goto out;
425 
426 	if (status != ACPI_DOCK_STATUS_UNDOCKED &&
427 	    status != ACPI_DOCK_STATUS_DOCKED) {
428 		err = EINVAL;
429 		goto out;
430 	}
431 
432 	if (status == sc->status)
433 		goto out;
434 
435 	switch (status) {
436 	case ACPI_DOCK_STATUS_UNDOCKED:
437 		acpi_dock_removal(dev);
438 		break;
439 	case ACPI_DOCK_STATUS_DOCKED:
440 		acpi_dock_device_check(dev);
441 		break;
442 	default:
443 		err = EINVAL;
444 		break;
445 	}
446 out:
447 	ACPI_SERIAL_END(dock);
448 	return (err);
449 }
450 
451 static int
452 acpi_dock_probe(device_t dev)
453 {
454 	ACPI_HANDLE	h, tmp;
455 
456 	h = acpi_get_handle(dev);
457 	if (acpi_disabled("dock") ||
458 	    ACPI_FAILURE(AcpiGetHandle(h, "_DCK", &tmp)))
459 		return (ENXIO);
460 
461 	device_set_desc(dev, "ACPI Docking Station");
462 
463 	/*
464 	 * XXX Somewhere else in the kernel panics on "sysctl kern" if we
465 	 * return a negative value here (reprobe ok).
466 	 */
467 	return (0);
468 }
469 
470 static int
471 acpi_dock_attach(device_t dev)
472 {
473 	struct acpi_softc *acpi_sc;
474 	struct acpi_dock_softc *sc;
475 	ACPI_HANDLE	h;
476 
477 	sc = device_get_softc(dev);
478 	h = acpi_get_handle(dev);
479 	if (sc == NULL || h == NULL)
480 		return (ENXIO);
481 
482 	sc->status = ACPI_DOCK_STATUS_UNKNOWN;
483 
484 	AcpiEvaluateObject(h, "_INI", NULL, NULL);
485 
486 	ACPI_SERIAL_BEGIN(dock);
487 
488 	acpi_dock_device_check(dev);
489 
490 	/* Get the sysctl tree */
491 	acpi_sc = acpi_device_get_parent_softc(dev);
492 	sysctl_ctx_init(&sc->sysctl_ctx);
493 	sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
494 	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
495 	    OID_AUTO, "dock", CTLFLAG_RD, 0, "");
496 
497 	SYSCTL_ADD_INT(&sc->sysctl_ctx,
498 		SYSCTL_CHILDREN(sc->sysctl_tree),
499 		OID_AUTO, "_sta", CTLFLAG_RD,
500 		&sc->_sta, 0, "Dock _STA");
501 	SYSCTL_ADD_INT(&sc->sysctl_ctx,
502 		SYSCTL_CHILDREN(sc->sysctl_tree),
503 		OID_AUTO, "_bdn", CTLFLAG_RD,
504 		&sc->_bdn, 0, "Dock _BDN");
505 	SYSCTL_ADD_INT(&sc->sysctl_ctx,
506 		SYSCTL_CHILDREN(sc->sysctl_tree),
507 		OID_AUTO, "_uid", CTLFLAG_RD,
508 		&sc->_uid, 0, "Dock _UID");
509 	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
510 		SYSCTL_CHILDREN(sc->sysctl_tree),
511 		OID_AUTO, "status",
512 		CTLTYPE_INT|CTLFLAG_RW, dev, 0,
513 		acpi_dock_status_sysctl, "I",
514 		"Dock/Undock operation");
515 
516 	ACPI_SERIAL_END(dock);
517 
518 	AcpiInstallNotifyHandler(h, ACPI_ALL_NOTIFY,
519 				 acpi_dock_notify_handler, dev);
520 
521 	return (0);
522 }
523 
524 static int
525 acpi_dock_detach(device_t dev)
526 {
527 	struct acpi_dock_softc *sc;
528 
529 	sc = device_get_softc(dev);
530 	sysctl_ctx_free(&sc->sysctl_ctx);
531 
532 	return (0);
533 }
534 
535 static device_method_t acpi_dock_methods[] = {
536 	/* Device interface */
537 	DEVMETHOD(device_probe, acpi_dock_probe),
538 	DEVMETHOD(device_attach, acpi_dock_attach),
539 	DEVMETHOD(device_detach, acpi_dock_detach),
540 
541 	DEVMETHOD_END
542 };
543 
544 static driver_t	acpi_dock_driver = {
545 	"acpi_dock",
546 	acpi_dock_methods,
547 	sizeof(struct acpi_dock_softc),
548 };
549 
550 static devclass_t acpi_dock_devclass;
551 
552 DRIVER_MODULE(acpi_dock, acpi, acpi_dock_driver, acpi_dock_devclass, NULL, NULL);
553 MODULE_DEPEND(acpi_dock, acpi, 1, 1, 1);
554 
555