xref: /freebsd/sys/dev/acpica/acpi_dock.c (revision d056fa046c6a91b90cd98165face0e42a33a5173)
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$
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 
35 #include <contrib/dev/acpica/acpi.h>
36 #include <contrib/dev/acpica/acnamesp.h>
37 #include <dev/acpica/acpivar.h>
38 #include <dev/acpica/acpiio.h>
39 
40 /* Hooks for the ACPI CA debugging infrastructure */
41 #define _COMPONENT	ACPI_DOCK
42 ACPI_MODULE_NAME("DOCK")
43 
44 /* For Notify handler */
45 #define ACPI_DOCK_NOTIFY_BUS_CHECK	0x00
46 #define ACPI_DOCK_NOTIFY_DEVICE_CHECK	0x01
47 #define ACPI_DOCK_NOTIFY_EJECT_REQUEST	0x03
48 
49 /* For Docking status */
50 #define ACPI_DOCK_STATUS_UNKNOWN	-1
51 #define ACPI_DOCK_STATUS_UNDOCKED	0
52 #define ACPI_DOCK_STATUS_DOCKED		1
53 
54 /* Prevent the device from being removed or not. */
55 #define ACPI_DOCK_UNLOCK		0
56 #define ACPI_DOCK_LOCK			1
57 
58 struct acpi_dock_softc {
59 	int		_sta;
60 	int		_bdn;
61 	int		_uid;
62 	int		status;
63 	struct sysctl_ctx_list	*sysctl_ctx;
64 	struct sysctl_oid	*sysctl_tree;
65 };
66 
67 /* Global docking status, for avoiding duplicated docking */
68 static	int		acpi_dock_status = ACPI_DOCK_STATUS_UNKNOWN;
69 
70 ACPI_SERIAL_DECL(dock, "ACPI Docking Station");
71 
72 /*
73  * Utility functions
74  */
75 
76 static void
77 acpi_dock_get_info(device_t dev)
78 {
79 	struct acpi_dock_softc *sc;
80 	ACPI_HANDLE	h;
81 
82 	sc = device_get_softc(dev);
83 	h = acpi_get_handle(dev);
84 
85 	if (ACPI_FAILURE(acpi_GetInteger(h, "_STA", &sc->_sta)))
86 		sc->_sta = ACPI_DOCK_STATUS_UNKNOWN;
87 	if (ACPI_FAILURE(acpi_GetInteger(h, "_BDN", &sc->_bdn)))
88 		sc->_bdn = ACPI_DOCK_STATUS_UNKNOWN;
89 	if (ACPI_FAILURE(acpi_GetInteger(h, "_UID", &sc->_uid)))
90 		sc->_uid = ACPI_DOCK_STATUS_UNKNOWN;
91 	ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
92 		    "_STA: %04x, _BDN: %04x, _UID: %04x\n", sc->_sta,
93 		    sc->_bdn, sc->_uid);
94 }
95 
96 static int
97 acpi_dock_execute_dck(device_t dev, int dock)
98 {
99 	ACPI_HANDLE	h;
100 	ACPI_OBJECT	argobj;
101 	ACPI_OBJECT_LIST args;
102 	ACPI_BUFFER	buf;
103 	ACPI_OBJECT	retobj;
104 	ACPI_STATUS	status;
105 
106 	h = acpi_get_handle(dev);
107 
108 	argobj.Type = ACPI_TYPE_INTEGER;
109 	argobj.Integer.Value = dock;
110 	args.Count = 1;
111 	args.Pointer = &argobj;
112 	buf.Pointer = &retobj;
113 	buf.Length = sizeof(retobj);
114 	status = AcpiEvaluateObject(h, "_DCK", &args, &buf);
115 
116 	/*
117 	 * When _DCK is called with 0, OSPM will ignore the return value.
118 	 */
119 	if (dock == ACPI_DOCK_STATUS_UNDOCKED)
120 		return (0);
121 
122 	/* If _DCK returned 1, the request succeeded. */
123 	if (ACPI_SUCCESS(status) && retobj.Type == ACPI_TYPE_INTEGER &&
124 	    retobj.Integer.Value == 1)
125 		return (0);
126 
127 	return (-1);
128 }
129 
130 /* Lock devices while docked. */
131 static void
132 acpi_dock_execute_lck(device_t dev, int lock)
133 {
134 	ACPI_HANDLE	h;
135 
136 	h = acpi_get_handle(dev);
137 	acpi_SetInteger(h, "_LCK", lock);
138 }
139 
140 static int
141 acpi_dock_execute_ejx(device_t dev, int eject, int state)
142 {
143 	ACPI_HANDLE	h;
144 	ACPI_STATUS	status;
145 	char		ejx[5];
146 
147 	h = acpi_get_handle(dev);
148 	snprintf(ejx, sizeof(ejx), "_EJ%d", state);
149 	status = acpi_SetInteger(h, ejx, eject);
150 	if (ACPI_SUCCESS(status))
151 		return (0);
152 
153 	return (-1);
154 }
155 
156 static int
157 acpi_dock_is_ejd_device(ACPI_HANDLE dock_handle, ACPI_HANDLE handle)
158 {
159 	int		ret;
160 	ACPI_STATUS	ret_status;
161 	ACPI_BUFFER	ejd_buffer;
162 	ACPI_OBJECT	*obj;
163 
164 	ret = 0;
165 
166 	ejd_buffer.Pointer = NULL;
167 	ejd_buffer.Length = ACPI_ALLOCATE_BUFFER;
168 	ret_status = AcpiEvaluateObject(handle, "_EJD", NULL, &ejd_buffer);
169 	if (ACPI_FAILURE(ret_status))
170 		goto out;
171 
172 	obj = (ACPI_OBJECT *)ejd_buffer.Pointer;
173 	if (dock_handle == acpi_GetReference(NULL, obj))
174 		ret = 1;
175 
176 out:
177 	if (ejd_buffer.Pointer != NULL)
178 		AcpiOsFree(ejd_buffer.Pointer);
179 
180 	return (ret);
181 }
182 
183 /*
184  * Docking functions
185  */
186 
187 static void
188 acpi_dock_attach_later(void *context)
189 {
190 	device_t	dev;
191 
192 	dev = (device_t)context;
193 
194 	if (!device_is_enabled(dev))
195 		device_enable(dev);
196 
197 	mtx_lock(&Giant);
198 	device_probe_and_attach(dev);
199 	mtx_unlock(&Giant);
200 }
201 
202 static ACPI_STATUS
203 acpi_dock_insert_child(ACPI_HANDLE handle, UINT32 level, void *context,
204     void **status)
205 {
206 	device_t	dock_dev, dev;
207 	ACPI_HANDLE	dock_handle;
208 
209 	dock_dev = (device_t)context;
210 	dock_handle = acpi_get_handle(dock_dev);
211 
212 	if (!acpi_dock_is_ejd_device(dock_handle, handle))
213 		goto out;
214 
215 	ACPI_VPRINT(dock_dev, acpi_device_get_parent_softc(dock_dev),
216 		    "inserting device for %s\n", acpi_name(handle));
217 
218 #if 0
219 	/*
220 	 * If the system boot up w/o Docking, the devices under the dock
221 	 * still un-initialized, also control methods such as _INI, _STA
222 	 * are not executed.
223 	 * Normal devices are initialized at booting by calling
224 	 * AcpiInitializeObjects(), however the devices under the dock
225 	 * need to be initialized here on the scheme of ACPICA.
226 	 */
227 	ACPI_INIT_WALK_INFO	Info;
228 
229 	AcpiNsWalkNamespace(ACPI_TYPE_ANY, handle,
230 	    100, TRUE, AcpiNsInitOneDevice, &Info, NULL);
231 #endif
232 
233 	dev = acpi_get_device(handle);
234 	if (dev == NULL) {
235 		device_printf(dock_dev, "error: %s has no associated device\n",
236 		    acpi_name(handle));
237 		goto out;
238 	}
239 
240 	AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_dock_attach_later, dev);
241 
242 out:
243 	return (AE_OK);
244 }
245 
246 static void
247 acpi_dock_insert_children(device_t dev)
248 {
249 	ACPI_STATUS	status;
250 	ACPI_HANDLE	sb_handle;
251 
252 	status = AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &sb_handle);
253 	if (ACPI_SUCCESS(status)) {
254 		AcpiWalkNamespace(ACPI_TYPE_DEVICE, sb_handle,
255 		    100, acpi_dock_insert_child, dev, NULL);
256 	}
257 }
258 
259 static void
260 acpi_dock_insert(device_t dev)
261 {
262 	struct acpi_dock_softc *sc;
263 	ACPI_HANDLE	h;
264 
265 	ACPI_SERIAL_ASSERT(dock);
266 
267 	sc = device_get_softc(dev);
268 	h = acpi_get_handle(dev);
269 
270 	if (acpi_dock_status == ACPI_DOCK_STATUS_UNDOCKED ||
271 	    acpi_dock_status == ACPI_DOCK_STATUS_UNKNOWN) {
272 		acpi_dock_execute_lck(dev, ACPI_DOCK_LOCK);
273 		if (acpi_dock_execute_dck(dev, 1) != 0) {
274 			device_printf(dev, "_DCK failed\n");
275 			return;
276 		}
277 
278 		if (!cold)
279 			acpi_dock_insert_children(dev);
280 		sc->status = acpi_dock_status = ACPI_DOCK_STATUS_DOCKED;
281 	}
282 }
283 
284 /*
285  * Undock
286  */
287 
288 static ACPI_STATUS
289 acpi_dock_eject_child(ACPI_HANDLE handle, UINT32 level, void *context,
290     void **status)
291 {
292 	device_t	dock_dev, dev;
293 	ACPI_HANDLE	dock_handle;
294 
295 	dock_dev = *(device_t *)context;
296 	dock_handle = acpi_get_handle(dock_dev);
297 
298 	if (!acpi_dock_is_ejd_device(dock_handle, handle))
299 		goto out;
300 
301 	ACPI_VPRINT(dock_dev, acpi_device_get_parent_softc(dock_dev),
302 	    "ejecting device for %s\n", acpi_name(handle));
303 
304 	dev = acpi_get_device(handle);
305 	if (dev != NULL && device_is_attached(dev)) {
306 		mtx_lock(&Giant);
307 		device_detach(dev);
308 		mtx_unlock(&Giant);
309 	}
310 
311 	acpi_SetInteger(handle, "_EJ0", 0);
312 out:
313 	return (AE_OK);
314 }
315 
316 static void
317 acpi_dock_eject_children(device_t dev)
318 {
319 	ACPI_HANDLE	sb_handle;
320 	ACPI_STATUS	status;
321 
322 	status = AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &sb_handle);
323 	if (ACPI_SUCCESS(status)) {
324 		AcpiWalkNamespace(ACPI_TYPE_DEVICE, sb_handle,
325 		    100, acpi_dock_eject_child, &dev, NULL);
326 	}
327 }
328 
329 static void
330 acpi_dock_removal(device_t dev)
331 {
332 	struct acpi_dock_softc *sc;
333 
334 	ACPI_SERIAL_ASSERT(dock);
335 
336 	sc = device_get_softc(dev);
337 	if (acpi_dock_status == ACPI_DOCK_STATUS_DOCKED ||
338 	    acpi_dock_status == ACPI_DOCK_STATUS_UNKNOWN) {
339 		acpi_dock_eject_children(dev);
340 		if (acpi_dock_execute_dck(dev, 0) != 0)
341 			return;
342 
343 		acpi_dock_execute_lck(dev, ACPI_DOCK_UNLOCK);
344 
345 		if (acpi_dock_execute_ejx(dev, 1, 0) != 0) {
346 			device_printf(dev, "_EJ0 failed\n");
347 			return;
348 		}
349 
350 		sc->status = acpi_dock_status = ACPI_DOCK_STATUS_UNDOCKED;
351 	}
352 
353 	acpi_dock_get_info(dev);
354 	if (sc->_sta != 0)
355 		device_printf(dev, "mechanical failure (%#x).\n", sc->_sta);
356 }
357 
358 /*
359  * Device/Bus check
360  */
361 
362 static void
363 acpi_dock_device_check(device_t dev)
364 {
365 	struct acpi_dock_softc *sc;
366 
367 	ACPI_SERIAL_ASSERT(dock);
368 
369 	sc = device_get_softc(dev);
370 	acpi_dock_get_info(dev);
371 
372 	/*
373 	 * If the _STA indicates 'present' and 'functioning',
374 	 * the system is docked.
375 	 */
376 	if (ACPI_DEVICE_PRESENT(sc->_sta))
377 		acpi_dock_insert(dev);
378 	if (sc->_sta == 0)
379 		acpi_dock_removal(dev);
380 }
381 
382 /*
383  * Notify Handler
384  */
385 
386 static void
387 acpi_dock_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context)
388 {
389 	device_t	dev;
390 
391 	dev = (device_t) context;
392 	ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
393 		    "got notification %#x\n", notify);
394 
395 	ACPI_SERIAL_BEGIN(dock);
396 	switch (notify) {
397 	case ACPI_DOCK_NOTIFY_BUS_CHECK:
398 	case ACPI_DOCK_NOTIFY_DEVICE_CHECK:
399 		acpi_dock_device_check(dev);
400 		break;
401 	case ACPI_DOCK_NOTIFY_EJECT_REQUEST:
402 		acpi_dock_removal(dev);
403 		break;
404 	default:
405 		device_printf(dev, "unknown notify %#x\n", notify);
406 		break;
407 	}
408 	ACPI_SERIAL_END(dock);
409 }
410 
411 static int
412 acpi_dock_status_sysctl(SYSCTL_HANDLER_ARGS)
413 {
414 	struct acpi_dock_softc *sc;
415 	device_t	dev;
416         int		status, err;
417 
418 	err = 0;
419         dev = (device_t)arg1;
420 	sc = device_get_softc(dev);
421         status = sc->status;
422 
423 	ACPI_SERIAL_BEGIN(dock);
424         err = sysctl_handle_int(oidp, &status, 0, req);
425         if (err != 0 || req->newptr == NULL)
426                 goto out;
427 
428 	if (status != ACPI_DOCK_STATUS_UNDOCKED &&
429 	    status != ACPI_DOCK_STATUS_DOCKED) {
430 		err = EINVAL;
431                 goto out;
432 	}
433 
434 	if (status == sc->status)
435 		goto out;
436 
437 	switch (status) {
438 	case ACPI_DOCK_STATUS_UNDOCKED:
439 		acpi_dock_removal(dev);
440 		break;
441 	case ACPI_DOCK_STATUS_DOCKED:
442 		acpi_dock_device_check(dev);
443 		break;
444 	default:
445 		err = EINVAL;
446 		break;
447 	}
448 out:
449 	ACPI_SERIAL_END(dock);
450 	return (err);
451 }
452 
453 static int
454 acpi_dock_probe(device_t dev)
455 {
456 	ACPI_HANDLE	h, tmp;
457 
458 	h = acpi_get_handle(dev);
459 	if (acpi_disabled("dock") ||
460 	    ACPI_FAILURE(AcpiGetHandle(h, "_DCK", &tmp)))
461 		return (ENXIO);
462 
463 	if (acpi_dock_status == ACPI_DOCK_STATUS_DOCKED)
464 		return (ENXIO);
465 
466 	device_set_desc(dev, "ACPI Docking Station");
467 
468 	/*
469 	 * XXX Somewhere else in the kernel panics on "sysctl kern" if we
470 	 * return a negative value here (reprobe ok).
471 	 */
472 	return (0);
473 }
474 
475 static int
476 acpi_dock_attach(device_t dev)
477 {
478 	struct acpi_dock_softc *sc;
479 	ACPI_HANDLE	h;
480 
481 	sc = device_get_softc(dev);
482 	if (sc == NULL)
483 		return (ENXIO);
484 
485 	h = acpi_get_handle(dev);
486 	if (h == NULL)
487 		return (ENXIO);
488 
489 	if (acpi_dock_status == ACPI_DOCK_STATUS_DOCKED)
490 		return (ENXIO);
491 
492 	sc->status = ACPI_DOCK_STATUS_UNKNOWN;
493 
494 	AcpiEvaluateObject(h, "_INI", NULL, NULL);
495 
496 	ACPI_SERIAL_BEGIN(dock);
497 
498 	acpi_dock_device_check(dev);
499 
500         /* Get the sysctl tree */
501 	sc->sysctl_ctx = device_get_sysctl_ctx(dev);
502 	sc->sysctl_tree = device_get_sysctl_tree(dev);
503 
504 	SYSCTL_ADD_INT(sc->sysctl_ctx,
505 		SYSCTL_CHILDREN(sc->sysctl_tree),
506 		OID_AUTO, "_sta", CTLFLAG_RD,
507 		&sc->_sta, 0, "Dock _STA");
508 	SYSCTL_ADD_INT(sc->sysctl_ctx,
509 		SYSCTL_CHILDREN(sc->sysctl_tree),
510 		OID_AUTO, "_bdn", CTLFLAG_RD,
511 		&sc->_bdn, 0, "Dock _BDN");
512 	SYSCTL_ADD_INT(sc->sysctl_ctx,
513 		SYSCTL_CHILDREN(sc->sysctl_tree),
514 		OID_AUTO, "_uid", CTLFLAG_RD,
515 		&sc->_uid, 0, "Dock _UID");
516 	SYSCTL_ADD_PROC(sc->sysctl_ctx,
517 		SYSCTL_CHILDREN(sc->sysctl_tree),
518 		OID_AUTO, "status",
519 		CTLTYPE_INT|CTLFLAG_RW, dev, 0,
520 		acpi_dock_status_sysctl, "I",
521 		"Dock/Undock operation");
522 
523 	ACPI_SERIAL_END(dock);
524 
525 	AcpiInstallNotifyHandler(h, ACPI_ALL_NOTIFY,
526 				 acpi_dock_notify_handler, dev);
527 
528 	return (0);
529 }
530 
531 static device_method_t acpi_dock_methods[] = {
532 	/* Device interface */
533 	DEVMETHOD(device_probe, acpi_dock_probe),
534 	DEVMETHOD(device_attach, acpi_dock_attach),
535 
536 	{0, 0}
537 };
538 
539 static driver_t	acpi_dock_driver = {
540 	"acpi_dock",
541 	acpi_dock_methods,
542 	sizeof(struct acpi_dock_softc),
543 };
544 
545 static devclass_t acpi_dock_devclass;
546 
547 DRIVER_MODULE(acpi_dock, acpi, acpi_dock_driver, acpi_dock_devclass, 0, 0);
548 MODULE_DEPEND(acpi_dock, acpi, 1, 1, 1);
549 
550