/*- * Copyright (c) 2005-2006 Mitsuru IWASAKI * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include "opt_acpi.h" #include #include #include #include #include #include #include "acpi.h" #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_DOCK ACPI_MODULE_NAME("DOCK") /* For Notify handler */ #define ACPI_DOCK_NOTIFY_BUS_CHECK 0x00 #define ACPI_DOCK_NOTIFY_DEVICE_CHECK 0x01 #define ACPI_DOCK_NOTIFY_EJECT_REQUEST 0x03 /* For Docking status */ #define ACPI_DOCK_STATUS_UNKNOWN -1 #define ACPI_DOCK_STATUS_UNDOCKED 0 #define ACPI_DOCK_STATUS_DOCKED 1 struct acpi_dock_softc { int _sta; int _bdn; int _uid; int status; struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; }; /* Global docking status, for avoiding duplicated docking */ static int acpi_dock_status = ACPI_DOCK_STATUS_UNKNOWN; ACPI_SERIAL_DECL(dock, "ACPI Dock Station"); /* * Utility */ static void acpi_dock_get_info(device_t dev) { struct acpi_dock_softc *sc; ACPI_HANDLE h; sc = device_get_softc(dev); h = acpi_get_handle(dev); if (ACPI_FAILURE(acpi_GetInteger(h, "_STA", &sc->_sta))) { sc->_sta = ACPI_DOCK_STATUS_UNKNOWN; } ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "_STA = %04x\n", sc->_sta); if (ACPI_FAILURE(acpi_GetInteger(h, "_BDN", &sc->_bdn))) { sc->_bdn = ACPI_DOCK_STATUS_UNKNOWN; } ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "_BDN = %04x\n", sc->_bdn); if (ACPI_FAILURE(acpi_GetInteger(h, "_UID", &sc->_uid))) { sc->_uid = ACPI_DOCK_STATUS_UNKNOWN; } ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "_UID = %04x\n", sc->_uid); } static int acpi_dock_execute_dck(device_t dev, int dock) { ACPI_HANDLE h; ACPI_OBJECT argobj; ACPI_OBJECT_LIST args; ACPI_BUFFER buf; ACPI_OBJECT retobj; ACPI_STATUS status; h = acpi_get_handle(dev); argobj.Type = ACPI_TYPE_INTEGER; argobj.Integer.Value = dock; args.Count = 1; args.Pointer = &argobj; buf.Pointer = &retobj; buf.Length = sizeof(retobj); status = AcpiEvaluateObject(h, "_DCK", &args, &buf); if (dock == ACPI_DOCK_STATUS_UNDOCKED) { /* * When _DCK is called with 0, OSPM will ignore the return value. */ return (0); } if (ACPI_SUCCESS(status)) { if (retobj.Type == ACPI_TYPE_INTEGER && retobj.Integer.Value == 1) { return (0); } } return (-1); } static void acpi_dock_execute_lck(device_t dev, int lock) { ACPI_HANDLE h; h = acpi_get_handle(dev); acpi_SetInteger(h, "_LCK", lock); } static int acpi_dock_execute_ejx(device_t dev, int eject, int state) { ACPI_HANDLE h; ACPI_STATUS status; char ejx[5]; h = acpi_get_handle(dev); snprintf(ejx, sizeof(ejx), "_EJ%d", state); status = acpi_SetInteger(h, ejx, eject); if (ACPI_SUCCESS(status)) { return (0); } return (-1); } static int acpi_dock_is_ejd_device(ACPI_HANDLE dock_handle, ACPI_HANDLE handle) { int ret; ACPI_STATUS ret_status; ACPI_BUFFER ejd_buffer; ACPI_OBJECT *obj; ret = 0; ejd_buffer.Pointer = NULL; ejd_buffer.Length = ACPI_ALLOCATE_BUFFER; ret_status = AcpiEvaluateObject(handle, "_EJD", NULL, &ejd_buffer); if (ACPI_FAILURE(ret_status)) { goto out; } obj = (ACPI_OBJECT *)ejd_buffer.Pointer; if (dock_handle != acpi_GetReference(NULL, obj)) { goto out; } ret = 1; out: if (ejd_buffer.Pointer != NULL) AcpiOsFree(ejd_buffer.Pointer); return (ret); } /* * Dock */ static void acpi_dock_attach_later(void *context) { device_t dev; dev = (device_t)context; if (!device_is_enabled(dev)) { device_enable(dev); } mtx_lock(&Giant); device_probe_and_attach(dev); mtx_unlock(&Giant); } static ACPI_STATUS acpi_dock_insert_child(ACPI_HANDLE handle, UINT32 level, void *context, void **status) { device_t dock_dev, dev; ACPI_HANDLE dock_handle; dock_dev = (device_t)context; dock_handle = acpi_get_handle(dock_dev); if (!acpi_dock_is_ejd_device(dock_handle, handle)) { goto out; } ACPI_VPRINT(dock_dev, acpi_device_get_parent_softc(dock_dev), "inserting device for %s\n", acpi_name(handle)); #if 0 /* * If the system boot up w/o Docking, the devices under the dock * still un-initialized, also control methods such as _INI, _STA * are not executed. * Normal devices are initialized at booting by calling * AcpiInitializeObjects(), however the devices under the dock * need to be initialized here on the scheme of ACPICA. */ ACPI_INIT_WALK_INFO Info; AcpiNsWalkNamespace(ACPI_TYPE_ANY, handle, 100, TRUE, AcpiNsInitOneDevice, &Info, NULL); #endif dev = acpi_get_device(handle); if (dev == NULL) { ACPI_VPRINT(dock_dev, acpi_device_get_parent_softc(dock_dev), "%s has no device, something wrong\n", acpi_name(handle)); goto out; } AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_dock_attach_later, dev); out: return (AE_OK); } static void acpi_dock_insert_children(device_t dev) { ACPI_HANDLE sb_handle; if (ACPI_SUCCESS(AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &sb_handle))) { AcpiWalkNamespace(ACPI_TYPE_DEVICE, sb_handle, 100, acpi_dock_insert_child, dev, NULL); } } static void acpi_dock_insert(device_t dev) { struct acpi_dock_softc *sc; ACPI_HANDLE h; ACPI_SERIAL_ASSERT(dock); sc = device_get_softc(dev); h = acpi_get_handle(dev); if (acpi_dock_status == ACPI_DOCK_STATUS_UNDOCKED || acpi_dock_status == ACPI_DOCK_STATUS_UNKNOWN) { acpi_dock_execute_lck(dev, 1); if (acpi_dock_execute_dck(dev, 1) != 0) { device_printf(dev, "_DCK failed\n"); return; } if (!cold) { acpi_dock_insert_children(dev); } sc->status = acpi_dock_status = ACPI_DOCK_STATUS_DOCKED; } } /* * Undock */ static ACPI_STATUS acpi_dock_eject_child(ACPI_HANDLE handle, UINT32 level, void *context, void **status) { device_t dock_dev, dev; ACPI_HANDLE dock_handle; dock_dev = *(device_t *)context; dock_handle = acpi_get_handle(dock_dev); if (!acpi_dock_is_ejd_device(dock_handle, handle)) { goto out; } ACPI_VPRINT(dock_dev, acpi_device_get_parent_softc(dock_dev), "ejecting device for %s\n", acpi_name(handle)); dev = acpi_get_device(handle); if (dev != NULL && device_is_attached(dev)) { mtx_lock(&Giant); device_detach(dev); mtx_unlock(&Giant); } acpi_SetInteger(handle, "_EJ0", 0); out: return (AE_OK); } static void acpi_dock_eject_children(device_t dev) { ACPI_HANDLE sb_handle; if (ACPI_SUCCESS(AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &sb_handle))) { AcpiWalkNamespace(ACPI_TYPE_DEVICE, sb_handle, 100, acpi_dock_eject_child, &dev, NULL); } } static void acpi_dock_removal(device_t dev) { struct acpi_dock_softc *sc; ACPI_SERIAL_ASSERT(dock); sc = device_get_softc(dev); if (acpi_dock_status == ACPI_DOCK_STATUS_DOCKED || acpi_dock_status == ACPI_DOCK_STATUS_UNKNOWN) { acpi_dock_eject_children(dev); if (acpi_dock_execute_dck(dev, 0) != 0) { return; } acpi_dock_execute_lck(dev, 0); if (acpi_dock_execute_ejx(dev, 1, 0) != 0) { device_printf(dev, "_EJ0 failed\n"); return; } sc->status = acpi_dock_status = ACPI_DOCK_STATUS_UNDOCKED; } acpi_dock_get_info(dev); if (sc->_sta != 0) { device_printf(dev, "mechanical failures (%#x).\n", sc->_sta); } } /* * Device/Bus check */ static void acpi_dock_device_check(device_t dev) { struct acpi_dock_softc *sc; ACPI_SERIAL_ASSERT(dock); sc = device_get_softc(dev); acpi_dock_get_info(dev); /* * If the _STA indicates 'present' and 'functioning', * the system is docked. */ if (ACPI_DEVICE_PRESENT(sc->_sta)) { acpi_dock_insert(dev); } if (sc->_sta == 0x0) { acpi_dock_removal(dev); } } /* * Notify Handler */ static void acpi_dock_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) { device_t dev; dev = (device_t) context; ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "got notification %#x\n", notify); ACPI_SERIAL_BEGIN(dock); switch (notify) { case ACPI_DOCK_NOTIFY_BUS_CHECK: case ACPI_DOCK_NOTIFY_DEVICE_CHECK: acpi_dock_device_check(dev); break; case ACPI_DOCK_NOTIFY_EJECT_REQUEST: acpi_dock_removal(dev); break; default: device_printf(dev, "unknown notify %#x\n", notify); break; } ACPI_SERIAL_END(dock); } /* * Sysctl proc */ static int acpi_dock_status_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_dock_softc *sc; device_t dev; int status, err; err = 0; dev = (device_t)arg1; sc = device_get_softc(dev); status = sc->status; ACPI_SERIAL_BEGIN(dock); err = sysctl_handle_int(oidp, &status, 0, req); if (err != 0 || req->newptr == NULL) { goto out; } if (status != ACPI_DOCK_STATUS_UNDOCKED && status != ACPI_DOCK_STATUS_DOCKED) { err = EINVAL; goto out; } if (status == sc->status) { goto out; } switch (status) { case ACPI_DOCK_STATUS_UNDOCKED: acpi_dock_removal(dev); break; case ACPI_DOCK_STATUS_DOCKED: acpi_dock_device_check(dev); break; default: err = EINVAL; break; } out: ACPI_SERIAL_END(dock); return (err); } /* * probe/attach */ static int acpi_dock_probe(device_t dev) { ACPI_HANDLE h, tmp; h = acpi_get_handle(dev); if (acpi_disabled("dock") || ACPI_FAILURE(AcpiGetHandle(h, "_DCK", &tmp))) return (ENXIO); if (acpi_dock_status == ACPI_DOCK_STATUS_DOCKED) { return (ENXIO); } device_set_desc(dev, "ACPI Dock Station"); return (0); } static int acpi_dock_attach(device_t dev) { struct acpi_dock_softc *sc; ACPI_HANDLE h; sc = device_get_softc(dev); if (sc == NULL) return (ENXIO); h = acpi_get_handle(dev); if (h == NULL) return (ENXIO); if (acpi_dock_status == ACPI_DOCK_STATUS_DOCKED) { return (ENXIO); } sc->status = ACPI_DOCK_STATUS_UNKNOWN; AcpiEvaluateObject(h, "_INI", NULL, NULL); ACPI_SERIAL_BEGIN(dock); acpi_dock_device_check(dev); /* Get the sysctl tree */ sc->sysctl_ctx = device_get_sysctl_ctx(dev); sc->sysctl_tree = device_get_sysctl_tree(dev); SYSCTL_ADD_INT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "_sta", CTLFLAG_RD, &sc->_sta, 0, "Dock _STA"); SYSCTL_ADD_INT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "_bdn", CTLFLAG_RD, &sc->_bdn, 0, "Dock _BDN"); SYSCTL_ADD_INT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "_uid", CTLFLAG_RD, &sc->_uid, 0, "Dock _UID"); SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "status", CTLTYPE_INT|CTLFLAG_RW, dev, 0, acpi_dock_status_sysctl, "I", "Dock/Undock operation"); ACPI_SERIAL_END(dock); AcpiInstallNotifyHandler(h, ACPI_ALL_NOTIFY, acpi_dock_notify_handler, dev); return (0); } static device_method_t acpi_dock_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_dock_probe), DEVMETHOD(device_attach, acpi_dock_attach), {0, 0} }; static driver_t acpi_dock_driver = { "acpi_dock", acpi_dock_methods, sizeof(struct acpi_dock_softc), }; static devclass_t acpi_dock_devclass; DRIVER_MODULE(acpi_dock, acpi, acpi_dock_driver, acpi_dock_devclass, 0, 0); MODULE_DEPEND(acpi_dock, acpi, 1, 1, 1);