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